switched to uv and gitea-actions-based pipeline #9
							
								
								
									
										29
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -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
 | 
			
		||||
@@ -2,6 +2,9 @@ on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
 | 
			
		||||
name: Build container
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from peewee import *
 | 
			
		||||
 | 
			
		||||
db = DatabaseProxy()
 | 
			
		||||
 | 
			
		||||
class BaseModel(Model):
 | 
			
		||||
@@ -46,7 +46,8 @@ class ListEntryModel(BaseModel):
 | 
			
		||||
    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))
 | 
			
		||||
    with db:
 | 
			
		||||
        db.create_tables([ListModel, ListEntryModel], safe=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,11 @@ from telegram.constants import ParseMode
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
FIRST = 1
 | 
			
		||||
import models
 | 
			
		||||
from .basehandler import BaseHandler
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StatusHandler(BaseHandler):
 | 
			
		||||
    """Shows a short status of the program."""
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +39,6 @@ class StatusHandler(BaseHandler):
 | 
			
		||||
        reply_markup = InlineKeyboardMarkup(keyboard)
 | 
			
		||||
 | 
			
		||||
        delta = str(datetime.datetime.now() - self.start_time)
 | 
			
		||||
        message = "BeebBop, this is Norbit\n"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            ip = httpx.get('https://api.ipify.org').text
 | 
			
		||||
@@ -47,12 +50,15 @@ class StatusHandler(BaseHandler):
 | 
			
		||||
            ip = "not fetchable"
 | 
			
		||||
            local_ips = "not fetchable"
 | 
			
		||||
 | 
			
		||||
        message += "Status: Running 🟢\n"
 | 
			
		||||
        message += f"Version: `{os.getenv('BOT_VERSION', 'dev')}`\n"
 | 
			
		||||
        message += f"Uptime: `{delta[:delta.rfind('.')]}`\n"
 | 
			
		||||
        message += f"IP \(public\): `{ip}`\n"
 | 
			
		||||
        message += f"IP \(private\): `{local_ips}`\n"
 | 
			
		||||
        message += f"Chat ID: `{update.effective_chat.id}`\n"
 | 
			
		||||
        message = f"""
 | 
			
		||||
            BeebBop\!
 | 
			
		||||
            Status: Running 🟢
 | 
			
		||||
            Version: `{os.getenv('BOT_VERSION', 'dev')}` and`prod={models.IS_PRODUCTION}`
 | 
			
		||||
            Uptime: `{delta[:delta.rfind('.')]}`
 | 
			
		||||
            IP \(public\): `{ip}`
 | 
			
		||||
            IP \(private\): `{local_ips}`
 | 
			
		||||
            Chat ID: `{update.effective_chat.id}`
 | 
			
		||||
        """.strip() # remove trailing whitespace
 | 
			
		||||
 | 
			
		||||
        if update.message:
 | 
			
		||||
            await update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ class SetChatPhotoJob():
 | 
			
		||||
        self.bot = bot
 | 
			
		||||
        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
 | 
			
		||||
            job_queue.run_repeating(self.callback_photo, interval=60)
 | 
			
		||||
        else:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										95
									
								
								bot/cronjob/leaderboard.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								bot/cronjob/leaderboard.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
        )
 | 
			
		||||
@@ -11,7 +11,7 @@ class RandomMemoryJob():
 | 
			
		||||
        self.bot = bot
 | 
			
		||||
        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
 | 
			
		||||
            job_queue.run_repeating(self.callback_memory, interval=3600)
 | 
			
		||||
            self.min_age = 0 # do not filter messages: show them all
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import logging
 | 
			
		||||
import models
 | 
			
		||||
from commands import journal, status, turtle, memory, advent
 | 
			
		||||
from commands.list import list
 | 
			
		||||
from cronjob import chat_photo, random_memory
 | 
			
		||||
from cronjob import chat_photo, random_memory, leaderboard
 | 
			
		||||
 | 
			
		||||
logging.basicConfig(
 | 
			
		||||
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 | 
			
		||||
@@ -32,6 +32,7 @@ def main() -> None:
 | 
			
		||||
 | 
			
		||||
    random_memory.RandomMemoryJob(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
 | 
			
		||||
    application.run_polling()
 | 
			
		||||
 
 | 
			
		||||
@@ -79,13 +79,13 @@ class JournalEntry(BaseModel):
 | 
			
		||||
        """Returns the text with all the frisky details hidden away"""
 | 
			
		||||
        new_text = self.text.replace("<", "<").replace(">", ">").replace("&", "&")
 | 
			
		||||
        pattern = re.compile(
 | 
			
		||||
            "("
 | 
			
		||||
            "(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence
 | 
			
		||||
            "([^\.\!\?])+" # any character being part of a sentence
 | 
			
		||||
            "((\:\))|😇|😈|[Ss]ex)" # the smiley
 | 
			
		||||
            "([^\.\!\?])*" # continuation of sentence
 | 
			
		||||
            "(\.|\!|\?|\,|$)" # end of the sentence
 | 
			
		||||
            ")"
 | 
			
		||||
            r"("
 | 
			
		||||
            r"(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence
 | 
			
		||||
            r"([^\.\!\?])+" # any character being part of a sentence
 | 
			
		||||
            r"((\:\))|😇|😈|[Ss]ex)" # the smiley
 | 
			
		||||
            r"([^\.\!\?])*" # continuation of sentence
 | 
			
		||||
            r"(\.|\!|\?|\,|$)" # end of the sentence
 | 
			
		||||
            r")"
 | 
			
		||||
        )
 | 
			
		||||
        matches = pattern.findall(new_text)
 | 
			
		||||
        for match in matches:
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ spec:
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: journal
 | 
			
		||||
          image: mollre/journal-bot:1.0.19
 | 
			
		||||
          image: journal
 | 
			
		||||
          envFrom:
 | 
			
		||||
            - secretRef:
 | 
			
		||||
                name: journal-secret-env
 | 
			
		||||
@@ -33,31 +33,3 @@ spec:
 | 
			
		||||
        - name: journal-nfs
 | 
			
		||||
          persistentVolumeClaim:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,15 @@
 | 
			
		||||
apiVersion: kustomize.config.k8s.io/v1beta1
 | 
			
		||||
kind: Kustomization
 | 
			
		||||
 | 
			
		||||
resources:
 | 
			
		||||
- ./namespace.yaml
 | 
			
		||||
- ./deployment.yaml
 | 
			
		||||
- ./sealedsecret.yaml
 | 
			
		||||
- ./pvc.yaml
 | 
			
		||||
 | 
			
		||||
namespace: journal-bot
 | 
			
		||||
 | 
			
		||||
images:
 | 
			
		||||
- name: mollre/journal-bot
 | 
			
		||||
  newTag: 1.0.68
 | 
			
		||||
- name: journal
 | 
			
		||||
  newName: git.kluster.moll.re/remoll/journal-bot
 | 
			
		||||
  newTag: 29d951427d6f3377e43767916cefb07e03e9eab8
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: Namespace
 | 
			
		||||
metadata:
 | 
			
		||||
  name: journal
 | 
			
		||||
  labels:
 | 
			
		||||
    name: journal
 | 
			
		||||
  name: placeholder
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								deployment/pvc.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								deployment/pvc.yaml
									
									
									
									
									
										Normal 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
 | 
			
		||||
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										14
									
								
								renovate.json5
									
									
									
									
									
										Normal 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]"
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user