Skip to content

Commit

Permalink
Merge pull request #2 from tbmc/feature_add_response_links
Browse files Browse the repository at this point in the history
feat: add links in event description
  • Loading branch information
tbmc authored May 23, 2024
2 parents aaf124a + 0801908 commit 58db94a
Show file tree
Hide file tree
Showing 45 changed files with 761 additions and 108 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,4 @@ data_*.txt

test/
docs/
database.db
16 changes: 0 additions & 16 deletions .github/workflows/build_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,6 @@ jobs:
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Black check
run: black --check .

- name: Run Pytest
run: pytest

- name: Login to Docker Hub
uses: docker/login-action@v3
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: test

on:
push:
branches:
- "main"
paths-ignore:
- 'docs'
- '**.md'
pull_request:
branches:
- "main"

jobs:
test_and_lint:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Black check
run: black --check .

- name: Run Mypy
run: mypy .

- name: Run Pytest
run: pytest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,5 @@ cython_debug/
*.ics

data_*.txt
database.db

11 changes: 8 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Github Container Pulls](https://img.shields.io/badge/Github%20Container%20Pulls-960-blue
[![Github Container Pulls](https://img.shields.io/badge/Github%20Container%20Pulls-1K-blue
)](https://github.com/tbmc/sporteasy-calendar-connector/pkgs/container/sporteasy-calendar-connector)
[![Docker Pulls](https://img.shields.io/docker/pulls/tbmc/sporteasy-calendar-connector)](https://hub.docker.com/r/tbmc/sporteasy-calendar-connector)

Expand Down Expand Up @@ -71,10 +71,15 @@ You can use mine, but at your own risk.
:warning: Data in base64 are not ciphered.

``
https://sporteasy-calendar-connector.tbmc.ovh?data={base64Data}
https://sporteasy-calendar-connector.tbmc.ovh/api?data={base64Data}
``

You can add a parameter `disable_save_login` to disable saving of logins and password, but it deactivates links in event description to set present or absent. Without saving logins, it can not connect to SportEasy servers.

``
https://sporteasy-calendar-connector.tbmc.ovh/api?data={base64Data}&disable_save_login=True
``

## Info

SportEasy block IPs from server providers, so you should have a domestic IP.

32 changes: 31 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@

import flask
from flask import send_from_directory
from werkzeug.exceptions import BadRequestKeyError

from calendar_connector.calendar_converter import CalendarConverter
from calendar_connector.consts import route_change_presence, PRESENCE
from calendar_connector.data_decoder import decode_data
from calendar_connector.sporteasy_connector import SporteasyConnector
from calendar_connector.database.user import generate_links_data
from calendar_connector.custom_exceptions import BadTokenException
from calendar_connector.presence_updater import set_presence_to_event

logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
Expand All @@ -33,8 +39,12 @@ def request_handler() -> flask.Response:
ip = flask.request.remote_addr
logging.info(f"New incoming request from {ip=} and {username=}")

url_root = flask.request.url_root
disable_save_login = flask.request.args.get("disable_save_login") is not None
calendar_converter = CalendarConverter()
calendar_text = calendar_converter.get_calendar_text(username, password, team_id)
calendar_text = calendar_converter.get_calendar_text(
username, password, not disable_save_login, url_root, team_id
)

return flask.Response(
calendar_text,
Expand All @@ -43,6 +53,26 @@ def request_handler() -> flask.Response:
)


@app.route(route_change_presence)
def change_my_presence() -> flask.Response:
try:
team_id = flask.request.args["team_id"]
event_id = flask.request.args["event_id"]
user_id = flask.request.args["user_id"]
token = flask.request.args["token"]
presence = flask.request.args["presence"].lower() == PRESENCE.present
except BadRequestKeyError as e:
return flask.Response("Parameter missing", status=500)

hash_token = generate_links_data(team_id, event_id, user_id, presence)
if token != hash_token:
raise BadTokenException()

set_presence_to_event(int(team_id), int(event_id), int(user_id), presence)

return flask.send_file("calendar_connector/html/auto_close.html")


@app.route("/api/list-teams")
def list_teams() -> flask.Response:
if flask.request.method == "OPTIONS":
Expand Down
67 changes: 60 additions & 7 deletions calendar_connector/calendar_converter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from typing import Optional, cast

from icalendar import Calendar, vText

from calendar_connector.database.user import save_user
from calendar_connector.datetime_utils import (
get_current_datetime,
get_formated_current_time,
)
from calendar_connector.env import load_env_data
from calendar_connector.event_convertor import event_to_calendar_event
from calendar_connector.event_utils.description import GenerateLinksData
from calendar_connector.sporteasy_connector import SporteasyConnector


Expand All @@ -13,9 +17,50 @@ def __init__(self) -> None:
self.connector = SporteasyConnector()

def get_calendar_text(
self, username: str, password: str, team_id: str | None = None
self,
username: str,
password: str,
save_login: bool,
url_root: str,
team_id: Optional[str] = None,
) -> str:
"""
Retrieves the calendar text from SportEasy for the specified team.
Args:
username (str): The username for the SportEasy account.
password (str): The password for the SportEasy account.
save_login (bool): If True, save the login credentials.
url_root (str): The base URL for the SportEasy API.
team_id (Optional[str], optional): The ID of the team to retrieve events for. Defaults to None.
Returns:
str: The calendar text in iCalendar format.
Raises:
Exception: If an error occurs while retrieving the calendar text.
Example:
```python
calendar_converter = CalendarConverter()
calendar_content = calendar_converter.get_calendar_text(
"example_username", "example_password", False, "http://localhost:5000/", "12345"
)
```
"""
self.connector.login(username, password)

links_data: Optional[GenerateLinksData] = None
if save_login:
user = save_user(username, password)
links_data = GenerateLinksData(
cast(int, user.id),
cast(str, username),
cast(str, password),
cast(str, user.salt),
url_root,
)

teams = self.connector.list_teams()

cal = Calendar()
Expand All @@ -28,12 +73,14 @@ def get_calendar_text(
cal.add("x-wr-calname", "SportEasy Calendar")
cal.add("x-wr-timezone", "Europe/Paris")

formatted_time = get_current_datetime().strftime("%Y-%m-%d %H:%M:%S")
cal.add("x-wr-caldesc", f"SportEasy Calendar | Last sync: {formatted_time}")
cal.add(
"x-wr-caldesc",
f"SportEasy Calendar | Last sync: {get_formated_current_time()}",
)
cal.add("REFRESH-INTERVAL;VALUE=DURATION", "PT8H")
cal.add("X-PUBLISHED-TTL", "PT8H")

for current_team_id, team_name in teams:
for current_team_id, team_name, team_url in teams:
# Ignore other teams
if (
team_id is not None
Expand All @@ -43,7 +90,11 @@ def get_calendar_text(
continue
events = self.connector.list_events(current_team_id)
for event in events:
cal.add_component(event_to_calendar_event(team_name, event))
cal.add_component(
event_to_calendar_event(
current_team_id, team_name, event, links_data, team_url
)
)

text_calendar: str = cal.to_ical().decode("utf-8").strip()

Expand All @@ -54,7 +105,9 @@ def main() -> None:
username, password, team_id = load_env_data()

calendar_converter = CalendarConverter()
calendar_content = calendar_converter.get_calendar_text(username, password, team_id)
calendar_content = calendar_converter.get_calendar_text(
username, password, False, "http://localhost:5000/", team_id
)
with open("./test.ics", "w", encoding="utf-8") as f:
f.write(calendar_content)

Expand Down
22 changes: 18 additions & 4 deletions calendar_connector/consts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
from typing import Any
from collections import namedtuple
import pytz

url_authenticate = "https://api.sporteasy.net/v2.1/account/authenticate/"
url_list_teams = "https://api.sporteasy.net/v2.1/me/teams/"
url_list_seasons = "https://api.sporteasy.net/v2.1/teams/{team_id}/seasons/"
url_list_events = "https://api.sporteasy.net/v2.1/teams/{team_id}/events/"
url_base = "https://api.sporteasy.net/v2.1"
url_authenticate = f"{url_base}/account/authenticate/"
url_csrf = f"{url_base}/account/csrf/"
url_me = f"{url_base}/me/"
url_list_teams = f"{url_base}/me/teams/"

url_team_id_base = url_base + "/teams/{team_id}"
url_list_seasons = f"{url_team_id_base}/seasons/"
url_list_events = f"{url_team_id_base}/events/"

url_event_base = url_team_id_base + "/events/{event_id}"
url_put_event_presence = url_event_base + "/profiles/{profile_id}/"

route_change_presence = "/api/change_my_presence"

PLAYED_WORDS = "played", "present", "available"
EVENT_TYPE = dict[str, int | str | Any | list[Any] | dict[str, Any]]
Expand All @@ -29,3 +40,6 @@
"unavailable": "CANCELLED",
"not_played": "CANCELLED",
}

_presence_type = namedtuple("_presence_type", ["present", "absent"])
PRESENCE = _presence_type("yes", "no")
31 changes: 31 additions & 0 deletions calendar_connector/cryptography.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import string
import random
import hashlib

from calendar_connector.consts import PRESENCE

_alphabet = string.printable


def generate_salt() -> str:
chars: list[str] = []
for i in range(random.randint(30, 50)):
chars.append(random.choice(_alphabet))
return "".join(chars)


def generate_hash(
team_id: int | str,
event_id: int | str,
user_id: int | str,
username: str,
password: str,
salt: str,
presence: bool,
) -> str:
presence_str = PRESENCE.present if presence else PRESENCE.absent
to_hash = (
f"{team_id}:{event_id}:{user_id}:{username}:{password}:{salt}:{presence_str}"
)
m = hashlib.sha3_256(to_hash.encode("utf-8"))
return m.hexdigest()
19 changes: 19 additions & 0 deletions calendar_connector/custom_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Optional


class AttributeNotFoundException(Exception):
def __init__(self, name: str) -> None:
super().__init__(f"{name} is not found")


class BadTokenException(Exception):
def __init__(self) -> None:
super().__init__(f"Your token is not valid")


class TooManyUsersException(Exception):
def __init__(self, mail: str, n: Optional[int] = None) -> None:
super().__init__(
f'There are too many users with the same mail "{mail}", this should not happen.'
+ ("" if n is None else f"Number of users {n}.")
)
Empty file.
3 changes: 3 additions & 0 deletions calendar_connector/database/all_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from calendar_connector.database.user import User

ALL_MODELS = [User]
10 changes: 10 additions & 0 deletions calendar_connector/database/base_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from peewee import Model

from calendar_connector.database.db_connector import get_db

db = get_db()


class BaseModel(Model):
class Meta:
database = db
12 changes: 12 additions & 0 deletions calendar_connector/database/create_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from peewee import SqliteDatabase

from calendar_connector.database.db_connector import get_db
from calendar_connector.database.all_models import ALL_MODELS


def create_db(db: SqliteDatabase) -> None:
db.create_tables(ALL_MODELS)


if __name__ == "__main__":
create_db(get_db())
Loading

0 comments on commit 58db94a

Please sign in to comment.