From 2c82286bfeff7c2ea4b40ac4b71bede2fd0fb27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abdulkadir=20Furkan=20=C5=9Eanl=C4=B1?= Date: Sun, 21 Jan 2024 10:38:05 +0100 Subject: [PATCH] Dockerize. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Abdulkadir Furkan Şanlı --- Dockerfile | 11 +++++++++++ compose.yml | 6 ++++++ example.env | 8 ++++---- main.py | 46 +++++++++++++++++++++++++++++++++------------- requirements.txt | 1 - 5 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 Dockerfile create mode 100644 compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1839caf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3 + +ENV PYTHONUNBUFFERED=1 + +WORKDIR /usr/src/app + +COPY main.py requirements.txt ./ + +RUN pip install --no-cache-dir -r requirements.txt + +CMD ["python", "./main.py"] diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..e29c745 --- /dev/null +++ b/compose.yml @@ -0,0 +1,6 @@ +services: + parkerbot: + build: . + env_file: .env + volumes: + - ./data:/data diff --git a/example.env b/example.env index cb6f60a..fe52d75 100644 --- a/example.env +++ b/example.env @@ -1,5 +1,5 @@ -# Path of sqlite3 file to use. -DB_PATH = "" +# Path for persistent app data. +DATA_DIR = "/data" # Matrix homeserver URL. MATRIX_SERVER = "" # Matrix room to monitor. @@ -9,6 +9,6 @@ MATRIX_USER = "" # Password for bot's Matrix user. MATRIX_PASSWORD = "" # Title of the playlists created, date of the week's Monday will be appended. -PLAYLIST_TITLE = "" +YOUTUBE_PLAYLIST_TITLE = "" # YouTube API client secret json path. -YOUTUBE_CLIENT_SECRETS_FILE = os.getenv("YOUTUBE_CLIENT_SECRETS_FILE") +YOUTUBE_CLIENT_SECRETS_FILE = "" diff --git a/main.py b/main.py index f35e39d..b2cff87 100755 --- a/main.py +++ b/main.py @@ -5,22 +5,27 @@ import asyncio import os import pickle import re +import signal import sqlite3 +import sys from datetime import datetime, timedelta -from dotenv import load_dotenv from google.auth.transport.requests import Request from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build -from nio import AsyncClient, ClientConfig, RoomMessageText, SyncResponse +from nio import AsyncClient, RoomMessageText, SyncResponse + +DATA_DIR = os.getenv("DATA_DIR", "./") +DB_PATH = os.path.join(DATA_DIR, "parkerbot.sqlite3") +TOKEN_PATH = os.path.join(DATA_DIR, "sync_token") +PICKLE_PATH = os.path.join(DATA_DIR, "token.pickle") -load_dotenv() -DB_PATH = os.getenv("DB_PATH") MATRIX_SERVER = os.getenv("MATRIX_SERVER") MATRIX_ROOM = os.getenv("MATRIX_ROOM") MATRIX_USER = os.getenv("MATRIX_USER") MATRIX_PASSWORD = os.getenv("MATRIX_PASSWORD") -PLAYLIST_TITLE = os.getenv("PLAYLIST_TITLE") + +YOUTUBE_PLAYLIST_TITLE = os.getenv("YOUTUBE_PLAYLIST_TITLE") YOUTUBE_CLIENT_SECRETS_FILE = os.getenv("YOUTUBE_CLIENT_SECRETS_FILE") YOUTUBE_API_SERVICE_NAME = "youtube" YOUTUBE_API_VERSION = "v3" @@ -61,9 +66,9 @@ def define_tables(): def get_authenticated_service(): """Get an authentivated YouTube service.""" credentials = None - # The file token.pickle stores the user's access and refresh tokens. - if os.path.exists("token.pickle"): - with open("token.pickle", "rb") as token: + # Stores the user's access and refresh tokens. + if os.path.exists(PICKLE_PATH): + with open(PICKLE_PATH, "rb") as token: credentials = pickle.load(token) # If there are no valid credentials available, let the user log in. @@ -77,7 +82,7 @@ def get_authenticated_service(): ) credentials = flow.run_local_server(port=8080) # Save the credentials for the next run - with open("token.pickle", "wb") as token: + with open(PICKLE_PATH, "wb") as token: pickle.dump(credentials, token) return build("youtube", "v3", credentials=credentials) @@ -111,7 +116,7 @@ def make_playlist(youtube, title): def get_or_make_playlist(youtube, monday_date): """Get ID of playlist for given Monday's week, make if doesn't exist.""" - playlist_title = f"{PLAYLIST_TITLE} {monday_date.strftime('%Y-%m-%d')}" + playlist_title = f"{YOUTUBE_PLAYLIST_TITLE} {monday_date.strftime('%Y-%m-%d')}" # Check if playlist exists in the database cursor.execute( @@ -216,20 +221,23 @@ async def message_callback(client, room, event): async def sync_callback(response): + """Save Matrix sync token.""" # Save the sync token to a file or handle it as needed - with open("sync_token", "w") as f: + with open(TOKEN_PATH, "w") as f: f.write(response.next_batch) def load_sync_token(): + """Get an existing Matrix sync token if it exists.""" try: - with open("sync_token", "r") as file: + with open(TOKEN_PATH, "r") as file: return file.read().strip() except FileNotFoundError: return None async def get_client(): + """Returns configured and logged in Matrix client.""" client = AsyncClient(MATRIX_SERVER, MATRIX_USER) client.add_event_callback( lambda room, event: message_callback(client, room, event), RoomMessageText @@ -240,12 +248,24 @@ async def get_client(): return client +def sigterm_handler(signum, frame): + """Gracefully stop syncing on SIGTERM.""" + asyncio.get_event_loop().stop() + + async def main(): """Get DB and Matrix client ready, and start syncing.""" define_tables() client = await get_client() + signal.signal(signal.SIGTERM, sigterm_handler) sync_token = load_sync_token() - await client.sync_forever(30000, full_state=True, since=sync_token) + try: + await client.sync_forever(30000, full_state=True, since=sync_token) + finally: + conn.close() + await client.logout() + sys.exit() + if __name__ == "__main__": asyncio.run(main()) diff --git a/requirements.txt b/requirements.txt index 3cd049c..2b80b5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ matrix-nio google-auth-oauthlib google-api-python-client -python-dotenv