Added remaining scripts
This commit is contained in:
parent
4ebdbe3863
commit
e91e6ba41e
24 changed files with 164332 additions and 0 deletions
14
custom_css/RU/customGeneral.css
Normal file
14
custom_css/RU/customGeneral.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
body {
|
||||
font-size: 1.5rem;
|
||||
text-shadow: none;
|
||||
font-family: 'Roboto';
|
||||
}
|
||||
|
||||
|
||||
#ru_page {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
.ui-bar, header {
|
||||
display: none;
|
||||
}
|
4
custom_css/RU/customLight.css
Normal file
4
custom_css/RU/customLight.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
#ru_page {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
3
custom_css/bluemind/customMobile.css
Normal file
3
custom_css/bluemind/customMobile.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
#mailview-bottom {
|
||||
min-height: 1000px;
|
||||
}
|
15
custom_css/ent/customMobile.css
Normal file
15
custom_css/ent/customMobile.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
#columns-table > tbody > tr > td {
|
||||
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.portlet {
|
||||
|
||||
padding : 5px;
|
||||
|
||||
}
|
23
custom_css/planex/customDark.css
Normal file
23
custom_css/planex/customDark.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
body {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
.fc-unthemed .fc-content, .fc-unthemed .fc-divider, .fc-unthemed .fc-list-heading td, .fc-unthemed .fc-list-view, .fc-unthemed .fc-popover, .fc-unthemed .fc-row, .fc-unthemed tbody, .fc-unthemed td, .fc-unthemed th, .fc-unthemed thead {
|
||||
border-color: #505050;
|
||||
}
|
||||
|
||||
h2, table, .fc-toolbar .fc-center > * {
|
||||
color: #ebebeb;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.fc-event-container .fc-bg {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fc-unthemed td.fc-today {
|
||||
background-color: #444;
|
||||
}
|
23
custom_css/planex/customDark2.css
Normal file
23
custom_css/planex/customDark2.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
body {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
.fc-unthemed .fc-content, .fc-unthemed .fc-divider, .fc-unthemed .fc-list-heading td, .fc-unthemed .fc-list-view, .fc-unthemed .fc-popover, .fc-unthemed .fc-row, .fc-unthemed tbody, .fc-unthemed td, .fc-unthemed th, .fc-unthemed thead {
|
||||
border-color: #505050;
|
||||
}
|
||||
|
||||
h2, table, .fc-toolbar .fc-center > * {
|
||||
color: #ebebeb;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.fc-event-container .fc-bg {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fc-unthemed td.fc-today {
|
||||
background-color: #444;
|
||||
}
|
98
custom_css/planex/customMobile.css
Normal file
98
custom_css/planex/customMobile.css
Normal file
|
@ -0,0 +1,98 @@
|
|||
body > .container {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fc-toolbar .fc-center {
|
||||
width: 100%;
|
||||
}
|
||||
.fc-toolbar .fc-center > * {
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#rotateToLandscape {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rotateToPortrait{
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 575px) {
|
||||
body > .container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
#rotateToLandscape {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#rotateToPortrait{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#calendar .fc-view-container {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#calendar .fc-agendaWeek-view {
|
||||
width: 225%;
|
||||
}
|
||||
|
||||
#calendar .fc-month-view {
|
||||
width: 250%;
|
||||
}
|
||||
|
||||
#entite {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
|
||||
#entite,
|
||||
#groupe,
|
||||
#calendar .fc-left,
|
||||
#calendar .fc-right {
|
||||
width: calc(100% - 20px);
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#calendar .fc-right .fc-button-group {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#calendar .fc-button {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
#calendar .fc-left .fc-button-group {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#calendar .fc-left .fc-button-group .fc-button {
|
||||
font-size: 1.2rem;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
#groupe_visibility {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title {
|
||||
font-size: 0.7rem
|
||||
}
|
||||
|
||||
.tooltiptopicevent h1 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tooltiptopicevent {
|
||||
border-radius: 0.2rem;
|
||||
border: solid 1px #717171;
|
||||
}
|
||||
}
|
98
custom_css/planex/customMobile2.css
Normal file
98
custom_css/planex/customMobile2.css
Normal file
|
@ -0,0 +1,98 @@
|
|||
body > .container {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fc-toolbar .fc-center {
|
||||
width: 100%;
|
||||
}
|
||||
.fc-toolbar .fc-center > * {
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#rotateToLandscape {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rotateToPortrait{
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 575px) {
|
||||
body > .container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
#rotateToLandscape {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#rotateToPortrait{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#calendar .fc-view-container {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#calendar .fc-agendaWeek-view {
|
||||
width: 225%;
|
||||
}
|
||||
|
||||
#calendar .fc-month-view {
|
||||
width: 250%;
|
||||
}
|
||||
|
||||
#entite {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
|
||||
#entite,
|
||||
#groupe,
|
||||
#calendar .fc-left,
|
||||
#calendar .fc-right {
|
||||
width: calc(100% - 20px);
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#calendar .fc-right .fc-button-group {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#calendar .fc-button {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
#calendar .fc-left .fc-button-group {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#calendar .fc-left .fc-button-group .fc-button {
|
||||
font-size: 1.2rem;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
#groupe_visibility {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title {
|
||||
font-size: 0.7rem
|
||||
}
|
||||
|
||||
.tooltiptopicevent h1 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tooltiptopicevent {
|
||||
border-radius: 0.2rem;
|
||||
border: solid 1px #717171;
|
||||
}
|
||||
}
|
181
custom_css/planex/customMobile3.css
Normal file
181
custom_css/planex/customMobile3.css
Normal file
|
@ -0,0 +1,181 @@
|
|||
body > .container {
|
||||
|
||||
padding-top: 10px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
header {
|
||||
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fc-toolbar .fc-center {
|
||||
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.fc-toolbar .fc-center > * {
|
||||
|
||||
float: none;
|
||||
|
||||
width: 100%;
|
||||
|
||||
margin: 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#entite {
|
||||
|
||||
margin-bottom: 5px !important;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#entite,
|
||||
|
||||
#groupe,
|
||||
|
||||
#calendar .fc-left,
|
||||
|
||||
#calendar .fc-right {
|
||||
|
||||
width: calc(100% - 20px);
|
||||
|
||||
margin: 0 10px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-right {
|
||||
|
||||
display: flex;
|
||||
|
||||
margin-top: 5px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-right .fc-button-group {
|
||||
|
||||
margin: 0;
|
||||
|
||||
margin-left: auto;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-button {
|
||||
|
||||
margin: 2px 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-left .fc-button-group {
|
||||
|
||||
float: right;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-left .fc-button-group .fc-button {
|
||||
|
||||
font-size: 1.2rem;
|
||||
|
||||
margin: 0 0 0 5px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#groupe_visibility {
|
||||
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title {
|
||||
|
||||
font-size: 0.6rem
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-time {
|
||||
|
||||
font-size: 0.5rem
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-month-view .fc-content-skeleton .fc-title {
|
||||
|
||||
font-size: 0.6rem
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#calendar .fc-month-view .fc-content-skeleton .fc-time {
|
||||
|
||||
font-size: 0.7rem
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fc-axis {
|
||||
|
||||
font-size: 0.8rem;
|
||||
|
||||
width: 15px !important;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fc-day-header {
|
||||
|
||||
font-size: 0.8rem;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tooltiptopicevent h1 {
|
||||
|
||||
font-size: 0.8rem;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tooltiptopicevent {
|
||||
|
||||
border-radius: 0.2rem;
|
||||
|
||||
border: solid 1px #717171;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
53
custom_css/rooms/customBibMobile.css
Normal file
53
custom_css/rooms/customBibMobile.css
Normal file
|
@ -0,0 +1,53 @@
|
|||
.navbar,.hero-unit, footer {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.hero-unit3, .hero-unit2, .hero-unit-form {
|
||||
background-color: #fff;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-unit-form h4 {
|
||||
font-size: 2rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.5rem;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-image: none;
|
||||
background-color: #be1522;
|
||||
}
|
||||
|
||||
.table {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.table td {
|
||||
padding: 0;
|
||||
height: 18.2333px;
|
||||
border: none;
|
||||
border-bottom: 1px solid #c1c1c1;
|
||||
}
|
||||
|
||||
.table td[style="max-width:55px;"] {
|
||||
max-width: 110px !important;
|
||||
}
|
||||
|
||||
.table-bordered {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
th {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.table-bordered {
|
||||
border-collapse: collapse;
|
||||
}
|
52
custom_css/rooms/customMobile.css
Normal file
52
custom_css/rooms/customMobile.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
body, body > .container2 {
|
||||
padding-top: 0;
|
||||
width: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
tbody tr, header, b, br, body > .container2 > h3, body > .container2 > h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-bordered td, .table-bordered th {
|
||||
border: none;
|
||||
border-right: 1px solid #dee2e6;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.table {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 200%;
|
||||
max-width: 200%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
tbody {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
thead {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tbody tr[bgcolor], thead tr {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.table tbody td, .table thead td[colspan] {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table tbody td[bgcolor="white"], .table thead td {
|
||||
flex: 0 0 150px;
|
||||
height: 50px;
|
||||
}
|
1
dashboard/dashboard_data.json
Normal file
1
dashboard/dashboard_data.json
Normal file
File diff suppressed because one or more lines are too long
0
dashboard/err
Normal file
0
dashboard/err
Normal file
142
dashboard/handler.py
Normal file
142
dashboard/handler.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
import json
|
||||
from datetime import date
|
||||
import urllib.request
|
||||
import os.path
|
||||
|
||||
WASHINSA_FILE = '../washinsa/washinsa.json'
|
||||
MENU_FILE = '../menu/menu_data.json'
|
||||
FACEBOOK_FILE = '../facebook/facebook_data.json'
|
||||
FACEBOOK_LOCK = '../facebook/lock'
|
||||
PROXIMO_URL = 'https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json'
|
||||
TUTORINSA_URL = 'https://etud.insa-toulouse.fr/~tutorinsa/api/get_data.php'
|
||||
PLANNING_URL = 'https://amicale-insat.fr/event/json/list'
|
||||
|
||||
DASHBOARD_FILE = 'dashboard_data.json'
|
||||
|
||||
|
||||
def get_available_machines():
|
||||
"""
|
||||
Get the number of available washing/drying machines
|
||||
|
||||
:return: a tuple containing the number of available dryers and washers
|
||||
"""
|
||||
with open(WASHINSA_FILE) as f:
|
||||
data = json.load(f)
|
||||
available_dryers = 0
|
||||
available_washers = 0
|
||||
for machine in data['dryers']:
|
||||
if machine['state'] == 'DISPONIBLE':
|
||||
available_dryers += 1
|
||||
for machine in data['washers']:
|
||||
if machine['state'] == 'DISPONIBLE':
|
||||
available_washers += 1
|
||||
return available_dryers, available_washers
|
||||
|
||||
|
||||
def get_today_menu():
|
||||
"""
|
||||
Check if the menu for the current day is available
|
||||
|
||||
:return: a list containing today's menu
|
||||
"""
|
||||
with open(MENU_FILE) as f:
|
||||
data = json.load(f)
|
||||
menu = []
|
||||
for i in range(0, len(data)):
|
||||
if data[i]['date'] == date.today().strftime('%Y-%m-%d'):
|
||||
menu = data[i]['meal'][0]['foodcategory']
|
||||
break
|
||||
return menu
|
||||
|
||||
|
||||
def get_proximo_article_number():
|
||||
"""
|
||||
Get the number of articles on sale at proximo
|
||||
|
||||
:return: an integer representing the number of articles
|
||||
"""
|
||||
with urllib.request.urlopen(PROXIMO_URL) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
count = 0
|
||||
for article in data['articles']:
|
||||
if int(article['quantity']) > 0:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def get_tutorinsa_tutoring_number():
|
||||
"""
|
||||
Get the number of tutoring classes available
|
||||
|
||||
:return: an integer representing the number of tutoring classes
|
||||
"""
|
||||
try:
|
||||
with urllib.request.urlopen(TUTORINSA_URL) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
return int(data['tutorials_number'])
|
||||
except:
|
||||
return 0
|
||||
|
||||
|
||||
def get_today_events():
|
||||
"""
|
||||
Get today's events
|
||||
|
||||
:return: an array containing today's events
|
||||
"""
|
||||
with urllib.request.urlopen(PLANNING_URL) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
today_events = []
|
||||
for event in data:
|
||||
if event['date_begin'].split(' ')[0] == date.today().strftime('%Y-%m-%d'):
|
||||
today_events.append(event)
|
||||
return today_events
|
||||
|
||||
|
||||
def get_news_feed():
|
||||
"""
|
||||
Get facebook news and truncate the data to 15 entries for faster loading
|
||||
|
||||
:return: an object containing the facebook feed data
|
||||
"""
|
||||
# Prevent concurrent access to file
|
||||
while os.path.isfile(FACEBOOK_LOCK):
|
||||
print("Waiting for lock")
|
||||
with open(FACEBOOK_FILE) as f:
|
||||
data = json.load(f)
|
||||
if 'data' in data and len(data['data']) > 15:
|
||||
del data['data'][14:]
|
||||
else:
|
||||
data = {'data': []}
|
||||
return data
|
||||
|
||||
|
||||
def generate_dashboard_json():
|
||||
"""
|
||||
Generate the actual dashboard
|
||||
|
||||
:return: an object containing the dashboard's data
|
||||
"""
|
||||
available_machines = get_available_machines()
|
||||
available_tutorials = get_tutorinsa_tutoring_number()
|
||||
return {
|
||||
'dashboard': {
|
||||
'today_events': get_today_events(),
|
||||
'available_machines': {
|
||||
'dryers': available_machines[0],
|
||||
'washers': available_machines[1]
|
||||
},
|
||||
'available_tutorials': available_tutorials,
|
||||
'today_menu': get_today_menu(),
|
||||
'proximo_articles': get_proximo_article_number()
|
||||
},
|
||||
'news_feed': get_news_feed()
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
with open(DASHBOARD_FILE, 'w') as f:
|
||||
json.dump(generate_dashboard_json(), f)
|
||||
|
||||
|
||||
main()
|
162931
dashboard/log
Normal file
162931
dashboard/log
Normal file
File diff suppressed because it is too large
Load diff
120
expo_notifications/dao.php
Normal file
120
expo_notifications/dao.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
10
expo_notifications/en.json
Normal file
10
expo_notifications/en.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
319
expo_notifications/exponent_server_sdk/__init__.py
Normal file
319
expo_notifications/exponent_server_sdk/__init__.py
Normal file
|
@ -0,0 +1,319 @@
|
|||
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)
|
10
expo_notifications/fr.json
Normal file
10
expo_notifications/fr.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
163
expo_notifications/handler.py
Normal file
163
expo_notifications/handler.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
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()
|
52
expo_notifications/save_token.php
Normal file
52
expo_notifications/save_token.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?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);
|
||||
}
|
1
facebook/facebook_data.json
Normal file
1
facebook/facebook_data.json
Normal file
File diff suppressed because one or more lines are too long
6
facebook/facebook_update.sh
Executable file
6
facebook/facebook_update.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#/bin/bash!
|
||||
|
||||
touch lock
|
||||
#curl "https://graph.facebook.com/v3.3/amicale.deseleves/posts?fields=message%2Cfull_picture%2Ccreated_time%2Cpermalink_url&&date_format=U&access_token=EAAGliUs4Ei8BAGwHmg7SNnosoEDMuDhP3i5lYOGrIGzZBNeMeGzGhpUigJt167cKXEIM0GiurSgaC0PS4Xg2GBzOVNiZCfr8u48VVB15a9YbOsuhjBqhHAMb2sz6ibwOuDhHSvwRZCUpBZCjmAW12e7RjWJp0jvyNoYYvIQbfaLWi3Nk2mBc" > facebook_data.json
|
||||
curl "https://graph.facebook.com/v6.0/amicale.deseleves/published_posts?fields=full_picture,message,permalink_url,created_time&date_format=U&access_token=EAAGliUs4Ei8BAGwHmg7SNnosoEDMuDhP3i5lYOGrIGzZBNeMeGzGhpUigJt167cKXEIM0GiurSgaC0PS4Xg2GBzOVNiZCfr8u48VVB15a9YbOsuhjBqhHAMb2sz6ibwOuDhHSvwRZCUpBZCjmAW12e7RjWJp0jvyNoYYvIQbfaLWi3Nk2mBc" > facebook_data.json
|
||||
rm lock
|
13
update_washinsa.sh
Executable file
13
update_washinsa.sh
Executable file
|
@ -0,0 +1,13 @@
|
|||
#/bin/bash!
|
||||
|
||||
cd $HOME/public_html/washinsa
|
||||
# update washing machine list
|
||||
php index.php
|
||||
|
||||
cd ../expo_notifications
|
||||
# watch for new notifications with the new list
|
||||
python3 handler.py
|
||||
|
||||
cd ../dashboard
|
||||
# Update the dashboard
|
||||
python3 handler.py > log 2> err
|
Loading…
Reference in a new issue