switched to uv and gitea-actions-based pipeline
All checks were successful
Build container / Build (pull_request) Successful in 43s

This commit is contained in:
2025-07-29 14:28:07 +02:00
parent b12eb62b41
commit 29d951427d
18 changed files with 279 additions and 194 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use nix

View File

@@ -0,0 +1,32 @@
on:
pull_request:
branches:
- main
name: Build container
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/actions/checkout@v4
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: git.kluster.moll.re
username: ${{ gitea.repository_owner }}
password: ${{ secrets.PACKAGE_REGISTRY_ACCESS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
# use the current commit SHA as the tag
tags: git.kluster.moll.re/remoll/journal-bot:${{ gitea.sha }}
push: true

10
.gitignore vendored
View File

@@ -1,9 +1,13 @@
# Nix shell files
.direnv/
# Secrets # Secrets
dev.env *.secret.yaml
secret.yaml
# Static data # Static data
.bot_storage/ .bot/
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

4
.vscode/launch.json vendored
View File

@@ -6,7 +6,7 @@
"configurations": [ "configurations": [
{ {
"name": "Python: Current project", "name": "Python: Current project",
"type": "python", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/bot/main.py", "program": "${workspaceFolder}/bot/main.py",
"console": "integratedTerminal", "console": "integratedTerminal",
@@ -14,4 +14,4 @@
"envFile": "${workspaceFolder}/dev.env", "envFile": "${workspaceFolder}/dev.env",
} }
] ]
} }

View File

@@ -1,17 +1,14 @@
FROM python:3-slim FROM docker.io/python:3.13-alpine
ENV DOCKERIZED=true # use the latest version of uv, independently of the python version
ARG BOT_VERSION COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# set at build time
ENV BOT_VERSION=$BOT_VERSION
WORKDIR /app WORKDIR /app
RUN pip install pipenv
# copy the requirements and install them
COPY pyproject.toml uv.lock .
RUN uv sync --frozen
COPY Pipfile Pipfile.lock ./ # copy the rest of the code
RUN pipenv install --system --deploy
COPY bot . COPY bot .
CMD ["python", "main.py"] CMD ["uv", "run", "main.py"]

View File

@@ -1,7 +0,0 @@
CURRENT_DIR := $(shell pwd)
DOTENV := ${CURRENT_DIR}/dev.env
PIPENV_CMD_PREFIX := PIPENV_DOTENV_LOCATION=${DOTENV} pipenv run
run:
${PIPENV_CMD_PREFIX} python bot/main.py

14
Pipfile
View File

@@ -1,14 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
peewee = "*"
python-telegram-bot = {extras = ["job-queue"], version = "*"}
anyio = "*"
[dev-packages]
[pipenv]
allow_prereleases = true

131
Pipfile.lock generated
View File

@@ -1,131 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "5458e81c4f85af776acc44f46af838644ef8c00ccf4223fbe06f9d76a4717fc6"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"anyio": {
"hashes": [
"sha256:48d53f0b141f5757c38d648309e6fe254857fae092d67f938fa248d7c0f36804",
"sha256:596b09c520820e7eed961ddc889540972f92d5e8fcb081117fc054c409df34ae"
],
"index": "pypi",
"version": "==4.0.0rc1"
},
"apscheduler": {
"hashes": [
"sha256:0293937d8f6051a0f493359440c1a1b93e882c57daf0197afeff0e727777b96e",
"sha256:e813ad5ada7aff36fb08cdda746b520531eaac7757832abc204868ba78e0c8f6"
],
"version": "==3.10.1"
},
"certifi": {
"hashes": [
"sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
"sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
],
"markers": "python_version >= '3.6'",
"version": "==2023.7.22"
},
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"httpcore": {
"hashes": [
"sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888",
"sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"
],
"markers": "python_version >= '3.7'",
"version": "==0.17.3"
},
"httpx": {
"hashes": [
"sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd",
"sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"
],
"markers": "python_version >= '3.7'",
"version": "==0.24.1"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"peewee": {
"hashes": [
"sha256:10769981198c7311f84a0ca8db892fa213303a8eb1305deb795a71e7bd606a91"
],
"index": "pypi",
"version": "==3.16.2"
},
"python-telegram-bot": {
"extras": [
"job-queue"
],
"hashes": [
"sha256:a6ac3f9c9674aaf7d1c7e652d8b75cde969fb872f75e9521b8516eceaba82b1b",
"sha256:e426404b0006989a5bcc05e11a7ef3ffe0c086b684a4e963db5bda1d361a049a"
],
"index": "pypi",
"version": "==20.4"
},
"pytz": {
"hashes": [
"sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
"sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
],
"version": "==2023.3"
},
"setuptools": {
"hashes": [
"sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f",
"sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"
],
"markers": "python_version >= '3.7'",
"version": "==68.0.0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"tzlocal": {
"hashes": [
"sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803",
"sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"
],
"markers": "python_version >= '3.7'",
"version": "==5.0.1"
}
},
"develop": {}
}

View File

@@ -38,7 +38,7 @@ class JournalHandler(BaseHandler):
async def entry_point(self, update, context): async def entry_point(self, update, context):
await super().entry_point(update, context) await super().entry_point(update, context)
if os.getenv("DOCKERIZED", "false") == "true" and os.getenv("CHAT_ID") != str(update.message.chat_id): if models.IS_PRODUCTION and os.getenv("CHAT_ID") != str(update.message.chat_id):
await update.message.reply_text("You are not authorized to use this bot") await update.message.reply_text("You are not authorized to use this bot")
return ConversationHandler.END return ConversationHandler.END
@@ -79,7 +79,7 @@ class JournalHandler(BaseHandler):
else: else:
await query.edit_message_text(text="An entry already exists for this date") await query.edit_message_text(text="An entry already exists for this date")
return ConversationHandler.END return ConversationHandler.END
return CONTENT_ENTRY return CONTENT_ENTRY
@@ -113,13 +113,13 @@ class JournalHandler(BaseHandler):
async def date_entry(self, update, context): async def date_entry(self, update, context):
date = update.message.text date = update.message.text
try: try:
date = datetime.datetime.strptime(date, "%d%m%Y").date() date = datetime.datetime.strptime(date, "%d%m%Y").date()
except ValueError: except ValueError:
await update.message.reply_text("Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2) await update.message.reply_text("Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2)
return ENTRY_OPTIONS return ENTRY_OPTIONS
if context.chat_data.get("delete", False): # if not set, delete was not chosen if context.chat_data.get("delete", False): # if not set, delete was not chosen
with models.db: with models.db:
self.current_model = models.JournalEntry.get_or_none( self.current_model = models.JournalEntry.get_or_none(
@@ -158,7 +158,7 @@ class JournalHandler(BaseHandler):
file = await update.message.effective_attachment[-1].get_file() file = await update.message.effective_attachment[-1].get_file()
else: else:
file = await update.message.effective_attachment.get_file() file = await update.message.effective_attachment.get_file()
file_bytes = await file.download_as_bytearray() file_bytes = await file.download_as_bytearray()
file_path = file.file_path file_path = file.file_path
self.current_model.save_media(file_bytes, file_path) self.current_model.save_media(file_bytes, file_path)
@@ -211,7 +211,7 @@ def get_names(dates: list):
suffix = "" suffix = ""
if models.JournalEntry.get_or_none(date = d): if models.JournalEntry.get_or_none(date = d):
suffix = "" suffix = ""
if d == datetime.datetime.now().date(): if d == datetime.datetime.now().date():
names.append("Today" + suffix) names.append("Today" + suffix)
elif d == datetime.datetime.now().date() - datetime.timedelta(days = 1): elif d == datetime.datetime.now().date() - datetime.timedelta(days = 1):

View File

@@ -3,7 +3,7 @@ from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, fi
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto
import models import models
from telegram.constants import ParseMode from telegram.constants import ParseMode
# ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
MEMORY_CHOICE = range(1) MEMORY_CHOICE = range(1)
@@ -28,7 +28,7 @@ class MemoryHandler(BaseHandler):
async def entry_point(self, update: Update, context: CallbackContext): async def entry_point(self, update: Update, context: CallbackContext):
await super().entry_point(update, context) await super().entry_point(update, context)
if os.getenv("DOCKERIZED", "false") == "true" and os.getenv("CHAT_ID") != str(update.message.chat_id): if models.IS_PRODUCTION and os.getenv("CHAT_ID") != str(update.message.chat_id):
await update.message.reply_text("You are not authorized to use this bot") await update.message.reply_text("You are not authorized to use this bot")
return ConversationHandler.END return ConversationHandler.END
@@ -56,9 +56,9 @@ class MemoryHandler(BaseHandler):
await update.message.reply_text( await update.message.reply_text(
f"Which moment would you like to remember?", reply_markup=keyboard f"Which moment would you like to remember?", reply_markup=keyboard
) )
context.chat_data["kept_matches"] = list(matching_models) context.chat_data["kept_matches"] = list(matching_models)
return MEMORY_CHOICE return MEMORY_CHOICE
async def choose_memory(self, update: Update, context: CallbackContext): async def choose_memory(self, update: Update, context: CallbackContext):

View File

@@ -13,8 +13,8 @@ class SetChatPhotoJob():
def __init__(self, bot: ExtBot, job_queue): def __init__(self, bot: ExtBot, job_queue):
self.bot = bot self.bot = bot
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
if os.getenv("DOCKERIZED", "false") != "true": if models.IS_PRODUCTION:
# when running locally, annoy the programmer every 60 seconds <3 # when running locally, annoy the programmer every 60 seconds <3
job_queue.run_repeating(self.callback_photo, interval=60) job_queue.run_repeating(self.callback_photo, interval=60)
else: else:
@@ -24,13 +24,13 @@ class SetChatPhotoJob():
async def callback_photo(self, context): async def callback_photo(self, context):
# last_seen of memory must be older than 10 days in past or None # last_seen of memory must be older than 10 days in past or None
with models.db: with models.db:
possible_photos = models.JournalEntry.select().where( possible_photos = models.JournalEntry.select().where(
models.JournalEntry.media_path != None models.JournalEntry.media_path != None
).order_by(fn.Random()) ).order_by(fn.Random())
try: try:
chosen_entry = possible_photos.get() chosen_entry = possible_photos.get()
except: except:

View File

@@ -4,13 +4,14 @@ import os
from peewee import fn from peewee import fn
import logging import logging
import models import models
from telegram.ext import ExtBot
class RandomMemoryJob(): class RandomMemoryJob():
def __init__(self, bot, job_queue): def __init__(self, bot: ExtBot, job_queue):
self.bot = bot self.bot = bot
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
if os.getenv("DOCKERIZED", "false") != "true": if models.IS_PRODUCTION:
# when running locally, annoy the programmer every 60 seconds <3 # when running locally, annoy the programmer every 60 seconds <3
job_queue.run_repeating(self.callback_memory, interval=3600) job_queue.run_repeating(self.callback_memory, interval=3600)
self.min_age = 0 # do not filter messages: show them all self.min_age = 0 # do not filter messages: show them all
@@ -22,14 +23,14 @@ class RandomMemoryJob():
async def callback_memory(self, context): async def callback_memory(self, context):
# last_seen of memory must be older than 10 days in past or None # last_seen of memory must be older than 10 days in past or None
with models.db: with models.db:
possible_entries = models.JournalEntry.select().where( possible_entries = models.JournalEntry.select().where(
(models.JournalEntry.last_shown <= datetime.today().date() - timedelta(days=self.min_age)) | \ (models.JournalEntry.last_shown <= datetime.today().date() - timedelta(days=self.min_age)) | \
(models.JournalEntry.last_shown == None) (models.JournalEntry.last_shown == None)
).order_by(fn.Random()) ).order_by(fn.Random())
try: try:
chosen_entry = possible_entries.get() chosen_entry = possible_entries.get()
except: except:

View File

@@ -2,7 +2,7 @@ from peewee import *
from pathlib import Path from pathlib import Path
import re import re
import os import os
import datetime import socket
ID_MAPPINGS = { ID_MAPPINGS = {
"Lia": 5603036217, "Lia": 5603036217,
@@ -11,9 +11,9 @@ ID_MAPPINGS = {
ID_MAPPINGS_REV = dict((v, k) for k, v in ID_MAPPINGS.items()) ID_MAPPINGS_REV = dict((v, k) for k, v in ID_MAPPINGS.items())
RATING_MAPPING = { RATING_MAPPING = {
1: "😵", 1: "🙁",
2: "☹️", 2: "😐",
3: "😐", 3: "🙂",
4: "😃", 4: "😃",
5: "🥰" 5: "🥰"
} }
@@ -21,6 +21,11 @@ RATING_MAPPING = {
MEDIA_DIR = Path(os.getenv("MEDIA_DIR")) MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
MEDIA_DIR.mkdir(parents=True, exist_ok=True) MEDIA_DIR.mkdir(parents=True, exist_ok=True)
# check if we are running on a cluster
IS_PRODUCTION = os.getenv('KUBERNETES_SERVICE_HOST') is not None
db = DatabaseProxy() db = DatabaseProxy()
class BaseModel(Model): class BaseModel(Model):
@@ -37,11 +42,11 @@ class JournalEntry(BaseModel):
media_path = TextField(null=True) media_path = TextField(null=True)
last_shown = DateField(null=True) last_shown = DateField(null=True)
rating = IntegerField(null=True) # mapped by RATING_MAPPING rating = IntegerField(null=True) # mapped by RATING_MAPPING
@property @property
def media(self): def media(self):
return Path(self.media_path).open('rb') return Path(self.media_path).open('rb')
def save_media(self, media: bytearray, file_name: str): def save_media(self, media: bytearray, file_name: str):
ext = Path(file_name).suffix ext = Path(file_name).suffix
file_name = f"{self.date.isoformat()}-media{ext}" file_name = f"{self.date.isoformat()}-media{ext}"
@@ -87,7 +92,7 @@ class JournalEntry(BaseModel):
group_to_replace = match[0] group_to_replace = match[0]
new_text = new_text.replace(group_to_replace, f"<tg-spoiler>{group_to_replace}</tg-spoiler>") new_text = new_text.replace(group_to_replace, f"<tg-spoiler>{group_to_replace}</tg-spoiler>")
return new_text return new_text
def set_db(db_path): def set_db(db_path):
db.initialize(SqliteDatabase(db_path)) db.initialize(SqliteDatabase(db_path))

29
default.nix Normal file
View File

@@ -0,0 +1,29 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
name = "journal-bot-shell";
buildInputs = with pkgs; [
# python
python313
# setuptools as downloaded by uv seems to be broken on nixos
python313Packages.setuptools
uv
];
# fix library dependencies:
env.LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
# required by numpy and the likes
pkgs.stdenv.cc.cc.lib
pkgs.libz
];
# tell UV where to put the virtualenv:
# env.UV_PROJECT_ENVIRONMENT = ".cache/uv-venvs/thesis";
# install the python packages through uv:
shellHook = "
uv sync
source .venv/bin/activate
";
}

3
dev.env Normal file
View File

@@ -0,0 +1,3 @@
MEDIA_DIR="./.bot/media"
PERSISTENCE_DIR="./.bot/persistence"
DB_PATH="./.bot/db.sqlite"

11
pyproject.toml Normal file
View File

@@ -0,0 +1,11 @@
[project]
name = "journal-bot"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"anyio>=4.9.0",
"peewee>=3.18.2",
"python-telegram-bot[job-queue]>=22.3",
]

153
uv.lock generated Normal file
View File

@@ -0,0 +1,153 @@
version = 1
revision = 2
requires-python = ">=3.13"
[[package]]
name = "anyio"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
]
[[package]]
name = "apscheduler"
version = "3.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzlocal" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4e/00/6d6814ddc19be2df62c8c898c4df6b5b1914f3bd024b780028caa392d186/apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133", size = 107347, upload-time = "2024-11-24T19:39:26.463Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004, upload-time = "2024-11-24T19:39:24.442Z" },
]
[[package]]
name = "certifi"
version = "2025.7.14"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "journal-bot"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "anyio" },
{ name = "peewee" },
{ name = "python-telegram-bot", extra = ["job-queue"] },
]
[package.metadata]
requires-dist = [
{ name = "anyio", specifier = ">=4.9.0" },
{ name = "peewee", specifier = ">=3.18.2" },
{ name = "python-telegram-bot", extras = ["job-queue"], specifier = ">=22.3" },
]
[[package]]
name = "peewee"
version = "3.18.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/04/89/76f6f1b744c8608e0d416b588b9d63c2a500ff800065ae610f7c80f532d6/peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0", size = 949220, upload-time = "2025-07-08T12:52:03.941Z" }
[[package]]
name = "python-telegram-bot"
version = "22.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
]
sdist = { url = "https://files.pythonhosted.org/packages/db/fc/0196e0d7ad247011a560788db204e0a28d76ab75b3d7c7131878f8fb5a06/python_telegram_bot-22.3.tar.gz", hash = "sha256:513d5ab9db96dcf25272dad0a726555e80edf60d09246a7d0d425b77115f5440", size = 1464513, upload-time = "2025-07-20T20:03:09.805Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/54/0955bd46a1e046169500e129c7883664b6675d580074d68823485e4d5de1/python_telegram_bot-22.3-py3-none-any.whl", hash = "sha256:88fab2d1652dbfd5379552e8b904d86173c524fdb9270d3a8685f599ffe0299f", size = 717115, upload-time = "2025-07-20T20:03:07.261Z" },
]
[package.optional-dependencies]
job-queue = [
{ name = "apscheduler" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "tzdata"
version = "2025.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
]
[[package]]
name = "tzlocal"
version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
]