2 Commits

Author SHA1 Message Date
Lia Schöneweiß
18af074b6d Geheim! 2023-11-25 14:52:47 +01:00
Lia Schöneweiß
94933c2da0 streng geheim! 2023-11-18 12:38:58 +01:00
20 changed files with 336 additions and 424 deletions

View File

@@ -16,10 +16,7 @@ steps:
from_secret: docker_pw from_secret: docker_pw
repo: mollre/journal-bot repo: mollre/journal-bot
tags: tags: latest
- 1.0.${DRONE_BUILD_NUMBER}
- latest
build_args: "BOT_VERSION=1.0.${DRONE_BUILD_NUMBER}"
trigger: trigger:

View File

@@ -1,16 +1,11 @@
FROM python:3-slim FROM python:3.10-slim
ENV DOCKERIZED=true ENV DOCKERIZED=true
ARG BOT_VERSION
# set at build time
ENV BOT_VERSION=$BOT_VERSION
WORKDIR /app WORKDIR /app
RUN pip install pipenv
COPY Pipfile Pipfile.lock ./ COPY Pipfile Pipfile.lock ./
RUN pipenv install --system --deploy RUN pip install pipenv && pipenv install --system --deploy
COPY bot . COPY bot .

View File

@@ -4,11 +4,7 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
python-telegram-bot = "*"
peewee = "*" peewee = "*"
python-telegram-bot = {extras = ["job-queue"], version = "*"}
anyio = "*"
[dev-packages] [dev-packages]
[pipenv]
allow_prereleases = true

77
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "5458e81c4f85af776acc44f46af838644ef8c00ccf4223fbe06f9d76a4717fc6" "sha256": "e95b9deab62bd0c661f20a178b8701fc84420db5f663fa4416666e1d05f6ce76"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@@ -16,26 +16,19 @@
"default": { "default": {
"anyio": { "anyio": {
"hashes": [ "hashes": [
"sha256:48d53f0b141f5757c38d648309e6fe254857fae092d67f938fa248d7c0f36804", "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421",
"sha256:596b09c520820e7eed961ddc889540972f92d5e8fcb081117fc054c409df34ae" "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"
], ],
"index": "pypi", "markers": "python_full_version >= '3.6.2'",
"version": "==4.0.0rc1" "version": "==3.6.2"
},
"apscheduler": {
"hashes": [
"sha256:0293937d8f6051a0f493359440c1a1b93e882c57daf0197afeff0e727777b96e",
"sha256:e813ad5ada7aff36fb08cdda746b520531eaac7757832abc204868ba78e0c8f6"
],
"version": "==3.10.1"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
"sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2023.7.22" "version": "==2022.12.7"
}, },
"h11": { "h11": {
"hashes": [ "hashes": [
@@ -47,19 +40,19 @@
}, },
"httpcore": { "httpcore": {
"hashes": [ "hashes": [
"sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888", "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb",
"sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87" "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.17.3" "version": "==0.16.3"
}, },
"httpx": { "httpx": {
"hashes": [ "hashes": [
"sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd", "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9",
"sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd" "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.24.1" "version": "==0.23.3"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@@ -77,38 +70,22 @@
"version": "==3.16.2" "version": "==3.16.2"
}, },
"python-telegram-bot": { "python-telegram-bot": {
"extras": [
"job-queue"
],
"hashes": [ "hashes": [
"sha256:a6ac3f9c9674aaf7d1c7e652d8b75cde969fb872f75e9521b8516eceaba82b1b", "sha256:4d1d4b643ce158aa17a0987b84005eaf25fe0ce8b38fd234099594985611c198",
"sha256:e426404b0006989a5bcc05e11a7ef3ffe0c086b684a4e963db5bda1d361a049a" "sha256:d0aa53e1f06d7cb7919cc0e2d6c81a02d968fc29921aeaa962edd1efb816a9bd"
], ],
"index": "pypi", "index": "pypi",
"version": "==20.4" "version": "==20.2"
}, },
"pytz": { "rfc3986": {
"hashes": [ "extras": [
"sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588", "idna2008"
"sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
], ],
"version": "==2023.3"
},
"setuptools": {
"hashes": [ "hashes": [
"sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f", "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
"sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235" "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
], ],
"markers": "python_version >= '3.7'", "version": "==1.5.0"
"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": { "sniffio": {
"hashes": [ "hashes": [
@@ -117,14 +94,6 @@
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==1.3.0" "version": "==1.3.0"
},
"tzlocal": {
"hashes": [
"sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803",
"sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"
],
"markers": "python_version >= '3.7'",
"version": "==5.0.1"
} }
}, },
"develop": {} "develop": {}

140
bot/commands/advent.py Normal file
View File

@@ -0,0 +1,140 @@
from datetime import date
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler, CallbackContext
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto
import random
# ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
ACTION, TUERCHEN_CHOICE = range(2)
from .basehandler import BaseHandler
class AdventsHandler(BaseHandler):
def __init__(self, entry_string):
self.entry_string = entry_string
self. tuerchen_texte = {
"1": f"Mein kleiner, süßer Schatz, ich geb dir einen Schmatz! \n - Gutschein für ein gaaanz dickes Küsschen! 😘",
"2": f"Die zwei ist eine tolle Zahl, \ndrum schenk ich dir nach deiner Wahl \nein Frühstück so wie's dir gefällt,\n dem cutesten Schuhu der Welt!\n - Gutschein für ein Wunschfrühstück von deinem Bubo 🥞",
"3": f"Die Nummer heute ist die drei mit einer Prise Glück dabei! \n - Der Tag für das Glückskleeblattspritzgebäck ist gekommen! 🍀",
"4": f"Hand in Hand, ganz fest verhakt, geht wir zwei auf den Weihnachtsmarkt!\n - Gutschein für einen heißen Punsch auf dem Zürcher Weihnachtsmarkt 🍹",
"5": f"Fühlt der Schuhu sich mal matt,\nso höre gut auf meinen Rat:\nMassageöl auf das Gefieder,\nDann glänzt und strahlt der Schuhu wieder!\n - Gutschein für eine Massage 👐🏻",
"6": f"Das ist das Haus vom Nikolaus\nund nebendrein der Bubos Heim!\n - Ich habe eine kleine Nascherei für dich! 🍫",
"7": f"Für etwas Spaß braucht es nicht viel,\nden Bubo und ein gutes Spiel! - Gutschein für einen Zockabend mit einem Spiel deiner Wahl 🎮",
"8": f"Zu abend da erzähl ich dir,\nso wundersam Geschichte fein\ndrum spitz dir ohren, lausche mir\ndoch schlaf mir dabei bloß nicht ein!\n - Gutschein für eine Vorlesesession 📖",
"9": f"Es duftet fein aus naher Ferne, denn naschen tuen Kätzchen gerne!\n - Gutschein für ein Überraschungsdessert von deinem Naschkätzchen 😽",
"10": f"Zu spät für Frühstück, zu spät für Lunch, dann machen wir daraus 'nen Brunch!\n - Gutschein für einen Brunch im Café des Amis 🥐",
"11": f"Mein Bubo macht so gerne Quatsch und ist so süß wie Brownie Fudge!\n - Gutschein für ein Ben'n'Jerry's Brownie Fudge Ice Cream 🍫",
"12": f"Halb ist geschafft die lange Reise,\ndrum wähle deine Toppings weise\nmit Apfel, Zimt und andren Sachen, wenn wir zusammen Milchreis machen!\n - Gutschein für eine Milchreismahlzeit, damit dir auf halber Strecke nicht die Kraft ausgeht 🍚",
"13": f"Was hat hier so verbrannt gerochen?\n Das sind die Schuhus, die hier kochen!\n - Gutschein für eine Kochsession für ein ausgefallenes Gericht deiner Wahl 👩‍🍳👨‍🍳",
"14": f"Auf leckres Essen darfst du hoffen, warm und dampfend aus dem Ofen!\n - Gutschein für einen Abend zum Bratapfel-Backen 🍎",
"15": f"Die Weihnacht langsam Einzug hält,\nder erste Schnee vom Himmel fällt,\nGedanken tief in ihm versinken,\nlass uns gehn' einen Kaffee trinken!\n - Gutschein für einen Nachmittagskaffee in der Altstadt ☕️",
"16": f"Ein Glück, der See noch nicht erstarrt,\nPerfekt fpr eine Bötchenfahrt!\n - Gutschein für eine Bootsfahrt auf dem Zürisee 🛳",
"17": f"Was schaut denn da so frech ums Eck,\nnanu, das ist dein Öhrchen-Schleck! \n - Du bekommst gratis einen tollen Öhrchenschleck für beide Schuhu-Spitzöhrchen! 👂🏻",
"18": f"Was ist im Winter glatt ein Muss?\nNa klar, der Spekulatius!\n - Gutschein für ein kleines Spekulatiusdessert 🍰",
"19": f"Wenn Bubos sind mal ganz allein, da können sie ganz unbrav sein...\n - Gutschein für eine ganz zärtliche, persönliche Verführung von deiner Bubodame 😈",
"20": f"Bubo, pack dich ganz warm ein\ndenn bald wird es noch kälter sein!\n - Gutschein für einen Eiskaffe bei Sprüngli und dabei gaaanz warm kuscheln! 🍨",
"21": f"Die Kufen werden angezogen,\nwir kommen übers Eis geflogen!\n - Gutschein für Schlittschuhlaufen, Flügel in Flügel ⛸",
"22": f"Oh bald es wird schon wieder wärmer,\nauf geht's in die Gelateria di Berna!\n - Gutschein für eine dicke Portion Eis in der Gelateria di Berna 🍦",
"23": f"Kleiner Bubo, moki moki,\nich koch dir eine heiße Schoki,\ndamit der Hunger auch nicht weint\nmit einem Kekse sie vereint!\n - Gutschein für eine heiße Spezial-Schoki mit Keksi dazu! 🍪",
"24": f"Das lange Warten ist geschafft,\ngekostet hat es ganz viel Kraft,\nnun höre mich, wenn ich dir sage:\nIch wünsch dir tolle Weihnachtstage,\nmit Family und Freunden schön\nund Kekse auf dem Tische stehend\nund in der Ferne, doch so nah\nist immer jemand für dich da,\nder an dich denkt und dich so liebt\n Mein Bubo ich hab dich so gern! ❤️🎄"
}
self.handler = ConversationHandler(
entry_points=[CommandHandler(entry_string, self.entry_point)],
states={
ACTION: [
CallbackQueryHandler(self.kuesschen, pattern = "^kuss$"),
CallbackQueryHandler(self.tuerchen, pattern = "^tuer$"),
CallbackQueryHandler(self.pretty_picture, pattern="^picture$"),
CallbackQueryHandler(self.byebye, pattern ="^bye$")
# CallbackQueryHandler(self.new_list, pattern="^new$")
],
TUERCHEN_CHOICE: [
MessageHandler(filters.ALL, self.choose_tuerchen)
]
},
fallbacks=[],
)
self.current_model = None
async def entry_point(self, update: Update, context: CallbackContext):
await super().entry_point(update, context)
keyboard = [[InlineKeyboardButton("Bubo Küsschen", callback_data="kuss")], [InlineKeyboardButton("Türchen öffnen", callback_data="tuer")], [InlineKeyboardButton("Pretty Bubo Picture", callback_data="picture")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_sticker(sticker=open(".bot_storage\stickers\stickerwhat.tgs", "rb"))
await update.message.reply_text(text="Hallo, mein süßer Weihnachts-Bubo! Ich bin dein Adventskalender ^^. Was möchtest du tun?", reply_markup=reply_markup)
return ACTION
async def kuesschen(self, update: Update, context: CallbackContext):
query = update.callback_query
await query.answer()
keyboard = [[InlineKeyboardButton("Kalendertürchen öffnen", callback_data="tuer")], [InlineKeyboardButton("Noch ein Küsschen!", callback_data="kuss")], [InlineKeyboardButton("Pretty Bubo Picture", callback_data="picture")], [InlineKeyboardButton("Bis zum nächsten Mal!", callback_data="bye")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.effective_message.reply_sticker(sticker=open(".bot_storage\stickers\stickerkiss.tgs", "rb"))
await update.effective_message.reply_text(text="Mua!", reply_markup=reply_markup)
return ACTION
async def tuerchen(self, update: Update, context: CallbackContext):
query = update.callback_query
await query.answer()
await update.effective_message.reply_text(text="Welches Türchen möchtest du öffnen? Bitte gib die Zahl des Türchens ein:")
return TUERCHEN_CHOICE
async def pretty_picture(self, update: Update, context: CallbackContext):
query = update.callback_query
await query.answer()
keyboard = [[InlineKeyboardButton("Kalendertürchen öffnen", callback_data="tuer")], [InlineKeyboardButton("Bubo Küsschen", callback_data="kuss")], [InlineKeyboardButton("Noch ein Bild!", callback_data="picture")], [InlineKeyboardButton("Bis zum nächsten Mal!", callback_data="bye")]]
reply_markup = InlineKeyboardMarkup(keyboard)
picture_number = random.randint(1,31)
await update.effective_message.reply_photo(photo=open(".bot_storage\pretty_pictures\photo_"+f"{picture_number}"+"_2023-11-25_14-25-53.jpg", "rb"), caption="So ein cutes Foto!", reply_markup=reply_markup)
return ACTION
async def choose_tuerchen(self, update: Update, context: CallbackContext):
tuerchen = update.message.text
tuer_nummer = int(tuerchen)
#print(date.today().strftime("%d"+"%m"))
keyboard = [[InlineKeyboardButton("Anderes Türchen öffnen", callback_data="tuer")], [InlineKeyboardButton("Bubo Küsschen", callback_data="kuss")], [InlineKeyboardButton("Pretty Bubo Picture", callback_data="picture")], [InlineKeyboardButton("Bis zum nächsten Mal!", callback_data="bye")]]
reply_markup = InlineKeyboardMarkup(keyboard)
#if tuer_nummer <= int(date.today().strftime("%d")):
if (tuer_nummer <= int(date.today().strftime("%d"))) and (int(date.today().strftime("%m"))==12):
await update.message.reply_document(document=open(".bot_storage\gifs\gif"+f"{tuer_nummer}"+".mp4", "rb"),caption=f"Türchen für den {tuer_nummer}. Dezember: \n" + self.tuerchen_texte[tuerchen], reply_markup=reply_markup)
else:
await update.message.reply_sticker(sticker=open(".bot_storage\stickers\stickerangry.tgs", "rb"))
await update.message.reply_text(text="Hey, nicht schummeln! Dieses Türchen darfst du noch nicht sehen.", reply_markup=reply_markup)
return ACTION
async def byebye(self, update: Update, context: CallbackContext):
query = update.callback_query
await query.answer()
await update.effective_message.reply_sticker(sticker=open(".bot_storage\stickers\stickerbye.tgs", "rb"))
await update.effective_message.reply_text(text="Bye bye, Bubo! Hab dich ganz doll lieb! 😘")
return ConversationHandler.END

View File

@@ -5,4 +5,4 @@ class BaseHandler:
entry_string: str entry_string: str
async def entry_point(self, update, context) -> None: async def entry_point(self, update, context) -> None:
self.logger.info(f"Chat ({update.message.chat_id}) said: {self.entry_string}") self.logger.info(f"Chat said: {self.entry_string}")

View File

@@ -2,30 +2,28 @@ import datetime
import os import os
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
import models
ENTRY_OPTIONS, CONTENT_ENTRY = range(2)
BUTTON_COUNT = 5
from .basehandler import BaseHandler from .basehandler import BaseHandler
class JournalHandler(BaseHandler): class JournalHandler(BaseHandler):
def __init__(self, entry_string): def __init__(self, entry_string, models):
self.models = models
self.entry_string = entry_string self.entry_string = entry_string
self.handler = ConversationHandler( self.handler = ConversationHandler(
entry_points=[CommandHandler(entry_string, self.entry_point)], entry_points=[CommandHandler(entry_string, self.entry_point)],
states={ states={
ENTRY_OPTIONS: [ ACTION_CHOICE: [
CallbackQueryHandler(self.date_button, pattern=r"^\d{8}$"), # a serialized date CallbackQueryHandler(self.date_choice, pattern="today|yesterday"),
CallbackQueryHandler(self.date_custom, pattern=r"^\d{1,3}$"), # a ~ small delta, symbolizing a new range to show CallbackQueryHandler(self.date_custom, pattern="custom"),
CallbackQueryHandler(self.option_delete, pattern="delete"), CallbackQueryHandler(self.option_delete, pattern="delete")
],
DATE_ENTRY: [
MessageHandler(filters.ALL, self.date_entry), MessageHandler(filters.ALL, self.date_entry),
], ],
CONTENT_ENTRY: [ ADD_CONTENT: [
MessageHandler(filters.ALL, self.content_save), MessageHandler(filters.ALL, self.content_save),
], ]
}, },
fallbacks=[], fallbacks=[],
) )
@@ -39,83 +37,52 @@ class JournalHandler(BaseHandler):
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
dates = [(datetime.datetime.now() - datetime.timedelta(days = i)).date() for i in range(BUTTON_COUNT + 2)][::-1]
# since there are two buttons additional buttons, we need to have two more days
names = [d.strftime("%d.%m.") for d in dates]
callbacks = [d.strftime("%d%m%Y") for d in dates]
names[-1] = "Today"
names[-2] = "Yesterday"
options = [ options = [[
[ InlineKeyboardButton("Today", callback_data="today"),
InlineKeyboardButton(names[-1], callback_data=callbacks[-1]) InlineKeyboardButton("Yesterday", callback_data="yesterday"),
], InlineKeyboardButton("Custom date", callback_data="custom"),
[
InlineKeyboardButton(names[-2], callback_data=callbacks[-2])
],
[
InlineKeyboardButton(n, callback_data=c) for n,c in zip(names[:-2], callbacks[:-2])
],
[
InlineKeyboardButton("<<", callback_data=BUTTON_COUNT + 2)
], ],
[ [
InlineKeyboardButton("Delete", callback_data="delete") InlineKeyboardButton("Delete", callback_data="delete")
] ]
] ]
keyboard = InlineKeyboardMarkup(options) keyboard = InlineKeyboardMarkup(options)
await update.message.reply_text("Please choose a date \(or type it in the format _DDMMYYYY_\)", reply_markup=keyboard, parse_mode=ParseMode.MARKDOWN_V2) await update.message.reply_text("Please choose an option for the entry:", reply_markup=keyboard)
return ENTRY_OPTIONS return ACTION_CHOICE
async def date_button(self, update, context): async def date_choice(self, update, context):
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
date = datetime.datetime.strptime(query.data, "%d%m%Y").date() if query.data == "today":
date = datetime.datetime.now().date()
elif query.data == "yesterday":
date = datetime.datetime.now().date() - datetime.timedelta(days=1)
else:
raise ValueError("Invalid date choice")
with models.db: with self.models.db:
self.current_model, new = models.JournalEntry.get_or_create( self.current_model, new = self.models.JournalEntry.get_or_create(
date = date date = date
) )
if new: if new:
count = models.JournalEntry.select().count()
await query.edit_message_text( await query.edit_message_text(
text=f"Journal entry no. {count}. What happened on {self.current_model.date_pretty}?" text=f"What is your entry for {self.current_model.date_pretty}?"
) )
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 ADD_CONTENT
async def date_custom(self, update, context): async def date_custom(self, update, context):
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
delta = int(query.data) await query.edit_message_text(text="Please enter the date in the format DDMMYYYY")
return DATE_ENTRY
dates = [(datetime.datetime.now() - datetime.timedelta(days = i + delta)).date() for i in range(BUTTON_COUNT)][::-1]
names = [d.strftime("%d.%m.") for d in dates]
callbacks = [d.strftime("%d%m%Y") for d in dates]
options = [
[
InlineKeyboardButton(">>", callback_data=delta - BUTTON_COUNT)
],
[
InlineKeyboardButton(n, callback_data=c) for n,c in zip(names, callbacks)
],
[
InlineKeyboardButton("<<", callback_data=delta + BUTTON_COUNT)
],
[
InlineKeyboardButton("Delete", callback_data="delete")
]
]
keyboard = InlineKeyboardMarkup(options)
await query.edit_message_text("Please choose a date \(or type it in the format _DDMMYYYY_\)", parse_mode=ParseMode.MARKDOWN_V2, reply_markup=keyboard)
return ENTRY_OPTIONS
async def date_entry(self, update, context): async def date_entry(self, update, context):
date = update.message.text date = update.message.text
@@ -123,12 +90,12 @@ class JournalHandler(BaseHandler):
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")
return ENTRY_OPTIONS return DATE_ENTRY
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 self.models.db:
self.current_model = models.JournalEntry.get_or_none( self.current_model = self.models.JournalEntry.get_or_none(
date = date date = date
) )
if self.current_model: if self.current_model:
@@ -138,23 +105,20 @@ class JournalHandler(BaseHandler):
context.chat_data["delete"] = False context.chat_data["delete"] = False
return ConversationHandler.END return ConversationHandler.END
else: else:
with models.db: with self.models.db:
self.current_model, new = models.JournalEntry.get_or_create( self.current_model, new = self.models.JournalEntry.get_or_create(
date = date date = date
) )
if not new: if not new:
await update.message.reply_text("An entry already exists for this date") await update.message.reply_text("An entry already exists for this date")
return ConversationHandler.END return ConversationHandler.END
else: else:
count = models.JournalEntry.select().count() await update.message.reply_text(f"What is your entry for {self.current_model.date_pretty}?")
await update.message.reply_text( return ADD_CONTENT
text=f"Journal entry no. {count}. What happened on {self.current_model.date_pretty}?"
)
return CONTENT_ENTRY
async def content_save(self, update, context): async def content_save(self, update, context):
with models.db: with self.models.db:
self.current_model.author_id = update.message.from_user.id self.current_model.author_id = update.message.from_user.id
if update.message.text: if update.message.text:
@@ -170,7 +134,7 @@ class JournalHandler(BaseHandler):
self.current_model.save_media(file_bytes, file_path) self.current_model.save_media(file_bytes, file_path)
self.current_model.text = update.message.caption self.current_model.text = update.message.caption
self.current_model.save() self.current_model.save()
await update.message.reply_text(f"Saved entry ✅") await update.message.reply_text(f"Saved entry ✅")
@@ -180,13 +144,13 @@ class JournalHandler(BaseHandler):
async def option_delete(self, update, context): async def option_delete(self, update, context):
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
await query.edit_message_text(text="Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2) await query.edit_message_text(text="Please enter the date in the format DDMMYYYY")
context.chat_data["delete"] = True context.chat_data["delete"] = True
return ENTRY_OPTIONS return DATE_ENTRY
async def delete_entry(self, update, context): async def delete_entry(self, update, context):
with models.db: with self.models.db:
self.current_model.delete_instance() self.current_model.delete_instance()
context.chat_data["delete"] = False context.chat_data["delete"] = False
await update.message.reply_text(text="Entry deleted ✅") await update.message.reply_text(text="Entry deleted ✅")

View File

@@ -5,7 +5,10 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from .models import ListModel, set_db, db from .models import ListModel, set_db, db
PERSISTENCE_DIR = Path(os.getenv("PERSISTENCE_DIR")) MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
DB_DIR = MEDIA_DIR / "lists_db"
DB_DIR.mkdir(parents=True, exist_ok=True)
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE, ITEMTOGGLE = range(6) NAME, NEW, ACTION, ITEMADD, ITEMREMOVE, ITEMTOGGLE = range(6)
@@ -15,17 +18,16 @@ from ..basehandler import BaseHandler
class ListHandler(BaseHandler): class ListHandler(BaseHandler):
"""Create and edit lists""" """Create and edit lists"""
def __init__(self, entry_string): def __init__(self, entry_string, models):
self.journal_models = models # not needed here
self.entry_string = entry_string self.entry_string = entry_string
set_db(PERSISTENCE_DIR / "lists.sqlite")
self.list_overview_keyboard = [ self.list_overview_keyboard = [
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Add item", callback_data="add")], [InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Toggle item", callback_data="toggle")], [InlineKeyboardButton("Toggle item", callback_data="toggle")],
[InlineKeyboardButton("Remove item", callback_data="remove")], [InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")], [InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")], [InlineKeyboardButton("Delete list", callback_data="delete")],
] ]
@@ -38,13 +40,13 @@ class ListHandler(BaseHandler):
], ],
NEW : [MessageHandler(filters.TEXT, callback=self.new_listname)], NEW : [MessageHandler(filters.TEXT, callback=self.new_listname)],
ACTION: [ ACTION: [
CallbackQueryHandler(self.list_print, pattern="^print$"),
CallbackQueryHandler(self.list_add, pattern="^add$"), CallbackQueryHandler(self.list_add, pattern="^add$"),
CallbackQueryHandler(self.list_toggle, pattern="^toggle$"), CallbackQueryHandler(self.list_toggle, pattern="^toggle$"),
CallbackQueryHandler(self.list_menu, pattern="^overview$"),
CallbackQueryHandler(self.list_remove, pattern="^remove$"), CallbackQueryHandler(self.list_remove, pattern="^remove$"),
CallbackQueryHandler(self.list_clear, pattern="^clear$"), CallbackQueryHandler(self.list_clear, pattern="^clear$"),
CallbackQueryHandler(self.list_delete, pattern="^delete$"), CallbackQueryHandler(self.list_delete, pattern="^delete$"),
CallbackQueryHandler(self.list_print, pattern="^print$"),
CallbackQueryHandler(self.list_menu, pattern="^overview$"),
], ],
ITEMADD : [MessageHandler(filters.TEXT, callback=self.list_add_item)], ITEMADD : [MessageHandler(filters.TEXT, callback=self.list_add_item)],
ITEMTOGGLE: [CallbackQueryHandler(self.list_toggle_index)], ITEMTOGGLE: [CallbackQueryHandler(self.list_toggle_index)],
@@ -56,9 +58,10 @@ class ListHandler(BaseHandler):
async def entry_point(self, update, context) -> None: async def entry_point(self, update, context) -> None:
await super().entry_point(update, context) await super().entry_point(update, context)
set_db(DB_DIR / f"chat_{update.message.chat_id}.db")
with db: with db:
lists = ListModel.select().where(ListModel.chat_id == update.effective_chat.id) lists = ListModel.select()
keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.id}")] for k in lists] + \ keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.name}")] for k in lists] + \
[[InlineKeyboardButton("New list", callback_data="new")]] [[InlineKeyboardButton("New list", callback_data="new")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
@@ -69,13 +72,13 @@ class ListHandler(BaseHandler):
async def choose_list(self, update, context: CallbackContext) -> None: async def choose_list(self, update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
data = query.data data = query.data
id = data.replace("list-","") name = data.replace("list-","")
await query.answer() await query.answer()
context.user_data["current_list"] = ListModel.get(id = id) context.user_data["current_list"] = ListModel.get(name = name)
reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard) reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard)
await query.edit_message_text(f"Using {context.user_data['current_list'].name}. Available actions:", reply_markup=reply_markup) await query.edit_message_text("Very well. For " + name + " the following actions are available:", reply_markup=reply_markup)
return ACTION return ACTION
@@ -85,7 +88,7 @@ class ListHandler(BaseHandler):
reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard) reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard)
await query.edit_message_text(f"Using {context.user_data['current_list'].name}. Available actions:", reply_markup=reply_markup) await query.edit_message_text(f"Very well. For {context.user_data['current_list'].name} the following actions are available:", reply_markup=reply_markup)
return ACTION return ACTION
@@ -100,7 +103,7 @@ class ListHandler(BaseHandler):
name = update.message.text name = update.message.text
try: try:
with db: with db:
context.user_data["current_list"] = ListModel.create(name = name, chat_id=update.effective_chat.id) context.user_data["current_list"] = ListModel.create(name = name)
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup) await update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup)
@@ -122,17 +125,10 @@ class ListHandler(BaseHandler):
await query.answer() await query.answer()
list_object = context.user_data["current_list"] list_object = context.user_data["current_list"]
readable_it = printable_list(list_object) keyboard = [[InlineKeyboardButton(v, callback_data=k)] for k,v in list_object.content.items()]
reply_markup = InlineKeyboardMarkup(keyboard)
if readable_it:
msg_content = "Which item would you like to toggle?"
keyboard = [[InlineKeyboardButton(v, callback_data=k)] for k,v in zip(list_object.content.keys(), readable_it)]
reply_markup = InlineKeyboardMarkup(keyboard)
else:
msg_content = "List empty"
reply_markup = None
await query.edit_message_text(msg_content, reply_markup = reply_markup) await query.edit_message_text("Which item would you like to toggle?", reply_markup = reply_markup)
return ITEMTOGGLE return ITEMTOGGLE
@@ -174,10 +170,14 @@ class ListHandler(BaseHandler):
await query.answer() await query.answer()
list_object = context.user_data["current_list"] list_object = context.user_data["current_list"]
readable_it = printable_list(list_object) content_it = list_object.content.values()
done_it = [
if readable_it: "· " if e is None \
msg_content = "\n".join(readable_it) else "" if e \
else "" \
for e in list_object.done_dict.values()]
if content_it:
msg_content = "\n".join([f"{d} {c}" for d, c in zip(done_it, content_it)])
else: else:
msg_content = "List empty" msg_content = "List empty"
@@ -193,6 +193,7 @@ class ListHandler(BaseHandler):
new = list_object.content new = list_object.content
new.update({"random_key": item}) new.update({"random_key": item})
list_object.content = new list_object.content = new
# TODO test me!
keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"Added {item}", reply_markup=reply_markup) await update.message.reply_text(f"Added {item}", reply_markup=reply_markup)
@@ -205,10 +206,11 @@ class ListHandler(BaseHandler):
await query.answer() await query.answer()
list_object = context.user_data["current_list"] list_object = context.user_data["current_list"]
old = list_object.done_dict[toggle_key] or False old = list_object.done_dict[toggle_key]
# if it was previously unset (None), we can later on set it to not old = True # if all None or all False (first toggle or all false) then set all dones to False
if not any(list_object.done_dict.values()):
new_done_dict = list_object.done_dict new_done_dict = dict.fromkeys(list_object.done_dict, False)
else: new_done_dict = list_object.done_dict
new_done_dict[toggle_key] = not old new_done_dict[toggle_key] = not old
list_object.done_dict = new_done_dict list_object.done_dict = new_done_dict
@@ -234,22 +236,3 @@ class ListHandler(BaseHandler):
await query.edit_message_text(f"Removed {name}", reply_markup=reply_markup) await query.edit_message_text(f"Removed {name}", reply_markup=reply_markup)
return ACTION return ACTION
def printable_list(list_object: ListModel):
content_it = list_object.content.values()
done_bool_it = list_object.done_dict.values()
# distinguish the enumeration:
# either all done_dict values are None -> the list is not toggleable
# or at least one value is of type bool -> the list is toggleable and None === False
if any([type(e) == bool for e in done_bool_it]):
done_it = [
"" if e else "" \
for e in list_object.done_dict.values()
]
else:
done_it = ["-" for e in done_bool_it]
readable_it = [f"{d} {c}" for d, c in zip(done_it, content_it)]
return readable_it

View File

@@ -8,7 +8,6 @@ class BaseModel(Model):
class ListModel(BaseModel): class ListModel(BaseModel):
name = CharField(default="") name = CharField(default="")
chat_id = IntegerField()
@property @property
def content(self) -> dict: def content(self) -> dict:
@@ -46,6 +45,22 @@ class ListEntryModel(BaseModel):
done = BooleanField(default=None, null=True) done = BooleanField(default=None, null=True)
# class ListModel(BaseModel):
# name = CharField(unique=True)
# content = TextField(default="") # unlimited length, use to serialise list into
# @property
# def content_list(self):
# return json.loads(self.content or '[]')
# @content_list.setter
# def content_list(self, list_content):
# self.content = json.dumps(list_content)
# with db:
# self.save()
def set_db(db_path): def set_db(db_path):
db.initialize(SqliteDatabase(db_path)) db.initialize(SqliteDatabase(db_path))
with db: with db:

View File

@@ -1,8 +1,6 @@
import os import datetime
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler, CallbackContext from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler, CallbackContext
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto
import models
from telegram.constants import ParseMode
# ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3) # ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
MEMORY_CHOICE = range(1) MEMORY_CHOICE = range(1)
@@ -10,7 +8,8 @@ MEMORY_CHOICE = range(1)
from .basehandler import BaseHandler from .basehandler import BaseHandler
class MemoryHandler(BaseHandler): class MemoryHandler(BaseHandler):
def __init__(self, entry_string): def __init__(self, entry_string, models):
self.models = models
self.entry_string = entry_string self.entry_string = entry_string
self.handler = ConversationHandler( self.handler = ConversationHandler(
entry_points=[CommandHandler(entry_string, self.entry_point, )], entry_points=[CommandHandler(entry_string, self.entry_point, )],
@@ -28,27 +27,20 @@ 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):
await update.message.reply_text("You are not authorized to use this bot")
return ConversationHandler.END
search_string = " ".join(context.args) search_string = " ".join(context.args)
if search_string == '~photo': if search_string == '~photo':
matching_models = models.JournalEntry.select().where(models.JournalEntry.media_path != "").order_by(models.JournalEntry.date) matching_models = self.models.JournalEntry.select().where(self.models.JournalEntry.media_path != "").order_by(self.models.JournalEntry.date)
else: # searching for text else: # searching for text
matching_models = models.JournalEntry.select().where( matching_models = self.models.JournalEntry.select().where(
models.JournalEntry.text.contains( self.models.JournalEntry.text.contains(
search_string search_string
) )
).order_by(models.JournalEntry.date) ).order_by(self.models.JournalEntry.date)
# exit if no memory matches the string
if len(matching_models) == 0:
await update.message.reply_text(f"There is no matching memory yet.")
return ConversationHandler.END
options = [[InlineKeyboardButton(m.date_pretty, callback_data=i)] for i,m in enumerate(matching_models)] options = [[InlineKeyboardButton(m.date_pretty, callback_data=i)] for i,m in enumerate(matching_models)]
keyboard = InlineKeyboardMarkup(options) keyboard = InlineKeyboardMarkup(options)
@@ -76,15 +68,13 @@ class MemoryHandler(BaseHandler):
caption= caption=
f"On {chosen_match.date_pretty}, " f"On {chosen_match.date_pretty}, "
f"{chosen_match.author} wrote: \n" f"{chosen_match.author} wrote: \n"
f"{chosen_match.spoiler_text}", f"{chosen_match.text}"
parse_mode=ParseMode.HTML
) )
else: else:
await query.edit_message_text( await query.edit_message_text(
f"On {chosen_match.date_pretty}, " f"On {chosen_match.date_pretty}, "
f"{chosen_match.author} wrote: \n" f"{chosen_match.author} wrote: \n"
f"{chosen_match.spoiler_text}", f"{chosen_match.text}"
parse_mode=ParseMode.HTML
) )
return ConversationHandler.END return ConversationHandler.END

View File

@@ -4,16 +4,18 @@ import socket
from telegram.ext import ConversationHandler, CommandHandler, CallbackQueryHandler from telegram.ext import ConversationHandler, CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode from telegram.constants import ParseMode
import os
FIRST = 1 FIRST = 1
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, models):
self.start_time = datetime.datetime.now() self.start_time = datetime.datetime.now()
self.entry_string = entry_string self.entry_string = entry_string
self.models = models
self.handler = ConversationHandler( self.handler = ConversationHandler(
entry_points=[CommandHandler(self.entry_string, self.entry_point)], entry_points=[CommandHandler(self.entry_string, self.entry_point)],
states={ states={
@@ -48,11 +50,9 @@ class StatusHandler(BaseHandler):
local_ips = "not fetchable" local_ips = "not fetchable"
message += "Status: Running 🟢\n" message += "Status: Running 🟢\n"
message += f"Version: `{os.getenv('BOT_VERSION', 'dev')}`\n"
message += f"Uptime: `{delta[:delta.rfind('.')]}`\n" message += f"Uptime: `{delta[:delta.rfind('.')]}`\n"
message += f"IP \(public\): `{ip}`\n" message += f"IP \(public\): `{ip}`\n"
message += f"IP \(private\): `{local_ips}`\n" message += f"IP \(private\): `{local_ips}`\n"
message += f"Chat ID: `{update.effective_chat.id}`\n"
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

@@ -1,6 +1,7 @@
import os import os
from pathlib import Path from pathlib import Path
from telegram.ext import MessageHandler, filters from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram import Update from telegram import Update
import re import re
import random import random
@@ -17,47 +18,37 @@ TURTLE_VIDEOS = list(TURTLE_VIDEO_LOCATION.glob("*.mp4"))
class TurtleHandler(BaseHandler): class TurtleHandler(BaseHandler):
def __init__(self): def __init__(self):
self.entry_string = "Variation of hallo" self.entry_string = "Variation of hallo"
self.handler = MessageHandler( self.handler = MessageHandler(filters.Regex(r"[hH]([aA]+|[eE]+)[lL]{2,}[oOöÖ]+(le)?|(chen)") | # react to hello strings
filters.Regex(r"[hH]([aA]+|[eE]+)[lL]{2,}[oOöÖ]+(le|chen)?") | filters.Regex(b"\xF0\x9F\x90\xA2".decode("utf8")) | # react to turtle emoji
# react to hello strings filters.Regex(r"[sS](childkröte)|[tT](urtle)"), # react to turtle string
filters.Regex(b"\xF0\x9F\x90\xA2".decode("utf8")) | self.entry_point)
# react to turtle emoji pass
filters.Regex(r"[sS](childkröte)|[tT](urtle)"),
# react to turtle string
self.entry_point
)
async def entry_point(self, update: Update, context): async def entry_point(self, update: Update, context):
await super().entry_point(update, context) await super().entry_point(update, context)
msgtxt = update.message.text msgtxt = update.message.text
turtle_emoji = b"\xF0\x9F\x90\xA2".decode("utf8") turtle_emoji = b"\xF0\x9F\x90\xA2".decode("utf8")
if "hallo" in msgtxt: if "hallo" in msgtxt: # react to hallo
# react to hallo
vid = TURTLE_VIDEOS[0] vid = TURTLE_VIDEOS[0]
answertxt = "Hallo!" answertxt = "Hallo!"
elif re.search("[eE][lL]{2,}[oO]", msgtxt): elif re.search("[eE][lL]{2,}[oO]", msgtxt): # react to hello
# react to hello
vid = TURTLE_VIDEOS[2] vid = TURTLE_VIDEOS[2]
answertxt = "Hello!" answertxt = "Hello!"
elif re.search("([aA]{4,}|[lL]{4,}|[oO]{4,}|[öÖ]{4,})", msgtxt): elif re.search("([aA]{4,}|[lL]{4,}|[oO]{4,}|[öÖ]{4,})", msgtxt): # react to stretched hello
# react to stretched hello
vid = TURTLE_VIDEOS[5] vid = TURTLE_VIDEOS[5]
answertxt = "That's a lot of letters!" answertxt = "That's a lot of letters!"
elif re.search(turtle_emoji, msgtxt): elif re.search(turtle_emoji, msgtxt): # react to turtle emoji
# react to turtle emoji vid=TURTLE_VIDEOS[0] # TODO: choose video for smiley reaction
vid=TURTLE_VIDEOS[0]
answertxt="Turtle detected! Self-destruction mode activated..." answertxt="Turtle detected! Self-destruction mode activated..."
elif re.search("[sS](childkröte)|[tT](urtle)", msgtxt): elif re.search("[sS](childkröte)|[tT](urtle)", msgtxt): # react to turtle string
# react to turtle string
vid=None vid=None
answertxt=turtle_emoji answertxt=turtle_emoji
else: else:
vid = random.choice(TURTLE_VIDEOS[1:2]+TURTLE_VIDEOS[3:5]+TURTLE_VIDEOS[6:]) vid = random.choice(TURTLE_VIDEOS[1:2]+TURTLE_VIDEOS[3:5]+TURTLE_VIDEOS[6:])
answertxt = "" answertxt = ""
if vid != None: if vid!=None:
if re.search(turtle_emoji, msgtxt): if re.search(turtle_emoji, msgtxt):
await update.message.reply_text(text=answertxt) await update.message.reply_text(text=answertxt)
time.sleep(1) time.sleep(1)

View File

@@ -1,45 +1,22 @@
import os import os
from pathlib import Path
from telegram.ext import ExtBot from telegram.ext import ExtBot
from telegram.error import BadRequest import random
import logging
from datetime import time, timedelta, timezone, datetime, date
from peewee import fn
import models
MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
CHAT_ID = os.getenv("CHAT_ID") CHAT_ID = os.getenv("CHAT_ID")
class SetChatPhotoJob(): async def set_random(bot: ExtBot) -> None:
def __init__(self, bot: ExtBot, job_queue): """Set a random chat photo."""
self.bot = bot if os.getenv("DOCKERIZED", "false") == "false":
self.logger = logging.getLogger(self.__class__.__name__) # only change image on prod
return
if os.getenv("DOCKERIZED", "false") != "true":
# when running locally, annoy the programmer every 60 seconds <3 photos = list(MEDIA_DIR.glob("*.jpg")) + list(MEDIA_DIR.glob("*.png")) + list(MEDIA_DIR.glob("*.jpeg"))
job_queue.run_repeating(self.callback_photo, interval=60)
else: if len(photos) == 0:
# set the message sending time; include UTC shift +2 return
sending_time = time(hour=12, minute=0, second=0, tzinfo=timezone(timedelta(hours=2)))
job_queue.run_monthly(self.callback_photo, when=sending_time, day=-1)
photo = random.choice(photos)
async def callback_photo(self, context): await bot.set_chat_photo(CHAT_ID, photo)
# last_seen of memory must be older than 10 days in past or None
with models.db:
possible_photos = models.JournalEntry.select().where(
models.JournalEntry.media_path != None
).order_by(fn.Random())
try:
chosen_entry = possible_photos.get()
except:
self.logger.warning("No photos available.")
return
chat_id = os.getenv("CHAT_ID")
try:
await self.bot.set_chat_photo(chat_id, chosen_entry.media_path)
except BadRequest:
self.logger.error("This is a private chat!")
return

View File

@@ -1,64 +0,0 @@
from datetime import time, timedelta, timezone, datetime, date
from telegram.constants import ParseMode
import os
from peewee import fn
import logging
import models
class RandomMemoryJob():
def __init__(self, bot, job_queue):
self.bot = bot
self.logger = logging.getLogger(self.__class__.__name__)
if os.getenv("DOCKERIZED", "false") != "true":
# when running locally, annoy the programmer every 60 seconds <3
job_queue.run_repeating(self.callback_memory, interval=60)
self.min_age = 0 # do not filter messages: show them all
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_memory, sending_time)
self.min_age = 30 # days
async def callback_memory(self, context):
# last_seen of memory must be older than 10 days in past or None
with models.db:
possible_entries = models.JournalEntry.select().where(
(models.JournalEntry.last_shown <= datetime.today().date() - timedelta(days=self.min_age)) | \
(models.JournalEntry.last_shown == None)
).order_by(fn.Random())
try:
chosen_entry = possible_entries.get()
except:
self.logger.warning("Come back later for another memory.")
return
# update the last_shown of the chosen entry
chosen_entry.last_shown = datetime.today().date()
chosen_entry.save()
chat_id = os.getenv("CHAT_ID")
if chosen_entry.media_path:
await self.bot.send_photo(
chat_id = chat_id,
photo = chosen_entry.media_path,
caption =
f"On {chosen_entry.date_pretty}, "
f"{chosen_entry.author} wrote: \n"
f"{chosen_entry.spoiler_text}",
parse_mode=ParseMode.HTML
)
else:
await self.bot.send_message(
chat_id = chat_id,
text =
f"On {chosen_entry.date_pretty}, "
f"{chosen_entry.author} wrote: \n"
f"{chosen_entry.spoiler_text}",
parse_mode=ParseMode.HTML
)

View File

@@ -3,15 +3,16 @@ from telegram.ext import Application
import logging import logging
import models import models
from commands import journal, status, turtle, memory 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
logging.basicConfig( logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=logging.INFO level=logging.INFO
) )
logging.getLogger("httpx").setLevel(logging.WARNING) import asyncio
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,15 +24,22 @@ def main() -> None:
models.set_db(db_path) models.set_db(db_path)
application = Application.builder().token(token).build() application = Application.builder().token(token).build()
application.add_handler(journal.JournalHandler("journal").handler)
application.add_handler(list.ListHandler("list").handler) application.add_handler(journal.JournalHandler("journal", models).handler)
application.add_handler(status.StatusHandler("status").handler) application.add_handler(list.ListHandler("list", models).handler)
application.add_handler(status.StatusHandler("status", models).handler)
application.add_handler(turtle.TurtleHandler().handler) application.add_handler(turtle.TurtleHandler().handler)
application.add_handler(memory.MemoryHandler("memory").handler) application.add_handler(memory.MemoryHandler("memory", models).handler)
application.add_handler(advent.AdventsHandler("advent").handler)
random_memory.RandomMemoryJob(application.bot, application.job_queue) # application.add_handler(CommandHandler("help", help_command))
chat_photo.SetChatPhotoJob(application.bot, application.job_queue) # on non command i.e message - echo the message on Telegram
# application.add_handler(InlineQueryHandler(inline_query))
# on every start set a new chat photo
# loop = asyncio.get_event_loop()
asyncio.ensure_future(chat_photo.set_random(application.bot))
# 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

@@ -1,6 +1,5 @@
from peewee import * from peewee import *
from pathlib import Path from pathlib import Path
import re
import os import os
import datetime import datetime
@@ -61,25 +60,6 @@ class JournalEntry(BaseModel):
except ValueError: #fck windows except ValueError: #fck windows
return self.date.strftime('%a, %d. %b %Y') return self.date.strftime('%a, %d. %b %Y')
@property
def spoiler_text(self) -> str:
"""Returns the text with all the frisky details hidden away"""
new_text = self.text.replace("<", "&lt;").replace(">", "&gt;").replace("&", "&amp;")
pattern = re.compile(
"("
"(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence
"([^\.\!\?])+" # any character being part of a sentence
"((\:\))|😇|😈)" # the smiley
"([^\.\!\?])*" # continuation of sentence
"(\.|\!|\?|\,|$)" # end of the sentence
")"
)
matches = pattern.findall(new_text)
for match in matches:
group_to_replace = match[0]
new_text = new_text.replace(group_to_replace, f"<tg-spoiler>{group_to_replace}</tg-spoiler>")
return new_text
def set_db(db_path): def set_db(db_path):
db.initialize(SqliteDatabase(db_path)) db.initialize(SqliteDatabase(db_path))

View File

@@ -1,11 +1,12 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
namespace: journal
name: journal-bot name: journal-bot
labels: labels:
app: journal-bot app: journal-bot
spec: spec:
# deployment running a single container # deployment running a single container
selector: selector:
matchLabels: matchLabels:
app: journal-bot app: journal-bot
@@ -17,7 +18,8 @@ spec:
spec: spec:
containers: containers:
- name: journal - name: journal
image: mollre/journal-bot:1.0.19 image: mollre/journal-bot:latest
imagePullPolicy: Always
envFrom: envFrom:
- secretRef: - secretRef:
name: journal-secret-env name: journal-secret-env
@@ -37,7 +39,10 @@ spec:
apiVersion: v1 apiVersion: v1
kind: PersistentVolume kind: PersistentVolume
metadata: metadata:
namespace: journal
name: "journal-data-nfs" name: "journal-data-nfs"
# labels:
# directory: "journal-data"
spec: spec:
storageClassName: fast storageClassName: fast
capacity: capacity:
@@ -47,10 +52,12 @@ spec:
nfs: nfs:
path: /export/kluster/journal-bot path: /export/kluster/journal-bot
server: 192.168.1.157 server: 192.168.1.157
--- ---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
namespace: journal
name: "journal-data-nfs" name: "journal-data-nfs"
spec: spec:
storageClassName: "fast" storageClassName: "fast"
@@ -59,3 +66,7 @@ spec:
resources: resources:
requests: requests:
storage: "5Gi" storage: "5Gi"
# selector:
# matchLabels:
# directory: "journal-data"

View File

@@ -1,9 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespace.yaml
- ./deployment.yaml
- ./sealedsecret.yaml
images:
- name: mollre/journal-bot
newTag: 1.0.58

View File

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

View File

@@ -1,25 +0,0 @@
{
"kind": "SealedSecret",
"apiVersion": "bitnami.com/v1alpha1",
"metadata": {
"name": "journal-secret-env",
"namespace": "journal",
"creationTimestamp": null
},
"spec": {
"template": {
"metadata": {
"name": "journal-secret-env",
"namespace": "journal",
"creationTimestamp": null
},
"type": "Opaque"
},
"encryptedData": {
"BOT_TOKEN": "AgBcFTg4wRI37/sXlk7bNO3IB9dC2CaVi/Oh9TQeQ/wN+rRkTTqp/dBpzX3Y1Zcp16d59AqT7y3DafGGZ/V87zcxG1bngdCZUsZfDmZMP0+z+10caMxjbSY4xfBW1/MEL3rW6ONOibhTAI9DDS3p3YCu1V92xRLvOUkwc+mCkV7fneWWGU/wgeci+C75PKTyGIilo5NZXROOyytN7BQwvOiY70j9m/NC9L0Ulbcppho9iuVsYVfkkWHOU6/OcOeL7vjWcvYFTleyI0oclLgtBUSJzBTqe5eJeZPGoVWMGwyMw2BqR8DgeGpDIuSnMEgssh9wUlhVvqkoI7CEUrJy5Rb2YnWcriIbfUfUMwbfc4EpBKt1VVlQcEQmN9jJdoOJ81ywRXl6CJTMMM4apB3iHsBWdzVXCG4I5c6Mv8+xg0V+AYdDt4pGwuRX9s66LShnjFJKnn68chNLnGfNlU68YdwFio0GJkV0/FnXIgTrOwdOovtBz1Gl3ORIWTmSkY7yBRyYBzvzEBlXBAuj51yhdykrmuW6B5CvwqXsm3ia1fvtWwNfvmKySjzbHZQHmbbYR9hLvWm+rPS7TFXw52W6jUyvHh6U2mSHwpwI2byIE+uhXjVQgYbmDgJW2gif3Aam+2VSaGSqWUz7ECSGy4mVC755CyoZl0HDP4PxuHq5kcGm34qmjTRNYM6Y2QvaVb7cBplsZfs+cH+gcfVu2gg3KvAEaFJk85Hz4pirRhcPsLNce6Iw5OegCZ5IQBrzOJXC",
"CHAT_ID": "AgDIgpsygMIcsTDy8a49isS4Hfkmqa0oav+q7Mu7VtcPyrZ4o7hR1u/IlH8Qt4Cg/9QxOw7rJ4DfbK4GDmiOO1oOf0uaR6btLl+/GoKT3mbSHusWfHPrJDGX0SBFw7rOopC+LyFgDHPJEhbKviwnyrBkUuI6gnf1sic4jJ9arb/B97y89dMKFlVCbEzRCrTCK6WDBQ3Lpk+5MI+ugAPSKC8CqsjNc6jmWymdGMk/9n3sAdalfYBCucxHKeVgkrv4sPr9jEUEzIziKansavTs8qVbZgSUMEAAob2KBIAXLcRmo5ISwKvppuA6DMbbXEYEMHVJH9B4gI2eAxClOPVEOBElL+BtsJaSJnJbVEclMzOqwxXQRFPOq4BKxhguA+Uj8Vl8/2diwXEJoUiCZ5emGVvCFQd+Dr2LUPj4AO1AL4zAg+VahuqNV2gI/Dkxgt7Hj+i6jY/jmbk3MIJYjeZh0irmfsWQmUMcmizhxutQdV3kXhHSlomVDHuIdFHFIbjQrI8vSgeysQARSxrJZvt/qeNUNnD1InKa/EQ8I5XDX4o4qIV/pqY8XLVoTcciYDOPZEy3OleHK+26SJDkJOiDOAHbfIBeinaLvYIEW3BwgrakBD16HaNzYBPLPW2ikDCSBTyFRayvfkWHHUGawhdrauxvZzp5UsJViZogypBJuDT3SPvAOlAR4X8Yfr9SmwQYKv8rALH+wW7QtON6R9D7UA==",
"DB_PATH": "AgACucJGoBiO/ymyf6FvEeuQ2MDo+c+VgNk30xY2EZSQZpAbNE6VbaMO0lZj++T82OOBnhfmXID+TWJTIwIHaD2nkPq/ISppBIobVmtUmsR+sm76tao02HJCtPGycyCu1zhEpKtwy3k5nsd0jclq4bQFHccnBaBdZ6xcvmevvJ+YddysHhjSb6ESOoah/5lGiAa3sHe1Hwg57FPZeVuZOCx+MbbmhAXCYbu2bdZzSWA/mFAf1F6qkxSAuVFMJtijPrM13UIVONBPg5E02NO1VEV+LkI5NTUNx9YHVlGNPxogabDq+lhNwulWtLPbKkAXn/CTgcB2vb03geeVN20yz1UzbrdV5CKpIGGZ1At8ehuNypFa595XBVFSmL8RWNmfHCAvypAypjxOMWa89qC0diJW4tY+BUrl2jrIXpARNlD2GTqp7InLtwFsFb3AUbpg+0mnqqiqmtqYKQWJj0WDvfs7ol6k6Qx7P0FdAfRq3ojzArRQ7MruI24CS1hGTQ/hBJaHrcMzEIn/ZkKqZ2fEdNkJt8tTfxVsZ/paE97ERfYeKsuK2uHqUgvSQ+0w7FK7m6PYNpY9gmqZMaqRA7VIRblNauEuLDAGw8Jgyb3YUiV3xLjBuvaCaAEbXOsNsIF35+ZCw2bsYmgSxqNO2b1LvW/uFSUonjVJ9yT9+Qv6V0YJhMztzDLpNKRIEBKgyUN/BE7hV9vtygaan/AkUVmHVA9BwrU=",
"PERSISTENCE_DIR": "AgDF8D5PfJ4DF7wNd8hpEPdph3r/MvK8R/sREX83/b7jFJgSGb/Dku82vwtkDTzsk9A+gGgU//EILYt5Irlo5ObHlcKfimR4fTvdcO9lpBHJnbwXipUcg20Xz/awIGbD3yJmy0LJqgc5MVPtWicF/ZsxQkkcv3+DN4+BgXczncEL+3g55eEbCAwbmFSvdHWIZGbi04VmtmAlSduPVzi7nqH05Nslqtu4p5mZaxPHjGvna0DcIVQNFQC/Wgobox8pezeJ6tANUPFAUpJUp+E5N3q8DofZXiHBQTpNXo3tyM2JYT/IwovSIdPSgJeIhbrfP6hVKOeZjKqaC8/SV67R/1LUpdO7KeKig5d7LJa3vjeeDmrM/3+1vb0C96Xrgv39j4MyPx7HrZxFdOQif4PZlpLEfarEtuSFUAUXx4N2uhLbTyAXyl4dfGkqdbQ5O/UT6xxXw44JsK6DzOz5OkT6cB0uUvJa5TrHk+cVoxlUu3Ex/5o6KXnMFaxfzyizPXeiIs/mTT9Bq6nAtvGad84U6Wvua3ZPofOlH0gFyN8/uMJxwqXbKuO1iScjkxuCNX8YRFzcoWH7VXzbbNPIQLORTu9/PhAIRZXOSxZw2iPZVg3LKuyyW3MgTtnVvgrKYnynw1Yrvc7gu35MhwjvnRrPlVQ+yFrZuB3l2Cf0OWZwonlMr36TsPHIJq+wD4ZEja+ciOsRAWzWuxBEDQ=="
}
}
}