12 Commits

Author SHA1 Message Date
f79d1f3d84 Update docker/build-push-action action to v6
All checks were successful
Build container / Build (pull_request) Successful in 57s
2025-07-30 12:00:49 +00:00
cfcc88f8d4 [ci skip] update deployment 2025-07-30 11:39:43 +00:00
3d69f9b5d8 force tuple type
All checks were successful
Build container / Build (push) Successful in 41s
2025-07-30 11:35:47 +00:00
0ee2e3ac72 [ci skip] update deployment 2025-07-30 11:19:10 +00:00
840fd90b75 fix invalid scheduling
All checks were successful
Build container / Build (push) Successful in 38s
2025-07-30 12:41:37 +02:00
5e514bcc20 switch to semver so that renovate is happy
All checks were successful
Build container / Build (push) Successful in 35s
2025-07-30 12:17:31 +02:00
7b91ddbcb8 Update renovate.json5
All checks were successful
Build container / Build (push) Successful in 34s
2025-07-30 11:40:56 +02:00
8583ccd249 refined package rules
All checks were successful
Build container / Build (push) Successful in 45s
2025-07-30 00:18:13 +02:00
c43bd2b7ec Update deployment/kustomization.yaml
All checks were successful
Build container / Build (push) Successful in 47s
2025-07-29 15:47:14 +00:00
ea8b6fc8f7 Merge pull request 'switched to uv and gitea-actions-based pipeline' (#9) from chore/updated-pipeline into main
All checks were successful
Build container / Build (push) Successful in 43s
Reviewed-on: #9
2025-07-29 15:14:09 +00:00
f7478fb1e3 more refinements for the deployment
All checks were successful
Build container / Build (pull_request) Successful in 1m10s
2025-07-29 15:58:34 +02:00
29d951427d switched to uv and gitea-actions-based pipeline
All checks were successful
Build container / Build (pull_request) Successful in 43s
2025-07-29 14:28:07 +02:00
29 changed files with 472 additions and 291 deletions

View File

@@ -1,29 +0,0 @@
kind: pipeline
type: kubernetes
name: docker-build
node_selector:
kubernetes.io/arch: amd64
steps:
- name: docker
image: plugins/docker
settings:
username:
from_secret: docker_uname
password:
from_secret: docker_pw
repo: mollre/journal-bot
tags:
- 1.0.${DRONE_BUILD_NUMBER}
- latest
build_args: "BOT_VERSION=1.0.${DRONE_BUILD_NUMBER}"
trigger:
branch:
- main
event:
- push

1
.envrc Normal file
View File

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

View File

@@ -0,0 +1,50 @@
on:
pull_request:
branches:
- main
push:
branches:
- main
name: Build container
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/actions/checkout@v4
- name: Fetch tags from main branch
id: version
run: |
git fetch origin main --tags
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
# remove the 'v' prefix from the tag name
echo "BUILD_NAME=${LATEST_TAG//v}" >> $GITHUB_OUTPUT
- name: Output the version that is being used
run: |
echo "Building for version ${{ steps.version.outputs.BUILD_NAME }}"
- 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@v6
with:
context: .
# use the current commit SHA as the tag
tags: git.kluster.moll.re/remoll/journal-bot:${{ steps.version.outputs.BUILD_NAME }}-${{ gitea.run_number }}
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

2
.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",

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

View File

@@ -1,5 +1,5 @@
from pathlib import Path
from peewee import * from peewee import *
db = DatabaseProxy() db = DatabaseProxy()
class BaseModel(Model): class BaseModel(Model):
@@ -46,7 +46,8 @@ class ListEntryModel(BaseModel):
done = BooleanField(default=None, null=True) done = BooleanField(default=None, null=True)
def set_db(db_path): def set_db(db_path: Path):
db_path.parent.mkdir(parents=True, exist_ok=True)
db.initialize(SqliteDatabase(db_path)) db.initialize(SqliteDatabase(db_path))
with db: with db:
db.create_tables([ListModel, ListEntryModel], safe=True) db.create_tables([ListModel, ListEntryModel], safe=True)

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

View File

@@ -7,7 +7,11 @@ from telegram.constants import ParseMode
import os import os
FIRST = 1 FIRST = 1
import models
from .basehandler import BaseHandler from .basehandler import BaseHandler
class StatusHandler(BaseHandler): class StatusHandler(BaseHandler):
"""Shows a short status of the program.""" """Shows a short status of the program."""
@@ -35,7 +39,6 @@ class StatusHandler(BaseHandler):
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
delta = str(datetime.datetime.now() - self.start_time) delta = str(datetime.datetime.now() - self.start_time)
message = "BeebBop, this is Norbit\n"
try: try:
ip = httpx.get('https://api.ipify.org').text ip = httpx.get('https://api.ipify.org').text
@@ -47,12 +50,15 @@ class StatusHandler(BaseHandler):
ip = "not fetchable" ip = "not fetchable"
local_ips = "not fetchable" local_ips = "not fetchable"
message += "Status: Running 🟢\n" message = f"""
message += f"Version: `{os.getenv('BOT_VERSION', 'dev')}`\n" BeebBop\!
message += f"Uptime: `{delta[:delta.rfind('.')]}`\n" Status: Running 🟢
message += f"IP \(public\): `{ip}`\n" Version: `{os.getenv('BOT_VERSION', 'dev')}` and`prod={models.IS_PRODUCTION}`
message += f"IP \(private\): `{local_ips}`\n" Uptime: `{delta[:delta.rfind('.')]}`
message += f"Chat ID: `{update.effective_chat.id}`\n" IP \(public\): `{ip}`
IP \(private\): `{local_ips}`
Chat ID: `{update.effective_chat.id}`
""".strip() # remove trailing whitespace
if update.message: if update.message:
await update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) await update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2)

View File

@@ -14,7 +14,7 @@ class SetChatPhotoJob():
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 not 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:

View File

@@ -0,0 +1,92 @@
import os
from telegram.ext import ExtBot
from telegram.constants import ParseMode
import logging
from datetime import time, timedelta, timezone, datetime, date
from peewee import fn
import models
from telegram.ext import JobQueue
RANKING_TEMPLATE = """
<b>Journal Leaderboard</b>
This week: 📈{week_leader_name} - {week_leader_count} 📉{week_last_name} - {week_last_count}
This month: 📈{month_leader_name} - {month_leader_count} 📉{month_last_name} - {month_last_count}
This year: 📈{year_leader_name} - {year_leader_count} 📉{year_last_name} - {year_last_count}
🏆 Leader: {leader_name}
"""
def get_author_ranking(since_days):
"""Returns the query for the top authors by counting their journal entries. An additional field for the count is added."""
cutoff_date = date.today() - timedelta(days=since_days)
with models.db:
return models.JournalEntry.select(
models.JournalEntry.author,
fn.Count(models.JournalEntry.id).alias('message_count')
).where(
models.JournalEntry.date >= cutoff_date
).group_by(
models.JournalEntry.author
).order_by(
fn.Count(models.JournalEntry.id).desc()
)
class SendLeaderboard():
def __init__(self, bot: ExtBot, job_queue: JobQueue):
self.bot = bot
self.logger = logging.getLogger(self.__class__.__name__)
if not models.IS_PRODUCTION:
# when running locally, just run once after 10 seconds
job_queue.run_once(self.callback_leaderboard, when=10)
else:
# set the message sending time; include UTC shift +2
sending_time = time(hour=12, minute=0, second=0, tzinfo=timezone(timedelta(hours=2)))
job_queue.run_daily(self.callback_leaderboard, time=sending_time, days=(0,))
async def callback_leaderboard(self, context):
"""Send a weakly leaderboard to the chat."""
# get the top contributions of the past week, month and year:
ranking_week = get_author_ranking(7)
ranking_month = get_author_ranking(30)
ranking_year = get_author_ranking(365)
week_leader, week_last = ranking_week.first(n=2)
month_leader, month_last = ranking_month.first(n=2)
year_leader, year_last = ranking_year.first(n=2)
leader = year_leader
message_text = RANKING_TEMPLATE.format(
week_leader_name=week_leader.author,
week_leader_count=week_leader.message_count,
week_last_name=week_last.author,
week_last_count=week_last.message_count,
month_leader_name=month_leader.author,
month_leader_count=month_leader.message_count,
month_last_name=month_last.author,
month_last_count=month_last.message_count,
year_leader_name=year_leader.author,
year_leader_count=year_leader.message_count,
year_last_name=year_last.author,
year_last_count=year_last.message_count,
leader_name=leader.author
)
print(message_text)
chat_id = os.getenv("CHAT_ID")
await self.bot.send_message(
chat_id = chat_id,
text = message_text,
parse_mode=ParseMode.HTML
)

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 not 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

View File

@@ -5,7 +5,7 @@ import logging
import models import models
from commands import journal, status, turtle, memory, advent from commands import journal, status, turtle, memory, advent
from commands.list import list from commands.list import list
from cronjob import chat_photo, random_memory from cronjob import chat_photo, random_memory, leaderboard
logging.basicConfig( logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@@ -32,6 +32,7 @@ def main() -> None:
random_memory.RandomMemoryJob(application.bot, application.job_queue) random_memory.RandomMemoryJob(application.bot, application.job_queue)
chat_photo.SetChatPhotoJob(application.bot, application.job_queue) chat_photo.SetChatPhotoJob(application.bot, application.job_queue)
leaderboard.SendLeaderboard(application.bot, application.job_queue)
# Run the bot until the user presses Ctrl-C # Run the bot until the user presses Ctrl-C
application.run_polling() application.run_polling()

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):
@@ -74,13 +79,13 @@ class JournalEntry(BaseModel):
"""Returns the text with all the frisky details hidden away""" """Returns the text with all the frisky details hidden away"""
new_text = self.text.replace("<", "&lt;").replace(">", "&gt;").replace("&", "&amp;") new_text = self.text.replace("<", "&lt;").replace(">", "&gt;").replace("&", "&amp;")
pattern = re.compile( pattern = re.compile(
"(" r"("
"(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence r"(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence
"([^\.\!\?])+" # any character being part of a sentence r"([^\.\!\?])+" # any character being part of a sentence
"((\:\))|😇|😈|[Ss]ex)" # the smiley r"((\:\))|😇|😈|[Ss]ex)" # the smiley
"([^\.\!\?])*" # continuation of sentence r"([^\.\!\?])*" # continuation of sentence
"(\.|\!|\?|\,|$)" # end of the sentence r"(\.|\!|\?|\,|$)" # end of the sentence
")" r")"
) )
matches = pattern.findall(new_text) matches = pattern.findall(new_text)
for match in matches: for match in matches:

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
";
}

View File

@@ -17,7 +17,7 @@ spec:
spec: spec:
containers: containers:
- name: journal - name: journal
image: mollre/journal-bot:1.0.19 image: journal
envFrom: envFrom:
- secretRef: - secretRef:
name: journal-secret-env name: journal-secret-env
@@ -33,31 +33,3 @@ spec:
- name: journal-nfs - name: journal-nfs
persistentVolumeClaim: persistentVolumeClaim:
claimName: journal-data-nfs claimName: journal-data-nfs
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: "journal-data-nfs"
spec:
storageClassName: ""
capacity:
storage: "5Gi"
accessModes:
- ReadWriteOnce
nfs:
path: /export/kluster/journal-bot
server: 192.168.1.157
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "journal-data-nfs"
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "5Gi"
volumeName: journal-data-nfs

View File

@@ -1,9 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- ./namespace.yaml - ./namespace.yaml
- ./deployment.yaml - ./deployment.yaml
- ./sealedsecret.yaml - ./sealedsecret.yaml
- ./pvc.yaml
namespace: journal
images: images:
- name: mollre/journal-bot - name: journal
newTag: 1.0.68 newName: git.kluster.moll.re/remoll/journal-bot
newTag: 1.1.0-17

View File

@@ -1,6 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: journal name: placeholder
labels:
name: journal

27
deployment/pvc.yaml Normal file
View File

@@ -0,0 +1,27 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: "journal-data-nfs"
spec:
storageClassName: ""
capacity:
storage: "5Gi"
accessModes:
- ReadWriteOnce
nfs:
path: /export/kluster/journal-bot
server: 192.168.1.157
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "journal-data-nfs"
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "5Gi"
volumeName: journal-data-nfs

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",
]

View File

@@ -1,13 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"automergeType": "branch",
"ignoreTests": true
}
],
"commitMessagePrefix" : "[CI SKIP]"
}

17
renovate.json5 Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"dependencyDashboard": true,
"packageRules": [
// Fully automatically update the container version referenced in the deployment
{
"matchPackageNames": ["@kubernetes-sigs/kustomize"],
"automerge": true,
"automergeType": "branch",
"ignoreTests": true,
"commitMessagePrefix": "[skip ci]",
"registryUrls": [
"https://git.kluster.moll.re"
],
}
],
}

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" },
]