more refinements for the deployment
All checks were successful
Build container / Build (pull_request) Successful in 1m10s

This commit is contained in:
2025-07-29 15:58:34 +02:00
parent 29d951427d
commit f7478fb1e3
15 changed files with 180 additions and 99 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

View File

@@ -2,6 +2,9 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
push:
branches:
- main
name: Build container name: Build container

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):
@@ -13,7 +13,7 @@ class ListModel(BaseModel):
@property @property
def content(self) -> dict: def content(self) -> dict:
return {e.id: e.entry for e in self.entries} return {e.id: e.entry for e in self.entries}
@content.setter @content.setter
def content(self, new_content: dict): def content(self, new_content: dict):
old_content = self.content old_content = self.content
@@ -29,7 +29,7 @@ class ListModel(BaseModel):
@property @property
def done_dict(self): def done_dict(self):
return {e.id: e.done for e in self.entries} return {e.id: e.done for e in self.entries}
@done_dict.setter @done_dict.setter
def done_dict(self, new_done: dict): def done_dict(self, new_done: dict):
old_done_dict = self.done_dict old_done_dict = self.done_dict
@@ -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

@@ -7,10 +7,14 @@ 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."""
def __init__(self, entry_string): def __init__(self, entry_string):
self.start_time = datetime.datetime.now() self.start_time = datetime.datetime.now()
self.entry_string = entry_string self.entry_string = entry_string
@@ -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,18 +50,21 @@ 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)
else: else:
await update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) await update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2)
return FIRST return FIRST

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 models.IS_PRODUCTION: 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,95 @@
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, when=sending_time, day=-1)
async def callback_leaderboard(self, context):
"""Send a weakly leaderboard to the chat."""
if date.today().weekday() != 1:
self.logger.info("Today is not Monday, skipping leaderboard.")
return
# 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

@@ -11,7 +11,7 @@ class RandomMemoryJob():
self.bot = bot self.bot = bot
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
if models.IS_PRODUCTION: 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

@@ -79,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:

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-bot
images: images:
- name: mollre/journal-bot - name: journal
newTag: 1.0.68 newName: git.kluster.moll.re/remoll/journal-bot
newTag: 29d951427d6f3377e43767916cefb07e03e9eab8

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

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

14
renovate.json5 Normal file
View File

@@ -0,0 +1,14 @@
{
"$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]"
}