Compare commits

..

No commits in common. "main" and "secretbranch" have entirely different histories.

23 changed files with 222 additions and 511 deletions

View File

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

9
.vscode/launch.json vendored
View File

@ -4,6 +4,15 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Python: Current project",
"type": "python",

View File

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

View File

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

77
Pipfile.lock generated
View File

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

@ -1,9 +1,3 @@
# journal-bot
Sharing memories, the digital way...
## Migration 10.03.24
```
ALTER TABLE journalentry ADD COLUMN rating INTEGER;
```
Sharing memories, the digital way...

View File

@ -2,20 +2,11 @@ from datetime import date
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler, CallbackContext
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto
import random
import os
from pathlib import Path
# ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
ACTION, TUERCHEN_CHOICE = range(2)
from .basehandler import BaseHandler
MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
GIF_LOCATION = MEDIA_DIR / "advent" / "gifs"
# GIFS = list(GIF_LOCATION.glob("*.mp4"))
STICKER_LOCATION = MEDIA_DIR / "advent" / "stickers"
# STICKERS = list(STICKER_LOCATION.glob("*.tgs"))
PICTURE_LOCATION = MEDIA_DIR / "advent" / "pretty_pictures"
PICTURES = list(PICTURE_LOCATION.glob("*.jpg"))
from .basehandler import BaseHandler
class AdventsHandler(BaseHandler):
def __init__(self, entry_string):
@ -75,11 +66,12 @@ class AdventsHandler(BaseHandler):
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=STICKER_LOCATION/"stickerwhat.tgs")
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()
@ -87,7 +79,7 @@ class AdventsHandler(BaseHandler):
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=STICKER_LOCATION/"stickerkiss.tgs")
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
@ -109,8 +101,7 @@ class AdventsHandler(BaseHandler):
reply_markup = InlineKeyboardMarkup(keyboard)
picture_number = random.randint(1,31)
#print(picture_number-1)
await update.effective_message.reply_photo(photo=PICTURES[picture_number-1], caption="So ein cutes Foto!", reply_markup=reply_markup)
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
@ -125,11 +116,11 @@ class AdventsHandler(BaseHandler):
reply_markup = InlineKeyboardMarkup(keyboard)
datum_tuer_heute = date(2023, 12, tuer_nummer)
if (date.today() - datum_tuer_heute).days >= 0:
await update.message.reply_document(document=GIF_LOCATION/ f"gif{tuer_nummer}.mp4", caption=f"Türchen für den {tuer_nummer}. Dezember: \n" + self.tuerchen_texte[tuerchen], reply_markup=reply_markup)
#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=STICKER_LOCATION/"stickerangry.tgs")
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
@ -138,7 +129,7 @@ class AdventsHandler(BaseHandler):
query = update.callback_query
await query.answer()
await update.effective_message.reply_sticker(sticker=STICKER_LOCATION/"stickerbye.tgs")
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
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,33 +2,28 @@ import datetime
import os
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode
import models
ENTRY_OPTIONS, CONTENT_ENTRY, DAY_RATING = range(3)
BUTTON_COUNT = 5
ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
from .basehandler import BaseHandler
class JournalHandler(BaseHandler):
def __init__(self, entry_string):
def __init__(self, entry_string, models):
self.models = models
self.entry_string = entry_string
self.handler = ConversationHandler(
entry_points=[CommandHandler(entry_string, self.entry_point)],
states={
ENTRY_OPTIONS: [
CallbackQueryHandler(self.date_button, pattern=r"^\d{8}$"), # a serialized date
CallbackQueryHandler(self.date_custom, pattern=r"^\d{1,3}$"), # a ~ small delta, symbolizing a new range to show
CallbackQueryHandler(self.option_delete, pattern="delete"),
ACTION_CHOICE: [
CallbackQueryHandler(self.date_choice, pattern="today|yesterday"),
CallbackQueryHandler(self.date_custom, pattern="custom"),
CallbackQueryHandler(self.option_delete, pattern="delete")
],
DATE_ENTRY: [
MessageHandler(filters.ALL, self.date_entry),
],
CONTENT_ENTRY: [
ADD_CONTENT: [
MessageHandler(filters.ALL, self.content_save),
],
DAY_RATING: [
CallbackQueryHandler(self.day_rating_save),
],
]
},
fallbacks=[],
)
@ -42,74 +37,52 @@ class JournalHandler(BaseHandler):
await update.message.reply_text("You are not authorized to use this bot")
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 = get_names(dates)
callbacks = [d.strftime("%d%m%Y") for d in dates]
options = [
[InlineKeyboardButton(n, callback_data=c)] for n,c in zip(names[::-1], callbacks[::-1])
] + [
[
InlineKeyboardButton("<<", callback_data=BUTTON_COUNT + 2)
options = [[
InlineKeyboardButton("Today", callback_data="today"),
InlineKeyboardButton("Yesterday", callback_data="yesterday"),
InlineKeyboardButton("Custom date", callback_data="custom"),
],
[
InlineKeyboardButton("Delete", callback_data="delete")
]
]
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)
return ENTRY_OPTIONS
await update.message.reply_text("Please choose an option for the entry:", reply_markup=keyboard)
return ACTION_CHOICE
async def date_button(self, update, context):
async def date_choice(self, update, context):
query = update.callback_query
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:
self.current_model, new = models.JournalEntry.get_or_create(
with self.models.db:
self.current_model, new = self.models.JournalEntry.get_or_create(
date = date
)
if new:
count = models.JournalEntry.select().count()
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:
await query.edit_message_text(text="An entry already exists for this date")
return ConversationHandler.END
return CONTENT_ENTRY
return ADD_CONTENT
async def date_custom(self, update, context):
query = update.callback_query
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 = get_names(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[::-1], callbacks[::-1])
] + [
[
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):
date = update.message.text
@ -117,12 +90,12 @@ class JournalHandler(BaseHandler):
try:
date = datetime.datetime.strptime(date, "%d%m%Y").date()
except ValueError:
await update.message.reply_text("Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2)
return ENTRY_OPTIONS
await update.message.reply_text("Please enter the date in the format DDMMYYYY")
return DATE_ENTRY
if context.chat_data.get("delete", False): # if not set, delete was not chosen
with models.db:
self.current_model = models.JournalEntry.get_or_none(
with self.models.db:
self.current_model = self.models.JournalEntry.get_or_none(
date = date
)
if self.current_model:
@ -132,23 +105,20 @@ class JournalHandler(BaseHandler):
context.chat_data["delete"] = False
return ConversationHandler.END
else:
with models.db:
self.current_model, new = models.JournalEntry.get_or_create(
with self.models.db:
self.current_model, new = self.models.JournalEntry.get_or_create(
date = date
)
if not new:
await update.message.reply_text("An entry already exists for this date")
return ConversationHandler.END
else:
count = models.JournalEntry.select().count()
await update.message.reply_text(
text=f"Journal entry no. {count}. What happened on {self.current_model.date_pretty}?"
)
return CONTENT_ENTRY
await update.message.reply_text(f"What is your entry for {self.current_model.date_pretty}?")
return ADD_CONTENT
async def content_save(self, update, context):
with models.db:
with self.models.db:
self.current_model.author_id = update.message.from_user.id
if update.message.text:
@ -164,58 +134,23 @@ class JournalHandler(BaseHandler):
self.current_model.save_media(file_bytes, file_path)
self.current_model.text = update.message.caption
self.current_model.save()
options = [
[InlineKeyboardButton(models.RATING_MAPPING[idx], callback_data=idx) for idx in [1,2,3,4,5]]
]
await update.message.reply_text(f"Saved entry ✅. How was the day?", reply_markup=InlineKeyboardMarkup(options))
return DAY_RATING
async def day_rating_save(self, update, context):
query = update.callback_query
await query.answer()
rating = int(query.data)
with models.db:
self.current_model.rating = rating
self.current_model.save()
await query.edit_message_text(text="Rating saved ✅")
await update.message.reply_text(f"Saved entry ✅")
return ConversationHandler.END
async def option_delete(self, update, context):
query = update.callback_query
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
return ENTRY_OPTIONS
return DATE_ENTRY
async def delete_entry(self, update, context):
with models.db:
with self.models.db:
self.current_model.delete_instance()
context.chat_data["delete"] = False
await update.message.reply_text(text="Entry deleted ✅")
### HELPERS
def get_names(dates: list):
names = []
for d in dates:
suffix = ""
if models.JournalEntry.get_or_none(date = d):
suffix = ""
if d == datetime.datetime.now().date():
names.append("Today" + suffix)
elif d == datetime.datetime.now().date() - datetime.timedelta(days = 1):
names.append("Yesterday" + suffix)
else:
names.append(d.strftime("%d.%m.") + suffix)
return names
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
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)
@ -15,17 +18,16 @@ from ..basehandler import BaseHandler
class ListHandler(BaseHandler):
"""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
set_db(PERSISTENCE_DIR / "lists.sqlite")
self.list_overview_keyboard = [
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Toggle item", callback_data="toggle")],
[InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")],
]
@ -38,13 +40,13 @@ class ListHandler(BaseHandler):
],
NEW : [MessageHandler(filters.TEXT, callback=self.new_listname)],
ACTION: [
CallbackQueryHandler(self.list_print, pattern="^print$"),
CallbackQueryHandler(self.list_add, pattern="^add$"),
CallbackQueryHandler(self.list_toggle, pattern="^toggle$"),
CallbackQueryHandler(self.list_menu, pattern="^overview$"),
CallbackQueryHandler(self.list_remove, pattern="^remove$"),
CallbackQueryHandler(self.list_clear, pattern="^clear$"),
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)],
ITEMTOGGLE: [CallbackQueryHandler(self.list_toggle_index)],
@ -56,9 +58,10 @@ class ListHandler(BaseHandler):
async def entry_point(self, update, context) -> None:
await super().entry_point(update, context)
set_db(DB_DIR / f"chat_{update.message.chat_id}.db")
with db:
lists = ListModel.select().where(ListModel.chat_id == update.effective_chat.id)
keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.id}")] for k in lists] + \
lists = ListModel.select()
keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.name}")] for k in lists] + \
[[InlineKeyboardButton("New list", callback_data="new")]]
reply_markup = InlineKeyboardMarkup(keyboard)
@ -69,13 +72,13 @@ class ListHandler(BaseHandler):
async def choose_list(self, update, context: CallbackContext) -> None:
query = update.callback_query
data = query.data
id = data.replace("list-","")
name = data.replace("list-","")
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)
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
@ -85,7 +88,7 @@ class ListHandler(BaseHandler):
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
@ -100,7 +103,7 @@ class ListHandler(BaseHandler):
name = update.message.text
try:
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")]]
reply_markup = InlineKeyboardMarkup(keyboard)
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()
list_object = context.user_data["current_list"]
readable_it = printable_list(list_object)
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
keyboard = [[InlineKeyboardButton(v, callback_data=k)] for k,v in list_object.content.items()]
reply_markup = InlineKeyboardMarkup(keyboard)
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
@ -174,10 +170,14 @@ class ListHandler(BaseHandler):
await query.answer()
list_object = context.user_data["current_list"]
readable_it = printable_list(list_object)
if readable_it:
msg_content = "\n".join(readable_it)
content_it = list_object.content.values()
done_it = [
"· " if e is None \
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:
msg_content = "List empty"
@ -193,6 +193,7 @@ class ListHandler(BaseHandler):
new = list_object.content
new.update({"random_key": item})
list_object.content = new
# TODO test me!
keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"Added {item}", reply_markup=reply_markup)
@ -205,10 +206,11 @@ class ListHandler(BaseHandler):
await query.answer()
list_object = context.user_data["current_list"]
old = list_object.done_dict[toggle_key] or False
# if it was previously unset (None), we can later on set it to not old = True
new_done_dict = list_object.done_dict
old = list_object.done_dict[toggle_key]
# 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 = dict.fromkeys(list_object.done_dict, False)
else: new_done_dict = list_object.done_dict
new_done_dict[toggle_key] = not old
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)
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):
name = CharField(default="")
chat_id = IntegerField()
@property
def content(self) -> dict:
@ -46,6 +45,22 @@ class ListEntryModel(BaseModel):
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):
db.initialize(SqliteDatabase(db_path))
with db:

View File

@ -1,8 +1,6 @@
import os
import datetime
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler, CallbackContext
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto
import models
from telegram.constants import ParseMode
# ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3)
MEMORY_CHOICE = range(1)
@ -10,7 +8,8 @@ MEMORY_CHOICE = range(1)
from .basehandler import BaseHandler
class MemoryHandler(BaseHandler):
def __init__(self, entry_string):
def __init__(self, entry_string, models):
self.models = models
self.entry_string = entry_string
self.handler = ConversationHandler(
entry_points=[CommandHandler(entry_string, self.entry_point, )],
@ -28,27 +27,20 @@ class MemoryHandler(BaseHandler):
async def entry_point(self, update: Update, context: CallbackContext):
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)
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
matching_models = models.JournalEntry.select().where(
models.JournalEntry.text.contains(
matching_models = self.models.JournalEntry.select().where(
self.models.JournalEntry.text.contains(
search_string
)
).order_by(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
).order_by(self.models.JournalEntry.date)
options = [[InlineKeyboardButton(m.date_pretty, callback_data=i)] for i,m in enumerate(matching_models)]
keyboard = InlineKeyboardMarkup(options)
@ -69,23 +61,20 @@ class MemoryHandler(BaseHandler):
matching_models = context.chat_data["kept_matches"]
chosen_match = matching_models[ind]
rating_string = f" ({models.RATING_MAPPING[chosen_match.rating]})" if chosen_match.rating else ""
message_text = f"On {chosen_match.date_pretty}{rating_string}, " \
f"{chosen_match.author} wrote: \n" \
f"{chosen_match.spoiler_text}"
if chosen_match.media_path:
# context.bot.sendPhoto()
await update.effective_message.reply_photo(
photo = chosen_match.media_path,
caption = message_text,
parse_mode=ParseMode.HTML
caption=
f"On {chosen_match.date_pretty}, "
f"{chosen_match.author} wrote: \n"
f"{chosen_match.text}"
)
else:
await query.edit_message_text(
message_text,
parse_mode=ParseMode.HTML
f"On {chosen_match.date_pretty}, "
f"{chosen_match.author} wrote: \n"
f"{chosen_match.text}"
)
return ConversationHandler.END

View File

@ -4,16 +4,18 @@ import socket
from telegram.ext import ConversationHandler, CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode
import os
FIRST = 1
from .basehandler import BaseHandler
class StatusHandler(BaseHandler):
"""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.entry_string = entry_string
self.models = models
self.handler = ConversationHandler(
entry_points=[CommandHandler(self.entry_string, self.entry_point)],
states={
@ -48,11 +50,9 @@ class StatusHandler(BaseHandler):
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"
if update.message:
await update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2)

View File

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

View File

@ -1,45 +1,22 @@
import os
from pathlib import Path
from telegram.ext import ExtBot
from telegram.error import BadRequest
import logging
from datetime import time, timedelta, timezone, datetime, date
from peewee import fn
import models
import random
MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
CHAT_ID = os.getenv("CHAT_ID")
class SetChatPhotoJob():
def __init__(self, bot: ExtBot, 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_photo, interval=60)
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_monthly(self.callback_photo, when=sending_time, day=-1)
async def set_random(bot: ExtBot) -> None:
"""Set a random chat photo."""
if os.getenv("DOCKERIZED", "false") == "false":
# only change image on prod
return
photos = list(MEDIA_DIR.glob("*.jpg")) + list(MEDIA_DIR.glob("*.png")) + list(MEDIA_DIR.glob("*.jpeg"))
if len(photos) == 0:
return
async def callback_photo(self, context):
# 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
photo = random.choice(photos)
await bot.set_chat_photo(CHAT_ID, photo)

View File

@ -1,65 +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=3600)
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")
rating_string = f" ({models.RATING_MAPPING[chosen_entry.rating]})" if chosen_entry.rating else ""
message_text = f"On {chosen_entry.date_pretty}{rating_string}, " \
f"{chosen_entry.author} wrote: \n" \
f"{chosen_entry.spoiler_text}"
if chosen_entry.media_path:
await self.bot.send_photo(
chat_id = chat_id,
photo = chosen_entry.media_path,
caption = message_text,
parse_mode=ParseMode.HTML
)
else:
await self.bot.send_message(
chat_id = chat_id,
text = message_text,
parse_mode=ParseMode.HTML
)

View File

@ -5,13 +5,14 @@ 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
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=logging.INFO
)
logging.getLogger("httpx").setLevel(logging.WARNING)
import asyncio
logger = logging.getLogger(__name__)
@ -23,16 +24,22 @@ def main() -> None:
models.set_db(db_path)
application = Application.builder().token(token).build()
application.add_handler(journal.JournalHandler("journal").handler)
application.add_handler(list.ListHandler("list").handler)
application.add_handler(status.StatusHandler("status").handler)
application.add_handler(journal.JournalHandler("journal", models).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(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)
chat_photo.SetChatPhotoJob(application.bot, application.job_queue)
# application.add_handler(CommandHandler("help", help_command))
# 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
application.run_polling()

View File

@ -1,6 +1,5 @@
from peewee import *
from pathlib import Path
import re
import os
import datetime
@ -10,14 +9,6 @@ ID_MAPPINGS = {
}
ID_MAPPINGS_REV = dict((v, k) for k, v in ID_MAPPINGS.items())
RATING_MAPPING = {
1: "😵",
2: "☹️",
3: "😐",
4: "😃",
5: "🥰"
}
MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
MEDIA_DIR.mkdir(parents=True, exist_ok=True)
@ -36,7 +27,7 @@ class JournalEntry(BaseModel):
text = TextField(null=True)
media_path = TextField(null=True)
last_shown = DateField(null=True)
rating = IntegerField(null=True) # mapped by RATING_MAPPING
@property
def media(self):
@ -69,25 +60,6 @@ class JournalEntry(BaseModel):
except ValueError: #fck windows
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
"((\:\))|😇|😈|[Ss]ex)" # 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):
db.initialize(SqliteDatabase(db_path))

View File

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

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

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