Compare commits
	
		
			96 Commits
		
	
	
		
			secretbran
			...
			ac5311c7d0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac5311c7d0 | |||
| d8407bac65 | |||
| 2f9d94406c | |||
| 1ca13a9451 | |||
| 0cdc359463 | |||
| 2d923df965 | |||
| 421c3a7e1f | |||
|   | 9eafa55dd8 | ||
| 2a344817f7 | |||
| 27656c21ae | |||
|   | b733d1040c | ||
| 6d9c60b0d7 | |||
| 84fb43e836 | |||
|   | cfcea80a64 | ||
| 7daf30f851 | |||
|   | a030d06641 | ||
| 15304d565e | |||
|   | 6094112f48 | ||
|   | c75fb0b7a3 | ||
|   | b2820ce902 | ||
|   | b605ce315b | ||
|   | dc14eb0aec | ||
|   | 393ac72191 | ||
|   | f235b27916 | ||
|   | c4fbd089cc | ||
|   | ec87c6751c | ||
| 724b17c4b7 | |||
|   | 8b7a318e6b | ||
|   | a0e1aaa779 | ||
| c9254a3e88 | |||
|   | 3097594482 | ||
| 49df5a4495 | |||
|   | 9eb7f5bb77 | ||
| 35dbbe4ece | |||
| 6b63276dd7 | |||
|   | d40afca1a4 | ||
| 0e2b714848 | |||
| df55dbf6c7 | |||
|   | c1e7c0eb38 | ||
| c58e256194 | |||
|   | cbaedb04cb | ||
| 008cf08163 | |||
| b1a9e5fa46 | |||
|   | 7e3cf46765 | ||
| d6447c54d1 | |||
|   | db44a38eb7 | ||
|   | a77ab50f82 | ||
|   | 63daf6845c | ||
|   | 7d793c571c | ||
|   | a805775707 | ||
|   | 9ee7302f65 | ||
|   | 58fb79820f | ||
|   | 9518b813f6 | ||
|   | 122b090475 | ||
|   | 6a100e7364 | ||
|   | 0a14e8be4a | ||
|   | bdb58c8a5f | ||
|   | c526c23e3a | ||
|   | 8ff3f05034 | ||
|   | 1ce47df5f4 | ||
|   | fa5427ec09 | ||
|   | 378795d3ef | ||
|   | 8ea0c2f517 | ||
|   | 3622e76639 | ||
|   | d5349433d4 | ||
|   | 282a3d305d | ||
|   | e5d24b0171 | ||
|   | 0fb841fce4 | ||
|   | 3507d16394 | ||
|   | 6952e7f12f | ||
|   | 674a88f6ab | ||
|   | 11aefc0322 | ||
|   | 7273dd5121 | ||
|   | 350bd19b08 | ||
| e3f643e805 | |||
| 970d6931db | |||
| b39aa6ecb1 | |||
| 9b54f05d75 | |||
|   | cd7594350f | ||
| c202ad8035 | |||
|   | 50e7226709 | ||
|   | ae330eb389 | ||
| ebc89b48e2 | |||
| 0f49cbf4fb | |||
| cd3bbe9b00 | |||
| 1bc829f53d | |||
| 3975ca5997 | |||
| 2afde219a9 | |||
| a666b8e9ae | |||
| e77c106813 | |||
| 86a9762f39 | |||
| 3455946996 | |||
| 3f58eab8c7 | |||
|   | 1613e05b61 | ||
|   | 0860196a53 | ||
| 49339ebcb9 | 
| @@ -16,7 +16,10 @@ steps: | ||||
|       from_secret: docker_pw | ||||
|  | ||||
|     repo: mollre/journal-bot | ||||
|     tags: latest | ||||
|     tags:  | ||||
|       - 1.0.${DRONE_BUILD_NUMBER} | ||||
|       - latest | ||||
|     build_args: "BOT_VERSION=1.0.${DRONE_BUILD_NUMBER}" | ||||
|  | ||||
|  | ||||
| trigger: | ||||
|   | ||||
							
								
								
									
										9
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @@ -4,15 +4,6 @@ | ||||
|     // 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", | ||||
|   | ||||
| @@ -1,11 +1,16 @@ | ||||
| FROM python:3.10-slim | ||||
| FROM python:3-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 pip install pipenv && pipenv install --system --deploy | ||||
| RUN pipenv install --system --deploy | ||||
|  | ||||
| COPY bot . | ||||
|  | ||||
|   | ||||
							
								
								
									
										6
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								Pipfile
									
									
									
									
									
								
							| @@ -4,7 +4,11 @@ verify_ssl = true | ||||
| name = "pypi" | ||||
|  | ||||
| [packages] | ||||
| python-telegram-bot = "*" | ||||
| peewee = "*" | ||||
| python-telegram-bot = {extras = ["job-queue"], version = "*"} | ||||
| anyio = "*" | ||||
|  | ||||
| [dev-packages] | ||||
|  | ||||
| [pipenv] | ||||
| allow_prereleases = true | ||||
|   | ||||
							
								
								
									
										79
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										79
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|     "_meta": { | ||||
|         "hash": { | ||||
|             "sha256": "e95b9deab62bd0c661f20a178b8701fc84420db5f663fa4416666e1d05f6ce76" | ||||
|             "sha256": "5458e81c4f85af776acc44f46af838644ef8c00ccf4223fbe06f9d76a4717fc6" | ||||
|         }, | ||||
|         "pipfile-spec": 6, | ||||
|         "requires": {}, | ||||
| @@ -16,19 +16,26 @@ | ||||
|     "default": { | ||||
|         "anyio": { | ||||
|             "hashes": [ | ||||
|                 "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421", | ||||
|                 "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3" | ||||
|                 "sha256:48d53f0b141f5757c38d648309e6fe254857fae092d67f938fa248d7c0f36804", | ||||
|                 "sha256:596b09c520820e7eed961ddc889540972f92d5e8fcb081117fc054c409df34ae" | ||||
|             ], | ||||
|             "markers": "python_full_version >= '3.6.2'", | ||||
|             "version": "==3.6.2" | ||||
|             "index": "pypi", | ||||
|             "version": "==4.0.0rc1" | ||||
|         }, | ||||
|         "apscheduler": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0293937d8f6051a0f493359440c1a1b93e882c57daf0197afeff0e727777b96e", | ||||
|                 "sha256:e813ad5ada7aff36fb08cdda746b520531eaac7757832abc204868ba78e0c8f6" | ||||
|             ], | ||||
|             "version": "==3.10.1" | ||||
|         }, | ||||
|         "certifi": { | ||||
|             "hashes": [ | ||||
|                 "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", | ||||
|                 "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" | ||||
|                 "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", | ||||
|                 "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==2022.12.7" | ||||
|             "version": "==2023.7.22" | ||||
|         }, | ||||
|         "h11": { | ||||
|             "hashes": [ | ||||
| @@ -40,19 +47,19 @@ | ||||
|         }, | ||||
|         "httpcore": { | ||||
|             "hashes": [ | ||||
|                 "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb", | ||||
|                 "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0" | ||||
|                 "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888", | ||||
|                 "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.7'", | ||||
|             "version": "==0.16.3" | ||||
|             "version": "==0.17.3" | ||||
|         }, | ||||
|         "httpx": { | ||||
|             "hashes": [ | ||||
|                 "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9", | ||||
|                 "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6" | ||||
|                 "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd", | ||||
|                 "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.7'", | ||||
|             "version": "==0.23.3" | ||||
|             "version": "==0.24.1" | ||||
|         }, | ||||
|         "idna": { | ||||
|             "hashes": [ | ||||
| @@ -70,22 +77,38 @@ | ||||
|             "version": "==3.16.2" | ||||
|         }, | ||||
|         "python-telegram-bot": { | ||||
|             "extras": [ | ||||
|                 "job-queue" | ||||
|             ], | ||||
|             "hashes": [ | ||||
|                 "sha256:4d1d4b643ce158aa17a0987b84005eaf25fe0ce8b38fd234099594985611c198", | ||||
|                 "sha256:d0aa53e1f06d7cb7919cc0e2d6c81a02d968fc29921aeaa962edd1efb816a9bd" | ||||
|                 "sha256:a6ac3f9c9674aaf7d1c7e652d8b75cde969fb872f75e9521b8516eceaba82b1b", | ||||
|                 "sha256:e426404b0006989a5bcc05e11a7ef3ffe0c086b684a4e963db5bda1d361a049a" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==20.2" | ||||
|             "version": "==20.4" | ||||
|         }, | ||||
|         "rfc3986": { | ||||
|             "extras": [ | ||||
|                 "idna2008" | ||||
|             ], | ||||
|         "pytz": { | ||||
|             "hashes": [ | ||||
|                 "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", | ||||
|                 "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" | ||||
|                 "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588", | ||||
|                 "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" | ||||
|             ], | ||||
|             "version": "==1.5.0" | ||||
|             "version": "==2023.3" | ||||
|         }, | ||||
|         "setuptools": { | ||||
|             "hashes": [ | ||||
|                 "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f", | ||||
|                 "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.7'", | ||||
|             "version": "==68.0.0" | ||||
|         }, | ||||
|         "six": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", | ||||
|                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==1.16.0" | ||||
|         }, | ||||
|         "sniffio": { | ||||
|             "hashes": [ | ||||
| @@ -94,6 +117,14 @@ | ||||
|             ], | ||||
|             "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": {} | ||||
|   | ||||
| @@ -1,3 +1,9 @@ | ||||
| # journal-bot | ||||
|  | ||||
| Sharing memories, the digital way... | ||||
| Sharing memories, the digital way... | ||||
|  | ||||
|  | ||||
| ## Migration 10.03.24 | ||||
| ``` | ||||
| ALTER TABLE journalentry ADD COLUMN rating INTEGER; | ||||
| ``` | ||||
|   | ||||
| @@ -2,12 +2,21 @@ 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")) | ||||
|  | ||||
| class AdventsHandler(BaseHandler): | ||||
|     def __init__(self, entry_string): | ||||
|         self.entry_string = entry_string | ||||
| @@ -66,12 +75,11 @@ 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=open(".bot_storage\stickers\stickerwhat.tgs", "rb")) | ||||
|         await update.message.reply_sticker(sticker=STICKER_LOCATION/"stickerwhat.tgs") | ||||
|         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() | ||||
| @@ -79,7 +87,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=open(".bot_storage\stickers\stickerkiss.tgs", "rb")) | ||||
|         await update.effective_message.reply_sticker(sticker=STICKER_LOCATION/"stickerkiss.tgs") | ||||
|         await update.effective_message.reply_text(text="Mua!", reply_markup=reply_markup) | ||||
|  | ||||
|         return ACTION | ||||
| @@ -101,7 +109,8 @@ class AdventsHandler(BaseHandler): | ||||
|         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) | ||||
|         #print(picture_number-1) | ||||
|         await update.effective_message.reply_photo(photo=PICTURES[picture_number-1], caption="So ein cutes Foto!", reply_markup=reply_markup) | ||||
|  | ||||
|         return ACTION | ||||
|      | ||||
| @@ -116,11 +125,11 @@ class AdventsHandler(BaseHandler): | ||||
|  | ||||
|         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) | ||||
|         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) | ||||
|         else: | ||||
|             await update.message.reply_sticker(sticker=open(".bot_storage\stickers\stickerangry.tgs", "rb")) | ||||
|             await update.message.reply_sticker(sticker=STICKER_LOCATION/"stickerangry.tgs") | ||||
|             await update.message.reply_text(text="Hey, nicht schummeln! Dieses Türchen darfst du noch nicht sehen.", reply_markup=reply_markup) | ||||
|  | ||||
|         return ACTION | ||||
| @@ -129,7 +138,7 @@ class AdventsHandler(BaseHandler): | ||||
|         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_sticker(sticker=STICKER_LOCATION/"stickerbye.tgs") | ||||
|         await update.effective_message.reply_text(text="Bye bye, Bubo! Hab dich ganz doll lieb! 😘") | ||||
|  | ||||
|         return ConversationHandler.END | ||||
|   | ||||
| @@ -5,4 +5,4 @@ class BaseHandler: | ||||
|     entry_string: str | ||||
|  | ||||
|     async def entry_point(self, update, context) -> None: | ||||
|         self.logger.info(f"Chat said: {self.entry_string}") | ||||
|         self.logger.info(f"Chat ({update.message.chat_id}) said: {self.entry_string}") | ||||
| @@ -2,28 +2,33 @@ import datetime | ||||
| import os | ||||
| from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler | ||||
| from telegram import InlineKeyboardButton, InlineKeyboardMarkup | ||||
| ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3) | ||||
| from telegram.constants import ParseMode | ||||
| import models | ||||
|  | ||||
| ENTRY_OPTIONS, CONTENT_ENTRY, DAY_RATING = range(3) | ||||
| BUTTON_COUNT = 5 | ||||
|  | ||||
|  | ||||
| from .basehandler import BaseHandler | ||||
|  | ||||
| class JournalHandler(BaseHandler): | ||||
|     def __init__(self, entry_string, models): | ||||
|         self.models = models | ||||
|     def __init__(self, entry_string): | ||||
|         self.entry_string = entry_string | ||||
|         self.handler = ConversationHandler( | ||||
|             entry_points=[CommandHandler(entry_string, self.entry_point)], | ||||
|             states={ | ||||
|                 ACTION_CHOICE: [ | ||||
|                     CallbackQueryHandler(self.date_choice, pattern="today|yesterday"), | ||||
|                     CallbackQueryHandler(self.date_custom, pattern="custom"), | ||||
|                     CallbackQueryHandler(self.option_delete, pattern="delete") | ||||
|                     ], | ||||
|                 DATE_ENTRY: [ | ||||
|                 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"), | ||||
|                     MessageHandler(filters.ALL, self.date_entry), | ||||
|                     ], | ||||
|                 ADD_CONTENT: [ | ||||
|                 CONTENT_ENTRY: [ | ||||
|                     MessageHandler(filters.ALL, self.content_save), | ||||
|                     ] | ||||
|                     ], | ||||
|                 DAY_RATING: [ | ||||
|                     CallbackQueryHandler(self.day_rating_save), | ||||
|                     ], | ||||
|             }, | ||||
|             fallbacks=[], | ||||
|         ) | ||||
| @@ -37,52 +42,74 @@ 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("Today", callback_data="today"), | ||||
|                 InlineKeyboardButton("Yesterday", callback_data="yesterday"), | ||||
|                 InlineKeyboardButton("Custom date", callback_data="custom"), | ||||
|         options = [ | ||||
|                 [InlineKeyboardButton(n, callback_data=c)] for n,c in zip(names[::-1], callbacks[::-1]) | ||||
|         ] + [ | ||||
|             [ | ||||
|                 InlineKeyboardButton("<<", callback_data=BUTTON_COUNT + 2) | ||||
|             ], | ||||
|             [ | ||||
|                 InlineKeyboardButton("Delete", callback_data="delete") | ||||
|             ] | ||||
|         ] | ||||
|         keyboard = InlineKeyboardMarkup(options) | ||||
|         await update.message.reply_text("Please choose an option for the entry:", reply_markup=keyboard) | ||||
|         return ACTION_CHOICE | ||||
|         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 | ||||
|  | ||||
|  | ||||
|     async def date_choice(self, update, context): | ||||
|     async def date_button(self, update, context): | ||||
|         query = update.callback_query | ||||
|         await query.answer() | ||||
|         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") | ||||
|         date = datetime.datetime.strptime(query.data, "%d%m%Y").date() | ||||
|  | ||||
|         with self.models.db: | ||||
|             self.current_model, new = self.models.JournalEntry.get_or_create( | ||||
|         with models.db: | ||||
|             self.current_model, new = models.JournalEntry.get_or_create( | ||||
|                 date = date | ||||
|             ) | ||||
|         if new: | ||||
|             count = models.JournalEntry.select().count() | ||||
|             await query.edit_message_text( | ||||
|                 text=f"What is your entry for {self.current_model.date_pretty}?" | ||||
|                 text=f"Journal entry no. {count}. What happened on {self.current_model.date_pretty}?" | ||||
|             ) | ||||
|         else: | ||||
|             await query.edit_message_text(text="An entry already exists for this date") | ||||
|             return ConversationHandler.END | ||||
|          | ||||
|         return ADD_CONTENT | ||||
|         return CONTENT_ENTRY | ||||
|  | ||||
|  | ||||
|     async def date_custom(self, update, context): | ||||
|         query = update.callback_query | ||||
|         await query.answer() | ||||
|         await query.edit_message_text(text="Please enter the date in the format DDMMYYYY") | ||||
|         return DATE_ENTRY | ||||
|         delta = int(query.data) | ||||
|  | ||||
|         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 | ||||
| @@ -90,12 +117,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") | ||||
|             return DATE_ENTRY | ||||
|             await update.message.reply_text("Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2) | ||||
|             return ENTRY_OPTIONS | ||||
|          | ||||
|         if context.chat_data.get("delete", False): # if not set, delete was not chosen | ||||
|             with self.models.db: | ||||
|                 self.current_model = self.models.JournalEntry.get_or_none( | ||||
|             with models.db: | ||||
|                 self.current_model = models.JournalEntry.get_or_none( | ||||
|                     date = date | ||||
|                 ) | ||||
|             if self.current_model: | ||||
| @@ -105,20 +132,23 @@ class JournalHandler(BaseHandler): | ||||
|                 context.chat_data["delete"] = False | ||||
|             return ConversationHandler.END | ||||
|         else: | ||||
|             with self.models.db: | ||||
|                 self.current_model, new = self.models.JournalEntry.get_or_create( | ||||
|             with models.db: | ||||
|                 self.current_model, new = 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: | ||||
|                 await update.message.reply_text(f"What is your entry for {self.current_model.date_pretty}?") | ||||
|                 return ADD_CONTENT | ||||
|                 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 | ||||
|  | ||||
|  | ||||
|     async def content_save(self, update, context): | ||||
|         with self.models.db: | ||||
|         with models.db: | ||||
|             self.current_model.author_id = update.message.from_user.id | ||||
|  | ||||
|             if update.message.text: | ||||
| @@ -134,23 +164,58 @@ class JournalHandler(BaseHandler): | ||||
|                 self.current_model.save_media(file_bytes, file_path) | ||||
|  | ||||
|                 self.current_model.text = update.message.caption | ||||
|              | ||||
|  | ||||
|             self.current_model.save() | ||||
|  | ||||
|         await update.message.reply_text(f"Saved entry ✅") | ||||
|         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 ✅") | ||||
|         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") | ||||
|         await query.edit_message_text(text="Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2) | ||||
|         context.chat_data["delete"] = True | ||||
|         return DATE_ENTRY | ||||
|         return ENTRY_OPTIONS | ||||
|  | ||||
|  | ||||
|     async def delete_entry(self, update, context): | ||||
|         with self.models.db: | ||||
|         with models.db: | ||||
|             self.current_model.delete_instance() | ||||
|         context.chat_data["delete"] = False | ||||
|         await update.message.reply_text(text="Entry deleted ✅") | ||||
|         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 | ||||
|   | ||||
| @@ -5,10 +5,7 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup | ||||
|  | ||||
| from .models import ListModel, set_db, db | ||||
|  | ||||
| MEDIA_DIR = Path(os.getenv("MEDIA_DIR")) | ||||
| DB_DIR = MEDIA_DIR / "lists_db" | ||||
| DB_DIR.mkdir(parents=True, exist_ok=True) | ||||
|  | ||||
| PERSISTENCE_DIR = Path(os.getenv("PERSISTENCE_DIR")) | ||||
|  | ||||
| NAME, NEW, ACTION, ITEMADD, ITEMREMOVE, ITEMTOGGLE = range(6) | ||||
|  | ||||
| @@ -18,16 +15,17 @@ from ..basehandler import BaseHandler | ||||
| class ListHandler(BaseHandler): | ||||
|     """Create and edit lists""" | ||||
|  | ||||
|     def __init__(self, entry_string, models): | ||||
|         self.journal_models = models # not needed here | ||||
|     def __init__(self, entry_string): | ||||
|         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")], | ||||
|              | ||||
|         ] | ||||
| @@ -40,13 +38,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)], | ||||
| @@ -58,10 +56,9 @@ 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() | ||||
|         keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.name}")] for k in lists] + \ | ||||
|             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] + \ | ||||
|             [[InlineKeyboardButton("New list", callback_data="new")]] | ||||
|  | ||||
|         reply_markup = InlineKeyboardMarkup(keyboard) | ||||
| @@ -72,13 +69,13 @@ class ListHandler(BaseHandler): | ||||
|     async def choose_list(self, update, context: CallbackContext) -> None: | ||||
|         query = update.callback_query | ||||
|         data = query.data | ||||
|         name = data.replace("list-","") | ||||
|         id = data.replace("list-","") | ||||
|         await query.answer() | ||||
|         context.user_data["current_list"] = ListModel.get(name = name) | ||||
|         context.user_data["current_list"] = ListModel.get(id = id) | ||||
|  | ||||
|         reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard) | ||||
|  | ||||
|         await query.edit_message_text("Very well. For " + name + " the following actions are available:", reply_markup=reply_markup) | ||||
|         await query.edit_message_text(f"Using {context.user_data['current_list'].name}. Available actions:", reply_markup=reply_markup) | ||||
|         return ACTION | ||||
|  | ||||
|  | ||||
| @@ -88,7 +85,7 @@ class ListHandler(BaseHandler): | ||||
|  | ||||
|         reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard) | ||||
|  | ||||
|         await query.edit_message_text(f"Very well. For {context.user_data['current_list'].name} the following actions are available:", reply_markup=reply_markup) | ||||
|         await query.edit_message_text(f"Using {context.user_data['current_list'].name}. Available actions:", reply_markup=reply_markup) | ||||
|         return ACTION | ||||
|  | ||||
|  | ||||
| @@ -103,7 +100,7 @@ class ListHandler(BaseHandler): | ||||
|         name = update.message.text | ||||
|         try: | ||||
|             with db: | ||||
|                 context.user_data["current_list"] = ListModel.create(name = name) | ||||
|                 context.user_data["current_list"] = ListModel.create(name = name, chat_id=update.effective_chat.id) | ||||
|             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) | ||||
| @@ -125,10 +122,17 @@ class ListHandler(BaseHandler): | ||||
|         await query.answer() | ||||
|  | ||||
|         list_object = context.user_data["current_list"] | ||||
|         keyboard = [[InlineKeyboardButton(v, callback_data=k)] for k,v in list_object.content.items()] | ||||
|         reply_markup = InlineKeyboardMarkup(keyboard) | ||||
|         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 | ||||
|  | ||||
|         await query.edit_message_text("Which item would you like to toggle?", reply_markup = reply_markup) | ||||
|         await query.edit_message_text(msg_content, reply_markup = reply_markup) | ||||
|         return ITEMTOGGLE | ||||
|  | ||||
|  | ||||
| @@ -170,14 +174,10 @@ class ListHandler(BaseHandler): | ||||
|         await query.answer() | ||||
|         list_object = context.user_data["current_list"] | ||||
|  | ||||
|         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)]) | ||||
|         readable_it = printable_list(list_object) | ||||
|         | ||||
|         if readable_it: | ||||
|             msg_content = "\n".join(readable_it) | ||||
|         else: | ||||
|             msg_content = "List empty" | ||||
|          | ||||
| @@ -193,7 +193,6 @@ 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) | ||||
| @@ -206,11 +205,10 @@ class ListHandler(BaseHandler): | ||||
|         await query.answer() | ||||
|  | ||||
|         list_object = context.user_data["current_list"] | ||||
|         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 | ||||
|         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 | ||||
|         new_done_dict[toggle_key] = not old | ||||
|         list_object.done_dict = new_done_dict | ||||
|  | ||||
| @@ -236,3 +234,22 @@ 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 | ||||
|   | ||||
| @@ -8,6 +8,7 @@ class BaseModel(Model): | ||||
|  | ||||
| class ListModel(BaseModel): | ||||
|     name = CharField(default="") | ||||
|     chat_id = IntegerField() | ||||
|  | ||||
|     @property | ||||
|     def content(self) -> dict: | ||||
| @@ -45,22 +46,6 @@ 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: | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| import datetime | ||||
| import os | ||||
| 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) | ||||
|  | ||||
| @@ -8,8 +10,7 @@ MEMORY_CHOICE = range(1) | ||||
| from .basehandler import BaseHandler | ||||
|  | ||||
| class MemoryHandler(BaseHandler): | ||||
|     def __init__(self, entry_string, models): | ||||
|         self.models = models | ||||
|     def __init__(self, entry_string): | ||||
|         self.entry_string = entry_string | ||||
|         self.handler = ConversationHandler( | ||||
|             entry_points=[CommandHandler(entry_string, self.entry_point, )], | ||||
| @@ -27,20 +28,27 @@ 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 = self.models.JournalEntry.select().where(self.models.JournalEntry.media_path != "").order_by(self.models.JournalEntry.date) | ||||
|             matching_models = models.JournalEntry.select().where(models.JournalEntry.media_path != "").order_by(models.JournalEntry.date) | ||||
|         else: # searching for text | ||||
|             matching_models = self.models.JournalEntry.select().where( | ||||
|                 self.models.JournalEntry.text.contains( | ||||
|             matching_models = models.JournalEntry.select().where( | ||||
|                 models.JournalEntry.text.contains( | ||||
|                     search_string | ||||
|                 ) | ||||
|             ).order_by(self.models.JournalEntry.date) | ||||
|             ).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 | ||||
|  | ||||
|          | ||||
|         options = [[InlineKeyboardButton(m.date_pretty, callback_data=i)] for i,m in enumerate(matching_models)] | ||||
|  | ||||
|         keyboard = InlineKeyboardMarkup(options) | ||||
| @@ -61,20 +69,23 @@ 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= | ||||
|                     f"On {chosen_match.date_pretty}, " | ||||
|                     f"{chosen_match.author} wrote: \n" | ||||
|                     f"{chosen_match.text}" | ||||
|                 caption = message_text, | ||||
|                 parse_mode=ParseMode.HTML | ||||
|                 ) | ||||
|         else: | ||||
|             await query.edit_message_text( | ||||
|                 f"On {chosen_match.date_pretty}, " | ||||
|                 f"{chosen_match.author} wrote: \n" | ||||
|                 f"{chosen_match.text}" | ||||
|                 message_text, | ||||
|                 parse_mode=ParseMode.HTML | ||||
|             ) | ||||
|  | ||||
|         return ConversationHandler.END | ||||
|   | ||||
| @@ -4,18 +4,16 @@ 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, models): | ||||
|     def __init__(self, entry_string): | ||||
|         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={ | ||||
| @@ -50,9 +48,11 @@ 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) | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import os | ||||
| from pathlib import Path | ||||
| from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler | ||||
| from telegram import InlineKeyboardButton, InlineKeyboardMarkup | ||||
| from telegram.ext import MessageHandler, filters | ||||
| from telegram import Update | ||||
| import re | ||||
| import random | ||||
| @@ -18,37 +17,47 @@ 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) | ||||
|         pass | ||||
|         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 | ||||
|             ) | ||||
|  | ||||
|  | ||||
|     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] # TODO: choose video for smiley reaction | ||||
|         elif re.search(turtle_emoji, msgtxt): | ||||
|             # react to turtle emoji | ||||
|             vid=TURTLE_VIDEOS[0] | ||||
|             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) | ||||
|   | ||||
| @@ -1,22 +1,45 @@ | ||||
| import os | ||||
| from pathlib import Path | ||||
| from telegram.ext import ExtBot | ||||
| import random | ||||
| from telegram.error import BadRequest | ||||
| 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") | ||||
|  | ||||
|  | ||||
| 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 | ||||
| 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) | ||||
|  | ||||
|     photo = random.choice(photos) | ||||
|     await bot.set_chat_photo(CHAT_ID, photo) | ||||
|  | ||||
|     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 | ||||
|   | ||||
							
								
								
									
										65
									
								
								bot/cronjob/random_memory.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								bot/cronjob/random_memory.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| 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 | ||||
|             ) | ||||
|  | ||||
							
								
								
									
										23
									
								
								bot/main.py
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								bot/main.py
									
									
									
									
									
								
							| @@ -5,14 +5,13 @@ import logging | ||||
| import models | ||||
| from commands import journal, status, turtle, memory, advent | ||||
| from commands.list import list | ||||
| from cronjob import chat_photo | ||||
| from cronjob import chat_photo, random_memory | ||||
|  | ||||
| logging.basicConfig( | ||||
|     format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", | ||||
|     level=logging.INFO | ||||
| ) | ||||
| import asyncio | ||||
|  | ||||
| logging.getLogger("httpx").setLevel(logging.WARNING) | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| @@ -24,22 +23,16 @@ def main() -> None: | ||||
|     models.set_db(db_path) | ||||
|     application = Application.builder().token(token).build() | ||||
|  | ||||
|  | ||||
|     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(journal.JournalHandler("journal").handler) | ||||
|     application.add_handler(list.ListHandler("list").handler) | ||||
|     application.add_handler(status.StatusHandler("status").handler) | ||||
|     application.add_handler(turtle.TurtleHandler().handler) | ||||
|     application.add_handler(memory.MemoryHandler("memory", models).handler) | ||||
|     application.add_handler(memory.MemoryHandler("memory").handler) | ||||
|     application.add_handler(advent.AdventsHandler("advent").handler) | ||||
|  | ||||
|     # application.add_handler(CommandHandler("help", help_command)) | ||||
|     # on non command i.e message - echo the message on Telegram | ||||
|     # application.add_handler(InlineQueryHandler(inline_query)) | ||||
|     random_memory.RandomMemoryJob(application.bot, application.job_queue) | ||||
|     chat_photo.SetChatPhotoJob(application.bot, application.job_queue) | ||||
|  | ||||
|  | ||||
|     # 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() | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from peewee import * | ||||
| from pathlib import Path | ||||
| import re | ||||
| import os | ||||
| import datetime | ||||
|  | ||||
| @@ -9,6 +10,14 @@ 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) | ||||
|  | ||||
| @@ -27,7 +36,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): | ||||
| @@ -60,6 +69,25 @@ 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("<", "<").replace(">", ">").replace("&", "&") | ||||
|         pattern = re.compile( | ||||
|             "(" | ||||
|             "(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence | ||||
|             "([^\.\!\?])+" # any character being part of a sentence | ||||
|             "((\:\))|😇|😈|[Ss]ex)" # the smiley | ||||
|             "([^\.\!\?])*" # continuation of sentence | ||||
|             "(\.|\!|\?|\,|$)" # end of the sentence | ||||
|             ")" | ||||
|         ) | ||||
|         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)) | ||||
|   | ||||
| @@ -1,12 +1,11 @@ | ||||
| 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 | ||||
| @@ -18,8 +17,7 @@ spec: | ||||
|     spec: | ||||
|       containers: | ||||
|         - name: journal | ||||
|           image: mollre/journal-bot:latest | ||||
|           imagePullPolicy: Always | ||||
|           image: mollre/journal-bot:1.0.19 | ||||
|           envFrom: | ||||
|             - secretRef: | ||||
|                 name: journal-secret-env | ||||
| @@ -39,12 +37,9 @@ spec: | ||||
| apiVersion: v1 | ||||
| kind: PersistentVolume | ||||
| metadata: | ||||
|   namespace: journal | ||||
|   name: "journal-data-nfs" | ||||
|   # labels: | ||||
|   #   directory: "journal-data" | ||||
| spec: | ||||
|   storageClassName: fast | ||||
|   storageClassName: "" | ||||
|   capacity: | ||||
|     storage: "5Gi" | ||||
|   accessModes: | ||||
| @@ -52,21 +47,17 @@ 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: "fast" | ||||
|   storageClassName: "" | ||||
|   accessModes: | ||||
|     - ReadWriteOnce | ||||
|   resources: | ||||
|     requests: | ||||
|       storage: "5Gi" | ||||
|   # selector: | ||||
|   #   matchLabels: | ||||
|   #     directory: "journal-data" | ||||
|   volumeName: journal-data-nfs | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								deployment/kustomization.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								deployment/kustomization.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| apiVersion: kustomize.config.k8s.io/v1beta1 | ||||
| kind: Kustomization | ||||
| resources: | ||||
| - ./namespace.yaml | ||||
| - ./deployment.yaml | ||||
| - ./sealedsecret.yaml | ||||
| images: | ||||
| - name: mollre/journal-bot | ||||
|   newTag: 1.0.68 | ||||
							
								
								
									
										6
									
								
								deployment/namespace.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								deployment/namespace.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| apiVersion: v1 | ||||
| kind: Namespace | ||||
| metadata: | ||||
|   name: journal | ||||
|   labels: | ||||
|     name: journal | ||||
							
								
								
									
										25
									
								
								deployment/sealedsecret.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								deployment/sealedsecret.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| { | ||||
|   "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==" | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| { | ||||
|   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||
|   "packageRules": [ | ||||
|     { | ||||
|       "matchUpdateTypes": ["minor", "patch"], | ||||
|       "matchCurrentVersion": "!/^0/", | ||||
|       "automerge": true, | ||||
|       "automergeType": "branch" | ||||
|     } | ||||
|   ], | ||||
|   "commitMessagePrefix" : "[CI SKIP]" | ||||
| } | ||||
		Reference in New Issue
	
	Block a user