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