Removed expo notifications

This commit is contained in:
Arnaud Vergnet 2020-06-24 11:39:49 +02:00
parent 2acdfca975
commit 88470055c8
6 changed files with 0 additions and 674 deletions

View file

@ -1,120 +0,0 @@
<?php
class Dao
{
private $conn;
private $debug = false;
private function get_debug_mode()
{
$this->debug = file_exists(__DIR__ . DIRECTORY_SEPARATOR . "DEBUG");
}
public function __construct()
{
$this->get_debug_mode();
if ($this->debug) {
$username = 'test';
$password = $this->read_password();;
$dsn = 'mysql:dbname=test;host=127.0.0.1';
} else {
$username = 'amicale_app';
$password = $this->read_password();
$dsn = 'mysql:dbname=amicale_app;host=127.0.0.1';
}
try {
$this->conn = new PDO($dsn, $username, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']);
} catch (PDOException $e) {
echo $e;
}
}
private function read_password()
{
if ($this->debug)
$real_path = __DIR__ . DIRECTORY_SEPARATOR . ".htpassdb_debug";
else
$real_path = __DIR__ . DIRECTORY_SEPARATOR . ".htpassdb";
$file = fopen($real_path, "r") or die("Unable to open DB password file!");;
$password = fgets($file);
fclose($file);
return trim($password);
}
/**
* Return the list of machines watched by the user associated by the given token
*
* @param $token
* @return array
*/
public function get_machine_watchlist($token) {
$this->register_user($token);
$sql = "SELECT machine_id FROM machine_watchlist WHERE user_token=:token";
$cursor = $this->conn->prepare($sql); // Protect against SQL injections
$cursor->bindParam(':token', $token);
$cursor->execute();
$result = $cursor->fetchAll();
$finalArray = [];
foreach ($result as $row) {
array_push($finalArray, $row["machine_id"]);
}
return $finalArray;
}
public function set_machine_reminder($token, $time) {
$this->register_user($token);
$sql = "UPDATE users SET machine_reminder_time=:time WHERE token=:token";
$cursor = $this->conn->prepare($sql); // Protect against SQL injections
$cursor->bindParam(':token', $token);
$cursor->bindParam(':time', $time);
var_dump($cursor->execute());
}
/**
* Add/Remove a machine from the database for the specified token.
*
* @param $token
* @param $machine_id
* @param $should_add
*/
public function update_machine_end_token($token, $machine_id, $should_add, $locale)
{
$this->register_user($token);
$this->update_user_locale($token, $locale);
if ($should_add)
$sql = "INSERT INTO machine_watchlist (machine_id, user_token) VALUES (:id, :token)";
else
$sql = "DELETE FROM machine_watchlist WHERE machine_id=:id AND user_token=:token";
$cursor = $this->conn->prepare($sql); // Protect against SQL injections
$cursor->bindParam(':id', $machine_id);
$cursor->bindParam(':token', $token);
$cursor->execute();
}
/**
* Register user in the database if not already in it
* @param $userToken
* @param $locale
*/
private function register_user($userToken) {
$sql = "INSERT INTO users (token) VALUES (:token)";
$cursor = $this->conn->prepare($sql); // Protect against SQL injections
$cursor->bindParam(':token', $userToken);
$cursor->execute();
}
private function update_user_locale($token, $locale) {
$sql = "UPDATE users SET locale=:locale WHERE token=:token";
$cursor = $this->conn->prepare($sql); // Protect against SQL injections
$cursor->bindParam(':token', $token);
$cursor->bindParam(':locale', $locale);
$cursor->execute();
}
}

View file

@ -1,10 +0,0 @@
{
"endNotification": {
"title": "Machine n°%s finished",
"body": "Come get your laundry in machine n°%s"
},
"reminderNotification": {
"title": "Machine n°%s running...",
"body": "Don't forget your laundry in machine n°%s"
}
}

View file

@ -1,319 +0,0 @@
from collections import namedtuple
import json
class PushResponseError(Exception):
"""Base class for all push reponse errors"""
def __init__(self, push_response):
if push_response.message:
self.message = push_response.message
else:
self.message = 'Unknown push response error'
super(PushResponseError, self).__init__(self.message)
self.push_response = push_response
class DeviceNotRegisteredError(PushResponseError):
"""Raised when the push token is invalid
To handle this error, you should stop sending messages to this token.
"""
pass
class MessageTooBigError(PushResponseError):
"""Raised when the notification was too large.
On Android and iOS, the total payload must be at most 4096 bytes.
"""
pass
class MessageRateExceededError(PushResponseError):
"""Raised when you are sending messages too frequently to a device
You should implement exponential backoff and slowly retry sending messages.
"""
pass
class PushServerError(Exception):
"""Raised when the push token server is not behaving as expected
For example, invalid push notification arguments result in a different
style of error. Instead of a "data" array containing errors per
notification, an "error" array is returned.
{"errors": [
{"code": "API_ERROR",
"message": "child \"to\" fails because [\"to\" must be a string]. \"value\" must be an array."
}
]}
"""
def __init__(self, message, response, response_data=None, errors=None):
self.message = message
self.response = response
self.response_data = response_data
self.errors = errors
super(PushServerError, self).__init__(self.message)
class PushMessage(namedtuple('PushMessage', [
'to', 'data', 'title', 'body', 'sound', 'ttl', 'expiration',
'priority', 'badge', 'channel_id'])):
"""An object that describes a push notification request.
You can override this class to provide your own custom validation before
sending these to the Exponent push servers. You can also override the
get_payload function itself to take advantage of any hidden or new
arguments before this library updates upstream.
Args:
to: A token of the form ExponentPushToken[xxxxxxx]
data: A dict of extra data to pass inside of the push notification.
The total notification payload must be at most 4096 bytes.
title: The title to display in the notification. On iOS, this is
displayed only on Apple Watch.
body: The message to display in the notification.
sound: A sound to play when the recipient receives this
notification. Specify "default" to play the device's default
notification sound, or omit this field to play no sound.
ttl: The number of seconds for which the message may be kept around
for redelivery if it hasn't been delivered yet. Defaults to 0.
expiration: UNIX timestamp for when this message expires. It has
the same effect as ttl, and is just an absolute timestamp
instead of a relative one.
priority: Delivery priority of the message. 'default', 'normal',
and 'high' are the only valid values.
badge: An integer representing the unread notification count. This
currently only affects iOS. Specify 0 to clear the badge count.
channel_id: ID of the Notification Channel through which to display
this notification on Android devices.
"""
def get_payload(self):
# Sanity check for invalid push token format.
if not PushClient.is_exponent_push_token(self.to):
raise ValueError('Invalid push token')
# There is only one required field.
payload = {
'to': self.to,
}
# All of these fields are optional.
if self.data is not None:
payload['data'] = self.data
if self.title is not None:
payload['title'] = self.title
if self.body is not None:
payload['body'] = self.body
if self.sound is not None:
payload['sound'] = self.sound
if self.ttl is not None:
payload['ttl'] = self.ttl
if self.expiration is not None:
payload['expiration'] = self.expiration
if self.priority is not None:
payload['priority'] = self.priority
if self.badge is not None:
payload['badge'] = self.badge
if self.channel_id is not None:
payload['channelId'] = self.channel_id
return payload
# Allow optional arguments for PushMessages since everything but the `to` field
# is optional. Unfortunately namedtuples don't allow for an easy way to create
# a required argument at the contructor level right now.
PushMessage.__new__.__defaults__ = (None,) * len(PushMessage._fields)
class PushResponse(namedtuple('PushResponse', [
'push_message', 'status', 'message', 'details'])):
"""Wrapper class for a push notification response.
A successful single push notification:
{'status': 'ok'}
An invalid push token
{'status': 'error',
'message': '"adsf" is not a registered push notification recipient'}
"""
# Known status codes
ERROR_STATUS = 'error'
SUCCESS_STATUS = 'ok'
# Known error strings
ERROR_DEVICE_NOT_REGISTERED = 'DeviceNotRegistered'
ERROR_MESSAGE_TOO_BIG = 'MessageTooBig'
ERROR_MESSAGE_RATE_EXCEEDED = 'MessageRateExceeded'
def is_success(self):
"""Returns True if this push notification successfully sent."""
return self.status == PushResponse.SUCCESS_STATUS
def validate_response(self):
"""Raises an exception if there was an error. Otherwise, do nothing.
Clients should handle these errors, since these require custom handling
to properly resolve.
"""
if self.is_success():
return
# Handle the error if we have any information
if self.details:
error = self.details.get('error', None)
if error == PushResponse.ERROR_DEVICE_NOT_REGISTERED:
raise DeviceNotRegisteredError(self)
elif error == PushResponse.ERROR_MESSAGE_TOO_BIG:
raise MessageTooBigError(self)
elif error == PushResponse.ERROR_MESSAGE_RATE_EXCEEDED:
raise MessageRateExceededError(self)
# No known error information, so let's raise a generic error.
raise PushResponseError(self)
class PushClient(object):
"""Exponent push client
See full API docs at https://docs.expo.io/versions/latest/guides/push-notifications.html#http2-api
"""
DEFAULT_HOST = "https://exp.host"
DEFAULT_BASE_API_URL = "/--/api/v2"
def __init__(self, host=None, api_url=None):
"""Construct a new PushClient object.
Args:
host: The server protocol, hostname, and port.
api_url: The api url at the host.
"""
self.host = host
if not self.host:
self.host = PushClient.DEFAULT_HOST
self.api_url = api_url
if not self.api_url:
self.api_url = PushClient.DEFAULT_BASE_API_URL
@classmethod
def is_exponent_push_token(cls, token):
"""Returns `True` if the token is an Exponent push token"""
import six
return (
isinstance(token, six.string_types) and
token.startswith('ExponentPushToken'))
def _publish_internal(self, push_messages):
"""Send push notifications
The server will validate any type of syntax errors and the client will
raise the proper exceptions for the user to handle.
Each notification is of the form:
{
'to': 'ExponentPushToken[xxx]',
'body': 'This text gets display in the notification',
'badge': 1,
'data': {'any': 'json object'},
}
Args:
push_messages: An array of PushMessage objects.
"""
# Delayed import because this file is immediately read on install, and
# the requests library may not be installed yet.
import requests
response = requests.post(
self.host + self.api_url + '/push/send',
data=json.dumps([pm.get_payload() for pm in push_messages]),
headers={
'accept': 'application/json',
'accept-encoding': 'gzip, deflate',
'content-type': 'application/json',
}
)
# Let's validate the response format first.
try:
response_data = response.json()
except ValueError:
# The response isn't json. First, let's attempt to raise a normal
# http error. If it's a 200, then we'll raise our own error.
response.raise_for_status()
raise PushServerError('Invalid server response', response)
# If there are errors with the entire request, raise an error now.
if 'errors' in response_data:
raise PushServerError(
'Request failed',
response,
response_data=response_data,
errors=response_data['errors'])
# We expect the response to have a 'data' field with the responses.
if 'data' not in response_data:
raise PushServerError(
'Invalid server response',
response,
response_data=response_data)
# Use the requests library's built-in exceptions for any remaining 4xx
# and 5xx errors.
response.raise_for_status()
# Sanity check the response
if len(push_messages) != len(response_data['data']):
raise PushServerError(
('Mismatched response length. Expected %d %s but only '
'received %d' % (
len(push_messages),
'receipt' if len(push_messages) == 1 else 'receipts',
len(response_data['data']))),
response,
response_data=response_data)
# At this point, we know it's a 200 and the response format is correct.
# Now let's parse the responses per push notification.
receipts = []
for i, receipt in enumerate(response_data['data']):
receipts.append(PushResponse(
push_message=push_messages[i],
# If there is no status, assume error.
status=receipt.get('status', PushResponse.ERROR_STATUS),
message=receipt.get('message', ''),
details=receipt.get('details', None)))
return receipts
def publish(self, push_message):
"""Sends a single push notification
Args:
push_message: A single PushMessage object.
Returns:
A PushResponse object which contains the results.
"""
return self.publish_multiple([push_message])[0]
def publish_multiple(self, push_messages):
"""Sends multiple push notifications at once
Args:
push_messages: An array of PushMessage objects.
Returns:
An array of PushResponse objects which contains the results.
"""
return self._publish_internal(push_messages)

View file

@ -1,10 +0,0 @@
{
"endNotification": {
"title": "Machine n°%s terminée",
"body": "Vous pouvez venir chercher votre linge dans la machine n°%s"
},
"reminderNotification": {
"title": "Machine n°%s en cours...",
"body": "N'oubliez pas votre linge dans la machine n°%s"
}
}

View file

@ -1,163 +0,0 @@
from exponent_server_sdk import PushClient
from exponent_server_sdk import PushMessage
import mysql.connector # using lib from https://github.com/expo/expo-server-sdk-python
import json
from enum import Enum
isDebug = False
class Priority(Enum):
DEFAULT = 'default'
NORMAL = 'normal'
HIGH = 'high'
class ChannelIDs(Enum):
REMINDERS = 'reminders'
class MachineStates(Enum):
FINISHED = 'TERMINE'
READY = 'DISPONIBLE'
RUNNING = 'EN COURS'
BROKEN = 'HS'
ERROR = 'ERROR'
if isDebug:
washinsaFile = 'data.json'
db = mysql.connector.connect(
host="127.0.0.1",
user="test",
passwd="coucou",
database="test"
)
else:
washinsaFile = '../washinsa/washinsa.json'
db = mysql.connector.connect(
host="127.0.0.1",
user="amicale_app",
passwd="EYiDCalfNj",
database="amicale_app"
)
def send_push_message(token, title, body, channel_id, extra=None):
prio = Priority.HIGH.value if channel_id == ChannelIDs.REMINDERS.value else Priority.NORMAL.value
print(prio)
response = PushClient().publish(
PushMessage(to=token,
title=title,
body=body,
data=extra,
sound='default',
priority=prio))
def get_machines_of_state(state):
machines = []
with open(washinsaFile) as f:
data = json.load(f)
for d in data['dryers']:
if d['state'] == state:
machines.append(d['number'])
for d in data['washers']:
if d['state'] == state:
machines.append(d['number'])
return machines
def get_machine_remaining_time(machine_id):
with open(washinsaFile) as f:
data = json.load(f)
for d in data['dryers']:
if d['number'] == machine_id:
return int(d['remainingTime'])
for d in data['washers']:
if d['number'] == machine_id:
return int(d['remainingTime'])
return 0
def send_end_notifications():
cursor = db.cursor()
machines = get_machines_of_state(MachineStates.FINISHED.value)
for machine_id in machines:
cursor.execute('SELECT * FROM machine_watchlist WHERE machine_id=%s', (machine_id,))
result = cursor.fetchall()
for r in result:
token = r[2]
translation = get_notification_translation(token, False)
body = translation["body"].replace("%s", machine_id, 1)
title = translation["title"].replace("%s", machine_id, 1)
# Remove from db
cursor.execute('DELETE FROM machine_watchlist WHERE machine_id=%s AND user_token=%s', (machine_id, token,))
db.commit()
send_push_message(token, title, body, ChannelIDs.REMINDERS.value)
def send_reminder_notifications():
cursor = db.cursor()
machines = get_machines_of_state(MachineStates.RUNNING.value)
print(machines)
for machine_id in machines:
remaining_time = get_machine_remaining_time(machine_id)
print(remaining_time)
cursor.execute('SELECT * FROM machine_watchlist WHERE machine_id=%s', (machine_id,))
result = cursor.fetchall()
print(result)
for r in result:
if r[3] == 0: # We did not send a reminder notification yet
token = r[2]
user_reminder_time = get_user_reminder_time(token)
if user_reminder_time >= remaining_time:
translation = get_notification_translation(token, True)
body = translation["body"].replace("%s", machine_id, 1)
title = translation["title"].replace("%s", machine_id, 1)
cursor.execute(
'UPDATE machine_watchlist SET reminder_sent=%s WHERE machine_id=%s AND user_token=%s',
(1, machine_id, token,))
db.commit()
send_push_message(token, title, body, ChannelIDs.REMINDERS.value)
def get_user_reminder_time(token):
cursor = db.cursor()
cursor.execute('SELECT machine_reminder_time FROM users WHERE token=%s', (token,))
result = cursor.fetchall()
print(result[0][0])
return result[0][0]
def get_user_locale(token):
cursor = db.cursor()
cursor.execute('SELECT locale FROM users WHERE token=%s', (token,))
result = cursor.fetchall()
print(result[0][0])
locale = 'en'
if "fr" in result[0][0]:
locale = 'fr'
return locale
def get_notification_translation(token, is_reminder):
locale = get_user_locale(token)
file_name = locale + '.json'
print(file_name)
with open(file_name) as f:
data = json.load(f)
if is_reminder:
translation = data["reminderNotification"]
else:
translation = data["endNotification"]
print(translation)
return translation
def main():
send_reminder_notifications()
send_end_notifications()
main()

View file

@ -1,52 +0,0 @@
<?php
require_once 'dao.php';
$password = "df1g3f1ghdf54qds3f879";
$rest_json = file_get_contents("php://input");
$_POST = json_decode($rest_json, true);
if (!isset($_POST['password']) || isset($_POST['password']) != $password)
die("Access denied");
if (isset($_POST['function'])) {
if ($_POST['function'] == "setup_machine_notification")
setup_machine_notification();
elseif ($_POST['function'] == "get_machine_watchlist")
get_machine_watchlist();
elseif ($_POST['function'] == "set_machine_reminder")
set_machine_reminder();
} else
show_error();
function setup_machine_notification() {
$token = $_POST['token'];
$enabled = boolval($_POST['enabled']);
$machineId = intval($_POST['machine_id']);
$locale = $_POST['locale'];
$dao = new Dao();
$dao->update_machine_end_token($token, $machineId, $enabled, $locale);
}
function get_machine_watchlist() {
$token = $_POST['token'];
$dao = new Dao();
echo json_encode($dao->get_machine_watchlist($token));
}
function set_machine_reminder() {
$token = $_POST['token'];
$time = intval($_POST['time']);
$dao = new Dao();
$dao->set_machine_reminder($token, $time);
}
function show_error() {
echo "Échec :\n";
var_dump($_POST);
}