Compare commits
No commits in common. "master" and "master" have entirely different histories.
5 changed files with 45 additions and 214 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
||||||
/facebook/token
|
/facebook/token
|
||||||
/washinsa/washinsa_data.json
|
/washinsa/washinsa_data.json
|
||||||
/washinsa/tripode_b_data.json
|
|
||||||
/facebook/facebook_data.json
|
/facebook/facebook_data.json
|
||||||
/dashboard/dashboard_data.json
|
/dashboard/dashboard_data.json
|
||||||
/menu/menu_data.json
|
/menu/menu_data.json
|
||||||
|
|
89
README.md
89
README.md
|
@ -1,88 +1,3 @@
|
||||||
# Serveur pour l'application de l'Amicale (Campus)
|
# Serveur de l'application de l'Amicale
|
||||||
|
|
||||||
Partie serveur pour [l'application de l'amicale](https://git.etud.insa-toulouse.fr/vergnet/application-amicale), publiée sous licence GPLv3.
|
Partie serveur de l'application pour l'amicale, publiée sous licence GPLv3.
|
||||||
|
|
||||||
Le serveur est programmé avec python 3.6 en utilisant des [venv](https://docs.python.org/3/tutorial/venv.html).
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
Pour des raisons de compatibilité, 2 versions sont en ligne sur le serveur: une dans `publich_html` et une autre dans `public_html/v2`. La première version est à ignorer et à supprimer dans le futur. La v2 est celle actuellement utilisée.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Tout d'abord, clonez ce dépot dans le dossier désiré et déplacez vous dedans.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://git.etud.insa-toulouse.fr/vergnet/application-amicale-serveur.git
|
|
||||||
cd application-amicale-serveur
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensuite, créez le venv:
|
|
||||||
```shell
|
|
||||||
python3 -m venv tutorial-env
|
|
||||||
```
|
|
||||||
|
|
||||||
Et enfin, installez les dépendances:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mettre à jour les dépendances
|
|
||||||
|
|
||||||
Ouvrez le fichier `requirements.txt` et écrivez la nouvelle version de la librairie à utiliser.
|
|
||||||
Ensuite, chargez le venv dans votre terminal:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
source .venv/bin/activate
|
|
||||||
```
|
|
||||||
|
|
||||||
Cette commande permet d'utiliser le python installé dans le venv au lieu de celui du système.
|
|
||||||
Il ne reste plus qu'à installer les nouvelles versions référencées dans `requirements.txt`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Envoyer les mises à jour sur le serveur
|
|
||||||
|
|
||||||
Le serveur est synchronisé avec git, il suffit donc de se connecter sur l'espace web, de se déplacer dans le dossier v2 et de récupérer les derniers changements:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
ssh amicale_app@etud.insa-toulouse.fr
|
|
||||||
cd public_html/v2
|
|
||||||
git pull
|
|
||||||
```
|
|
||||||
|
|
||||||
Si vous avez modifié les versions des librairies dans `requirements.txt`, pensez à les mettre à jour sur le serveur avec la commande suivante:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mises à jour 'BREAKING'
|
|
||||||
|
|
||||||
Si une mise à jour casse la compatibilité avec la version actuelle de l'application, il est nécessaire de garder l'ancienne version du logiciel serveur le temps que tout le monde mette l'application à jour (plusieurs mois).
|
|
||||||
|
|
||||||
Pour cela, créez un nouveau dossier pour la nouvelle version dans `public_html`. Par exemple, pour passer de la version 2 (installée dans `public_html/v2`), il faut installer la nouvelle version dans le dossier `public_html/v3`.
|
|
||||||
|
|
||||||
Pour cela, il faut tout réinstaller dans ce dossier comme suit:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
ssh amicale_app@etud.insa-toulouse.fr
|
|
||||||
cd public_html
|
|
||||||
git clone https://git.etud.insa-toulouse.fr/vergnet/application-amicale-serveur.git v<NUMERO_DE_VERSION>
|
|
||||||
cd v<NUMERO_DE_VERSION>
|
|
||||||
```
|
|
||||||
Ensuite, créez le venv:
|
|
||||||
```shell
|
|
||||||
python3 -m venv tutorial-env
|
|
||||||
```
|
|
||||||
|
|
||||||
Et enfin, installez les dépendances:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Pensez ensuite à rediriger l'application vers cette nouvelle version.
|
|
|
@ -1,21 +1,20 @@
|
||||||
import json
|
import json
|
||||||
import facebook_scraper
|
import facebook_scraper
|
||||||
|
import enum
|
||||||
|
|
||||||
FILE = 'facebook_data.json'
|
FILE = 'facebook_data.json'
|
||||||
|
|
||||||
PAGES = ["amicale.deseleves", "campus.insat"]
|
PAGES = ["amicale.deseleves", "campus.insat"]
|
||||||
|
|
||||||
|
|
||||||
def scrape_data(page):
|
def scrape_data(page):
|
||||||
post_list = []
|
post_list = []
|
||||||
for post in facebook_scraper.get_posts(page, pages=4):
|
for post in facebook_scraper.get_posts(page, pages=3):
|
||||||
print(post)
|
print(post)
|
||||||
cleaned_post = {
|
cleaned_post = {
|
||||||
"id": post["post_id"],
|
"id": post["post_id"],
|
||||||
"message": post["post_text"],
|
"message": post["post_text"],
|
||||||
"url": post["post_url"],
|
"url": post["post_url"],
|
||||||
"image": post["image"],
|
"image": post["image"],
|
||||||
"images": post["images"],
|
|
||||||
"video": post["video"],
|
"video": post["video"],
|
||||||
"link": post["link"],
|
"link": post["link"],
|
||||||
"time": post["time"].timestamp(),
|
"time": post["time"].timestamp(),
|
||||||
|
@ -28,7 +27,6 @@ def scrape_data(page):
|
||||||
def get_all_data():
|
def get_all_data():
|
||||||
data = {}
|
data = {}
|
||||||
for page in PAGES:
|
for page in PAGES:
|
||||||
print(" -> " + page)
|
|
||||||
data[page] = scrape_data(page)
|
data[page] = scrape_data(page)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ bs4==0.0.1
|
||||||
certifi==2020.6.20
|
certifi==2020.6.20
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
cssselect==1.1.0
|
cssselect==1.1.0
|
||||||
facebook-scraper==0.2.34
|
facebook-scraper==0.2.9
|
||||||
fake-useragent==0.1.11
|
fake-useragent==0.1.11
|
||||||
html2text==2020.1.16
|
html2text==2020.1.16
|
||||||
idna==2.10
|
idna==2.10
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
# Parser made with BeautifulSoup4
|
# Parser made with BeautifulSoup4
|
||||||
# https://www.crummy.com/software/BeautifulSoup/bs4/doc
|
# https://www.crummy.com/software/BeautifulSoup/bs4/doc
|
||||||
from json import JSONDecodeError
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from typing.io import TextIO
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
PAGE STRUCTURE
|
PAGE STRUCTURE
|
||||||
as of july 2021
|
as of june 2020
|
||||||
|
|
||||||
A table with a row (tr html tag) for each machine
|
A table with a row (tr html tag) for each machine
|
||||||
Each machine row is composed of 6 columns
|
Each machine row is composed of 6 columns
|
||||||
|
@ -23,20 +19,11 @@ Each machine row is composed of 6 columns
|
||||||
- 4 - Program (Name of the program or empty)
|
- 4 - Program (Name of the program or empty)
|
||||||
- 5 - Start time (The start time in format HH:MM or empty)
|
- 5 - Start time (The start time in format HH:MM or empty)
|
||||||
- 6 - End time (The end time in format HH:MM or empty)
|
- 6 - End time (The end time in format HH:MM or empty)
|
||||||
|
|
||||||
Custom message (errors displayed on the website)
|
|
||||||
Must use the non-raw url to see it.
|
|
||||||
In the <font> under the <div> of id msg-permanent
|
|
||||||
example message: Perturbations operateur, laverie non connectee a internet depuis le 12/07/2021 a 19h45
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
DUMP_FILE_INSA = "washinsa_data.json"
|
DUMP_FILE = "washinsa_data.json"
|
||||||
DUMP_FILE_TRIPODE_B = "tripode_b_data.json"
|
WASHINSA_URL = "https://www.proxiwash.com/weblaverie/component/weblaverie/?view=instancesfiche&format=raw&s=cf4f39"
|
||||||
WASHINSA_RAW_URL = "https://www.proxiwash.com/weblaverie/component/weblaverie/?view=instancesfiche&format=raw&s="
|
|
||||||
WASHINSA_URL = "https://www.proxiwash.com/weblaverie/ma-laverie-2?s="
|
|
||||||
DRYER_STRING = "SECHE LINGE"
|
DRYER_STRING = "SECHE LINGE"
|
||||||
# 10 min
|
|
||||||
CUSTOM_MESSAGE_INTERVAL = 10 * 60 * 1000
|
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
class State(Enum):
|
||||||
|
@ -61,64 +48,19 @@ STATE_CONVERSION_TABLE = {
|
||||||
TIME_RE = re.compile("^\d\d:\d\d$")
|
TIME_RE = re.compile("^\d\d:\d\d$")
|
||||||
|
|
||||||
|
|
||||||
def get_json(code: str, file: TextIO):
|
def download_page():
|
||||||
file_json = {
|
|
||||||
"info": {},
|
|
||||||
"dryers": [],
|
|
||||||
"washers": []
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
file_json = json.load(file)
|
|
||||||
except JSONDecodeError as e:
|
|
||||||
print("Error reading file " + file.name)
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
if not ("info" in file_json):
|
|
||||||
file_json["info"] = {}
|
|
||||||
|
|
||||||
info = file_json["info"]
|
|
||||||
if not ("last_checked" in info) or info[
|
|
||||||
"last_checked"] < datetime.now().timestamp() * 1000 - CUSTOM_MESSAGE_INTERVAL:
|
|
||||||
print("Updating proxiwash message")
|
|
||||||
info["message"] = get_message(code)
|
|
||||||
info["last_checked"] = datetime.now().timestamp() * 1000
|
|
||||||
parsed_data = get_machines(code)
|
|
||||||
file_json["dryers"] = parsed_data["dryers"]
|
|
||||||
file_json["washers"] = parsed_data["washers"]
|
|
||||||
return file_json
|
|
||||||
|
|
||||||
|
|
||||||
def get_machines(code: str):
|
|
||||||
soup = BeautifulSoup(download_page(code), 'html.parser')
|
|
||||||
rows = get_rows(soup)
|
|
||||||
return get_parsed_data(rows)
|
|
||||||
|
|
||||||
|
|
||||||
def get_message(code: str):
|
|
||||||
soup = BeautifulSoup(download_page(code, False), 'html.parser')
|
|
||||||
msg = soup.find(id="msg-permanent")
|
|
||||||
if msg:
|
|
||||||
return soup.find(id="msg-permanent").font.string
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def download_page(code: str, raw=True):
|
|
||||||
"""
|
"""
|
||||||
Downloads the page from proxiwash website
|
Downloads the page from proxiwash website
|
||||||
"""
|
"""
|
||||||
url = WASHINSA_RAW_URL + code
|
|
||||||
if not raw:
|
|
||||||
url = WASHINSA_URL + code
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(url) as response:
|
with urllib.request.urlopen(WASHINSA_URL) as response:
|
||||||
return response.read().decode()
|
return response.read().decode()
|
||||||
except:
|
except:
|
||||||
print("Error processing following url: " + url)
|
print("Error processing following url: " + WASHINSA_URL)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def get_rows(soup: BeautifulSoup):
|
def get_rows(soup):
|
||||||
"""
|
"""
|
||||||
Gets rows corresponding to machines on the page
|
Gets rows corresponding to machines on the page
|
||||||
"""
|
"""
|
||||||
|
@ -136,13 +78,6 @@ def is_machine_dryer(row):
|
||||||
return DRYER_STRING in row.contents[0].text
|
return DRYER_STRING in row.contents[0].text
|
||||||
|
|
||||||
|
|
||||||
def get_machine_weight(row):
|
|
||||||
"""
|
|
||||||
Find the maximum weight supported by the machine.
|
|
||||||
"""
|
|
||||||
return int(re.search("LINGE (.*?) KG", row.contents[0].text).group(1))
|
|
||||||
|
|
||||||
|
|
||||||
def get_machine_number(row):
|
def get_machine_number(row):
|
||||||
"""
|
"""
|
||||||
Gets the current machine number.
|
Gets the current machine number.
|
||||||
|
@ -231,69 +166,53 @@ def get_machine_remaining_time(row):
|
||||||
return time
|
return time
|
||||||
|
|
||||||
|
|
||||||
def is_machine_parsed(dryers, washers, number: int):
|
|
||||||
for m in dryers:
|
|
||||||
if m["number"] == number:
|
|
||||||
return True
|
|
||||||
for m in washers:
|
|
||||||
if m["number"] == number:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_parsed_data(rows):
|
def get_parsed_data(rows):
|
||||||
"""
|
"""
|
||||||
Gets the parsed data from the web page, formatting it in a easy to use object
|
Gets the parsed data from the web page, farmatting it in a easy to use object
|
||||||
"""
|
"""
|
||||||
dryers = []
|
dryers = []
|
||||||
washers = []
|
washers = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
machine_number = get_machine_number(row)
|
state = get_machine_state(row)
|
||||||
if not is_machine_parsed(dryers, washers, machine_number):
|
machine = {
|
||||||
state = get_machine_state(row)
|
"number": get_machine_number(row),
|
||||||
machine = {
|
"state": state.value,
|
||||||
"number": machine_number,
|
"startTime": "",
|
||||||
"state": state.value,
|
"endTime": "",
|
||||||
"maxWeight": get_machine_weight(row),
|
"donePercent": "",
|
||||||
"startTime": "",
|
"remainingTime": "",
|
||||||
"endTime": "",
|
"program": "",
|
||||||
"donePercent": "",
|
}
|
||||||
"remainingTime": "",
|
if state == State.RUNNING:
|
||||||
"program": "",
|
machine_times = get_machine_times(row)
|
||||||
}
|
machine["startTime"] = machine_times[0]
|
||||||
if state == State.RUNNING:
|
machine["endTime"] = machine_times[1]
|
||||||
machine_times = get_machine_times(row)
|
if len(machine_times[0]) == 0:
|
||||||
machine["startTime"] = machine_times[0]
|
state = State.RUNNING_NOT_STARTED
|
||||||
machine["endTime"] = machine_times[1]
|
machine["state"] = state.value
|
||||||
if len(machine_times[0]) == 0:
|
machine["program"] = get_machine_program(row)
|
||||||
state = State.RUNNING_NOT_STARTED
|
machine["donePercent"] = get_machine_done_percent(row)
|
||||||
machine["state"] = state.value
|
machine["remainingTime"] = get_machine_remaining_time(row)
|
||||||
machine["program"] = get_machine_program(row)
|
|
||||||
machine["donePercent"] = get_machine_done_percent(row)
|
if is_machine_dryer(row):
|
||||||
machine["remainingTime"] = get_machine_remaining_time(row)
|
dryers.append(machine)
|
||||||
|
else:
|
||||||
|
washers.append(machine)
|
||||||
|
|
||||||
if is_machine_dryer(row):
|
|
||||||
dryers.append(machine)
|
|
||||||
else:
|
|
||||||
washers.append(machine)
|
|
||||||
return {
|
return {
|
||||||
"dryers": dryers,
|
"dryers": dryers,
|
||||||
"washers": washers
|
"washers": washers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def write_json(data, f: TextIO):
|
|
||||||
f.seek(0)
|
|
||||||
f.truncate(0)
|
|
||||||
json.dump(data, f)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
dump_data = {}
|
soup = BeautifulSoup(download_page(), 'html.parser')
|
||||||
with open(DUMP_FILE_INSA, 'r+', encoding='utf-8') as f:
|
rows = get_rows(soup)
|
||||||
write_json(get_json("cf4f39", f), f)
|
with open(DUMP_FILE, 'w') as f:
|
||||||
with open(DUMP_FILE_TRIPODE_B, 'r+', encoding='utf-8') as f:
|
json.dump(get_parsed_data(rows), f)
|
||||||
write_json(get_json("b310b7", f), f)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue