From 564df78a87accccc930c8bcf850f05b99f72670f Mon Sep 17 00:00:00 2001 From: adjoly Date: Tue, 10 Jun 2025 12:02:50 +0200 Subject: [PATCH] =?UTF-8?q?=E3=80=8C=F0=9F=8E=89=E3=80=8D=20init:=20workin?= =?UTF-8?q?g=20shitty=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .envrc | 1 + .gitignore | 3 ++ 24h.ics | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.lock | 44 +++++++++++++++ flake.nix | 58 ++++++++++++++++++++ rpc.py | 93 ++++++++++++++++++++++++++++++++ 6 files changed, 355 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 24h.ics create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 rpc.py diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a20f371 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.direnv + +.env diff --git a/24h.ics b/24h.ics new file mode 100644 index 0000000..3f801f6 --- /dev/null +++ b/24h.ics @@ -0,0 +1,156 @@ +BEGIN:VCALENDAR +PRODID:-//Proton AG//ProtonCalendar 1.0.0//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:24h du Mans +X-WR-CALDESC: +X-WR-TIMEZONE:Europe/Paris +REFRESH-INTERVAL;VALUE=DURATION:PT240M +X-PUBLISHED-TTL:PT240M +BEGIN:VTIMEZONE +TZID:Europe/Paris +LAST-MODIFIED:20250218T131521Z +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:sabre-vobject-244d1309-86d5-42d5-8db3-e6e61f4bb67c +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - Race +DTSTART;TZID=Europe/Paris:20250614T160000 +DTEND;TZID=Europe/Paris:20250615T160000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-460cb9f4-cd83-4249-805c-212be3043944 +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - Qualifying - LMP2 & LMGT3 +DTSTART;TZID=Europe/Paris:20250611T184500 +DTEND;TZID=Europe/Paris:20250611T191500 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-c1f55045-3080-47aa-bf94-abf5d15a1ec0 +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - Warm-up +DTSTART;TZID=Europe/Paris:20250614T120000 +DTEND;TZID=Europe/Paris:20250614T121500 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-80f4f074-58c0-456f-ba1b-589c0c6da159 +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - Free Practice 1 +DTSTART;TZID=Europe/Paris:20250611T140000 +DTEND;TZID=Europe/Paris:20250611T170000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-428f7889-c774-481f-b75c-b7db74799381 +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - Free Practice 4 +DTSTART;TZID=Europe/Paris:20250612T230000 +DTEND;TZID=Europe/Paris:20250613T000000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-fe0d6ec8-5491-4fed-8db4-be1070b796fe +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - HYPERPOLE 2 - HYPERCAR +DTSTART;TZID=Europe/Paris:20250612T214000 +DTEND;TZID=Europe/Paris:20250612T215500 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-78a2d420-cf27-4527-911e-f8ac04204f23 +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - HYPERPOLE 1 - HYPERCAR +DTSTART;TZID=Europe/Paris:20250612T210500 +DTEND;TZID=Europe/Paris:20250612T212500 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-9a9dbd76-adb3-42e3-992c-2d0ef04dc909 +DTSTAMP:20250609T141021Z +SUMMARY:24 Hours of Le Mans 2025 - HYPERPOLE 2 - LMP2 & LMGT3 +DTSTART;TZID=Europe/Paris:20250612T203500 +DTEND;TZID=Europe/Paris:20250612T205000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-f07a2dbe-8958-4e6d-948e-05c47cd71890 +DTSTAMP:20250609T141022Z +SUMMARY:24 Hours of Le Mans 2025 - HYPERPOLE 1 - LMP2 & LMGT3 +DTSTART;TZID=Europe/Paris:20250612T200000 +DTEND;TZID=Europe/Paris:20250612T202000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-a7ec4975-04b1-44c8-8065-743e3cf6e7cb +DTSTAMP:20250609T141022Z +SUMMARY:24 Hours of Le Mans 2025 - Free Practice 3 +DTSTART;TZID=Europe/Paris:20250612T144500 +DTEND;TZID=Europe/Paris:20250612T174500 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-fce906a9-4434-4adc-bcd6-0bb7669e0bb1 +DTSTAMP:20250609T141022Z +SUMMARY:24 Hours of Le Mans 2025 - Free Practice 2 +DTSTART;TZID=Europe/Paris:20250611T220000 +DTEND;TZID=Europe/Paris:20250612T000000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:sabre-vobject-8c32bb13-bb2e-4f29-9706-e4d03d8149d1 +DTSTAMP:20250609T141022Z +SUMMARY:24 Hours of Le Mans 2025 - Qualifying - Hypercar +DTSTART;TZID=Europe/Paris:20250611T193000 +DTEND;TZID=Europe/Paris:20250611T200000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:AEf2LX8fa3lqe8dKTsDjPwmhvBip@proton.me +DTSTAMP:20250609T141022Z +SUMMARY:24 Hours of Le Mans 2025 - Eurosport 2 +DTSTART;TZID=Europe/Paris:20250610T193000 +DTEND;TZID=Europe/Paris:20250610T210000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +BEGIN:VEVENT +UID:x14Vl7X2L6pSQBH2mt1IRuy-Vjjx@proton.me +DTSTAMP:20250610T084743Z +SUMMARY:test +DTSTART;TZID=Europe/Paris:20250610T100000 +DTEND;TZID=Europe/Paris:20250610T120000 +SEQUENCE:0 +STATUS:CONFIRMED +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..fc1e3fe --- /dev/null +++ b/flake.lock @@ -0,0 +1,44 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1749285348, + "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pypresence": { + "flake": false, + "locked": { + "lastModified": 1743073125, + "narHash": "sha256-DjwDmQMbI9tV40TTe1CthhphoysKSFICrRhqijJjIAE=", + "owner": "qwertyquerty", + "repo": "pypresence", + "rev": "4e882c36d0f800c016c15977243ac9a49177095a", + "type": "github" + }, + "original": { + "owner": "qwertyquerty", + "repo": "pypresence", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "pypresence": "pypresence" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ea64428 --- /dev/null +++ b/flake.nix @@ -0,0 +1,58 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + pypresence.url = "github:qwertyquerty/pypresence"; + pypresence.flake = false; + }; + + outputs = + inputs@{ self, nixpkgs, ... }: + let + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forEachSupportedSystem = + f: + nixpkgs.lib.genAttrs supportedSystems ( + system: + f { + pkgs = import nixpkgs { inherit system; }; + } + ); + in + { + packages = forEachSupportedSystem ( + { pkgs }: + { + default = pkgs.python313Packages.buildPythonPackage { + pname = "pypresence"; + version = "4.4.0"; # Replace with the actual version or a placeholder + format = "setuptools"; + src = inputs.pypresence; + doCheck = false; # tests require internet connection + pythonImportsCheck = [ "pypresence" ]; + }; + } + ); + devShells = forEachSupportedSystem ( + { pkgs }: + { + default = pkgs.mkShell { + packages = with pkgs; [ + nixd + nixfmt-rfc-style + python313 + self.packages.${pkgs.system}.default + python313Packages.icalendar + python313Packages.pytz + python313Packages.python-dotenv + python313Packages.requests + ]; + }; + } + ); + }; +} diff --git a/rpc.py b/rpc.py new file mode 100644 index 0000000..2014353 --- /dev/null +++ b/rpc.py @@ -0,0 +1,93 @@ +from pypresence import Presence, ActivityType +import os +from dotenv import load_dotenv +import icalendar +import pytz +import requests +from datetime import datetime, timezone +import time + +load_dotenv() + +# Initialize PyPresence with your application ID +client_id = os.getenv('DISCORD_APPLICATION_ID') +ics_url = os.getenv('ICS_URL') + +if not client_id or not ics_url: + raise ValueError("issue in .env file") + +RPC = Presence(client_id) + +try: + RPC.connect() +except Exception as e: + print(f"Failed to connect to Discord: {e}") + exit(1) + +def fetch_ics(url): + response = requests.get(url) + if response.status_code == 200: + return response.content + else: + raise Exception("Failed to fetch the .ics file") + +def parse_ics(ics_content): + cal = icalendar.Calendar.from_ical(ics_content) + events = [] + for component in cal.walk(): + if component.name == 'VEVENT': + summary = component.get('summary') + dtstart = component.get('dtstart').dt + dtend = component.get('dtend').dt + + # Convert datetime to timezone-aware if necessary + if isinstance(dtstart, datetime) and dtstart.tzinfo is None: + dtstart = pytz.utc.localize(dtstart) + if isinstance(dtend, datetime) and dtend.tzinfo is None: + dtend = pytz.utc.localize(dtend) + + events.append({ + 'summary': str(summary), + 'start': dtstart, + 'end': dtend + }) + return events + +def update_presence(event): + now = datetime.now(timezone.utc) + if event['start'] <= now <= event['end']: + parts = event['summary'].split(' - ') + if len(parts) == 2: + state_part, details_part = parts + else: + state_part = event['summary'] + details_part = 'Event' + + RPC.update( + activity_type=ActivityType.WATCHING, + details=details_part, + state=state_part, + large_image="favicon", # Replace with your image key + large_text="Bangers incomming", + start=int(event['start'].timestamp()), + end=int(event['end'].timestamp()) + ) + else: + RPC.clear() + +print("Rich Presence is running. Press Ctrl+C to stop.") + +try: + while True: + try: + ics_content = fetch_ics(ics_url) + events = parse_ics(ics_content) + for event in events: + update_presence(event) + except Exception as e: + print(f"An error occurred: {e}") + time.sleep(60) # Update every minute +except KeyboardInterrupt: + print("Stopping Rich Presence...") +finally: + RPC.close()