Compare commits
	
		
			1 Commits
		
	
	
		
			renovate/d
			...
			ac5311c7d0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac5311c7d0 | 
							
								
								
									
										29
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | kind: pipeline | ||||||
|  | type: kubernetes | ||||||
|  | name: docker-build | ||||||
|  |  | ||||||
|  | node_selector: | ||||||
|  |   kubernetes.io/arch: amd64 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | steps: | ||||||
|  | - name: docker   | ||||||
|  |   image: plugins/docker | ||||||
|  |   settings: | ||||||
|  |     username:  | ||||||
|  |       from_secret: docker_uname | ||||||
|  |     password: | ||||||
|  |       from_secret: docker_pw | ||||||
|  |  | ||||||
|  |     repo: mollre/journal-bot | ||||||
|  |     tags:  | ||||||
|  |       - 1.0.${DRONE_BUILD_NUMBER} | ||||||
|  |       - latest | ||||||
|  |     build_args: "BOT_VERSION=1.0.${DRONE_BUILD_NUMBER}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | trigger: | ||||||
|  |   branch: | ||||||
|  |   - main | ||||||
|  |   event: | ||||||
|  |   - push | ||||||
| @@ -1,50 +0,0 @@ | |||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|  |  | ||||||
| name: Build container |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     name: Build |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|  |  | ||||||
|     - uses: https://gitea.com/actions/checkout@v4 |  | ||||||
|  |  | ||||||
|     - name: Fetch tags from main branch |  | ||||||
|       id: version |  | ||||||
|       run: | |  | ||||||
|         git fetch origin main --tags |  | ||||||
|         LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) |  | ||||||
|         # remove the 'v' prefix from the tag name |  | ||||||
|         echo "BUILD_NAME=${LATEST_TAG//v}" >> $GITHUB_OUTPUT |  | ||||||
|  |  | ||||||
|     - name: Output the version that is being used |  | ||||||
|       run: | |  | ||||||
|         echo "Building for version ${{ steps.version.outputs.BUILD_NAME }}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     - name: Login to Docker Registry |  | ||||||
|       uses: docker/login-action@v3 |  | ||||||
|       with: |  | ||||||
|         registry: git.kluster.moll.re |  | ||||||
|         username: ${{ gitea.repository_owner }} |  | ||||||
|         password: ${{ secrets.PACKAGE_REGISTRY_ACCESS }} |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     - name: Set up Docker Buildx |  | ||||||
|       uses: docker/setup-buildx-action@v3 |  | ||||||
|  |  | ||||||
|     - name: Build and push |  | ||||||
|       uses: docker/build-push-action@v6 |  | ||||||
|       with: |  | ||||||
|         context: . |  | ||||||
|         # use the current commit SHA as the tag |  | ||||||
|         tags: git.kluster.moll.re/remoll/journal-bot:${{ steps.version.outputs.BUILD_NAME }}-${{ gitea.run_number }} |  | ||||||
|         push: true |  | ||||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,13 +1,9 @@ | |||||||
| # Nix shell files |  | ||||||
| .direnv/ |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Secrets | # Secrets | ||||||
| *.secret.yaml | dev.env | ||||||
|  | secret.yaml | ||||||
|  |  | ||||||
| # Static data | # Static data | ||||||
| .bot/ | .bot_storage/ | ||||||
|  |  | ||||||
| # ---> Python | # ---> Python | ||||||
| # Byte-compiled / optimized / DLL files | # Byte-compiled / optimized / DLL files | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| 3.13 |  | ||||||
							
								
								
									
										4
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ | |||||||
|     "configurations": [ |     "configurations": [ | ||||||
|         { |         { | ||||||
|             "name": "Python: Current project", |             "name": "Python: Current project", | ||||||
|             "type": "debugpy", |             "type": "python", | ||||||
|             "request": "launch", |             "request": "launch", | ||||||
|             "program": "${workspaceFolder}/bot/main.py", |             "program": "${workspaceFolder}/bot/main.py", | ||||||
|             "console": "integratedTerminal", |             "console": "integratedTerminal", | ||||||
| @@ -14,4 +14,4 @@ | |||||||
|             "envFile": "${workspaceFolder}/dev.env", |             "envFile": "${workspaceFolder}/dev.env", | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
							
								
								
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,14 +1,17 @@ | |||||||
| FROM docker.io/python:3.13-alpine | FROM python:3-slim | ||||||
| # use the latest version of uv, independently of the python version | ENV DOCKERIZED=true | ||||||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ | ARG BOT_VERSION | ||||||
|  | # set at build time | ||||||
|  | ENV BOT_VERSION=$BOT_VERSION | ||||||
|  |  | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
|  | RUN pip install pipenv | ||||||
|  |  | ||||||
| # copy the requirements and install them |  | ||||||
| COPY pyproject.toml uv.lock . |  | ||||||
| RUN uv sync --frozen |  | ||||||
|  |  | ||||||
| # copy the rest of the code | COPY Pipfile Pipfile.lock ./ | ||||||
|  |  | ||||||
|  | RUN pipenv install --system --deploy | ||||||
|  |  | ||||||
| COPY bot . | COPY bot . | ||||||
|  |  | ||||||
| CMD ["uv", "run", "main.py"] | CMD ["python", "main.py"] | ||||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | CURRENT_DIR := $(shell pwd) | ||||||
|  | DOTENV := ${CURRENT_DIR}/dev.env | ||||||
|  | PIPENV_CMD_PREFIX := PIPENV_DOTENV_LOCATION=${DOTENV} pipenv run | ||||||
|  |  | ||||||
|  |  | ||||||
|  | run: | ||||||
|  | 	${PIPENV_CMD_PREFIX} python bot/main.py | ||||||
							
								
								
									
										14
									
								
								Pipfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Pipfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | [[source]] | ||||||
|  | url = "https://pypi.org/simple" | ||||||
|  | verify_ssl = true | ||||||
|  | name = "pypi" | ||||||
|  |  | ||||||
|  | [packages] | ||||||
|  | peewee = "*" | ||||||
|  | python-telegram-bot = {extras = ["job-queue"], version = "*"} | ||||||
|  | anyio = "*" | ||||||
|  |  | ||||||
|  | [dev-packages] | ||||||
|  |  | ||||||
|  | [pipenv] | ||||||
|  | allow_prereleases = true | ||||||
							
								
								
									
										131
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | |||||||
|  | { | ||||||
|  |     "_meta": { | ||||||
|  |         "hash": { | ||||||
|  |             "sha256": "5458e81c4f85af776acc44f46af838644ef8c00ccf4223fbe06f9d76a4717fc6" | ||||||
|  |         }, | ||||||
|  |         "pipfile-spec": 6, | ||||||
|  |         "requires": {}, | ||||||
|  |         "sources": [ | ||||||
|  |             { | ||||||
|  |                 "name": "pypi", | ||||||
|  |                 "url": "https://pypi.org/simple", | ||||||
|  |                 "verify_ssl": true | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |     }, | ||||||
|  |     "default": { | ||||||
|  |         "anyio": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:48d53f0b141f5757c38d648309e6fe254857fae092d67f938fa248d7c0f36804", | ||||||
|  |                 "sha256:596b09c520820e7eed961ddc889540972f92d5e8fcb081117fc054c409df34ae" | ||||||
|  |             ], | ||||||
|  |             "index": "pypi", | ||||||
|  |             "version": "==4.0.0rc1" | ||||||
|  |         }, | ||||||
|  |         "apscheduler": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:0293937d8f6051a0f493359440c1a1b93e882c57daf0197afeff0e727777b96e", | ||||||
|  |                 "sha256:e813ad5ada7aff36fb08cdda746b520531eaac7757832abc204868ba78e0c8f6" | ||||||
|  |             ], | ||||||
|  |             "version": "==3.10.1" | ||||||
|  |         }, | ||||||
|  |         "certifi": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", | ||||||
|  |                 "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" | ||||||
|  |             ], | ||||||
|  |             "markers": "python_version >= '3.6'", | ||||||
|  |             "version": "==2023.7.22" | ||||||
|  |         }, | ||||||
|  |         "h11": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", | ||||||
|  |                 "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" | ||||||
|  |             ], | ||||||
|  |             "markers": "python_version >= '3.7'", | ||||||
|  |             "version": "==0.14.0" | ||||||
|  |         }, | ||||||
|  |         "httpcore": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888", | ||||||
|  |                 "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87" | ||||||
|  |             ], | ||||||
|  |             "markers": "python_version >= '3.7'", | ||||||
|  |             "version": "==0.17.3" | ||||||
|  |         }, | ||||||
|  |         "httpx": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd", | ||||||
|  |                 "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd" | ||||||
|  |             ], | ||||||
|  |             "markers": "python_version >= '3.7'", | ||||||
|  |             "version": "==0.24.1" | ||||||
|  |         }, | ||||||
|  |         "idna": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", | ||||||
|  |                 "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" | ||||||
|  |             ], | ||||||
|  |             "markers": "python_version >= '3.5'", | ||||||
|  |             "version": "==3.4" | ||||||
|  |         }, | ||||||
|  |         "peewee": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:10769981198c7311f84a0ca8db892fa213303a8eb1305deb795a71e7bd606a91" | ||||||
|  |             ], | ||||||
|  |             "index": "pypi", | ||||||
|  |             "version": "==3.16.2" | ||||||
|  |         }, | ||||||
|  |         "python-telegram-bot": { | ||||||
|  |             "extras": [ | ||||||
|  |                 "job-queue" | ||||||
|  |             ], | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:a6ac3f9c9674aaf7d1c7e652d8b75cde969fb872f75e9521b8516eceaba82b1b", | ||||||
|  |                 "sha256:e426404b0006989a5bcc05e11a7ef3ffe0c086b684a4e963db5bda1d361a049a" | ||||||
|  |             ], | ||||||
|  |             "index": "pypi", | ||||||
|  |             "version": "==20.4" | ||||||
|  |         }, | ||||||
|  |         "pytz": { | ||||||
|  |             "hashes": [ | ||||||
|  |                 "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588", | ||||||
|  |                 "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" | ||||||
|  |             ], | ||||||
|  |             "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": [ | ||||||
|  |                 "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", | ||||||
|  |                 "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" | ||||||
|  |             ], | ||||||
|  |             "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": {} | ||||||
|  | } | ||||||
| @@ -38,7 +38,7 @@ class JournalHandler(BaseHandler): | |||||||
|  |  | ||||||
|     async def entry_point(self, update, context): |     async def entry_point(self, update, context): | ||||||
|         await super().entry_point(update, context) |         await super().entry_point(update, context) | ||||||
|         if models.IS_PRODUCTION and os.getenv("CHAT_ID") != str(update.message.chat_id): |         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") |             await update.message.reply_text("You are not authorized to use this bot") | ||||||
|             return ConversationHandler.END |             return ConversationHandler.END | ||||||
|  |  | ||||||
| @@ -79,7 +79,7 @@ class JournalHandler(BaseHandler): | |||||||
|         else: |         else: | ||||||
|             await query.edit_message_text(text="An entry already exists for this date") |             await query.edit_message_text(text="An entry already exists for this date") | ||||||
|             return ConversationHandler.END |             return ConversationHandler.END | ||||||
|  |          | ||||||
|         return CONTENT_ENTRY |         return CONTENT_ENTRY | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -113,13 +113,13 @@ class JournalHandler(BaseHandler): | |||||||
|  |  | ||||||
|     async def date_entry(self, update, context): |     async def date_entry(self, update, context): | ||||||
|         date = update.message.text |         date = update.message.text | ||||||
|  |          | ||||||
|         try: |         try: | ||||||
|             date = datetime.datetime.strptime(date, "%d%m%Y").date() |             date = datetime.datetime.strptime(date, "%d%m%Y").date() | ||||||
|         except ValueError: |         except ValueError: | ||||||
|             await update.message.reply_text("Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2) |             await update.message.reply_text("Please enter the date in the format _DDMMYYYY_", parse_mode=ParseMode.MARKDOWN_V2) | ||||||
|             return ENTRY_OPTIONS |             return ENTRY_OPTIONS | ||||||
|  |          | ||||||
|         if context.chat_data.get("delete", False): # if not set, delete was not chosen |         if context.chat_data.get("delete", False): # if not set, delete was not chosen | ||||||
|             with models.db: |             with models.db: | ||||||
|                 self.current_model = models.JournalEntry.get_or_none( |                 self.current_model = models.JournalEntry.get_or_none( | ||||||
| @@ -158,7 +158,7 @@ class JournalHandler(BaseHandler): | |||||||
|                     file = await update.message.effective_attachment[-1].get_file() |                     file = await update.message.effective_attachment[-1].get_file() | ||||||
|                 else: |                 else: | ||||||
|                     file = await update.message.effective_attachment.get_file() |                     file = await update.message.effective_attachment.get_file() | ||||||
|  |                  | ||||||
|                 file_bytes = await file.download_as_bytearray() |                 file_bytes = await file.download_as_bytearray() | ||||||
|                 file_path = file.file_path |                 file_path = file.file_path | ||||||
|                 self.current_model.save_media(file_bytes, file_path) |                 self.current_model.save_media(file_bytes, file_path) | ||||||
| @@ -211,7 +211,7 @@ def get_names(dates: list): | |||||||
|         suffix = "" |         suffix = "" | ||||||
|         if models.JournalEntry.get_or_none(date = d): |         if models.JournalEntry.get_or_none(date = d): | ||||||
|             suffix = " ✅" |             suffix = " ✅" | ||||||
|  |          | ||||||
|         if d == datetime.datetime.now().date(): |         if d == datetime.datetime.now().date(): | ||||||
|             names.append("Today" + suffix) |             names.append("Today" + suffix) | ||||||
|         elif d == datetime.datetime.now().date() - datetime.timedelta(days = 1): |         elif d == datetime.datetime.now().date() - datetime.timedelta(days = 1): | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| from pathlib import Path |  | ||||||
| from peewee import * | from peewee import * | ||||||
|  |  | ||||||
| db = DatabaseProxy() | db = DatabaseProxy() | ||||||
|  |  | ||||||
| class BaseModel(Model): | class BaseModel(Model): | ||||||
| @@ -13,7 +13,7 @@ class ListModel(BaseModel): | |||||||
|     @property |     @property | ||||||
|     def content(self) -> dict: |     def content(self) -> dict: | ||||||
|         return {e.id: e.entry for e in self.entries} |         return {e.id: e.entry for e in self.entries} | ||||||
|  |      | ||||||
|     @content.setter |     @content.setter | ||||||
|     def content(self, new_content: dict): |     def content(self, new_content: dict): | ||||||
|         old_content = self.content |         old_content = self.content | ||||||
| @@ -29,7 +29,7 @@ class ListModel(BaseModel): | |||||||
|     @property |     @property | ||||||
|     def done_dict(self): |     def done_dict(self): | ||||||
|         return {e.id: e.done for e in self.entries} |         return {e.id: e.done for e in self.entries} | ||||||
|  |      | ||||||
|     @done_dict.setter |     @done_dict.setter | ||||||
|     def done_dict(self, new_done: dict): |     def done_dict(self, new_done: dict): | ||||||
|         old_done_dict = self.done_dict |         old_done_dict = self.done_dict | ||||||
| @@ -46,8 +46,7 @@ class ListEntryModel(BaseModel): | |||||||
|     done = BooleanField(default=None, null=True) |     done = BooleanField(default=None, null=True) | ||||||
|  |  | ||||||
|  |  | ||||||
| def set_db(db_path: Path): | def set_db(db_path): | ||||||
|     db_path.parent.mkdir(parents=True, exist_ok=True) |  | ||||||
|     db.initialize(SqliteDatabase(db_path)) |     db.initialize(SqliteDatabase(db_path)) | ||||||
|     with db: |     with db: | ||||||
|         db.create_tables([ListModel, ListEntryModel], safe=True) |         db.create_tables([ListModel, ListEntryModel], safe=True) | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, fi | |||||||
| from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, InputMediaPhoto | ||||||
| import models | import models | ||||||
| from telegram.constants import ParseMode | from telegram.constants import ParseMode | ||||||
|  | # ACTION_CHOICE, DATE_ENTRY, ADD_CONTENT = range(3) | ||||||
| MEMORY_CHOICE = range(1) | MEMORY_CHOICE = range(1) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -28,7 +28,7 @@ class MemoryHandler(BaseHandler): | |||||||
|  |  | ||||||
|     async def entry_point(self, update: Update, context: CallbackContext): |     async def entry_point(self, update: Update, context: CallbackContext): | ||||||
|         await super().entry_point(update, context) |         await super().entry_point(update, context) | ||||||
|         if models.IS_PRODUCTION and os.getenv("CHAT_ID") != str(update.message.chat_id): |         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") |             await update.message.reply_text("You are not authorized to use this bot") | ||||||
|             return ConversationHandler.END |             return ConversationHandler.END | ||||||
|  |  | ||||||
| @@ -56,9 +56,9 @@ class MemoryHandler(BaseHandler): | |||||||
|         await update.message.reply_text( |         await update.message.reply_text( | ||||||
|             f"Which moment would you like to remember?", reply_markup=keyboard |             f"Which moment would you like to remember?", reply_markup=keyboard | ||||||
|             ) |             ) | ||||||
|  |          | ||||||
|         context.chat_data["kept_matches"] = list(matching_models) |         context.chat_data["kept_matches"] = list(matching_models) | ||||||
|  |          | ||||||
|         return MEMORY_CHOICE |         return MEMORY_CHOICE | ||||||
|  |  | ||||||
|     async def choose_memory(self, update: Update, context: CallbackContext): |     async def choose_memory(self, update: Update, context: CallbackContext): | ||||||
|   | |||||||
| @@ -7,14 +7,10 @@ from telegram.constants import ParseMode | |||||||
| import os | import os | ||||||
|  |  | ||||||
| FIRST = 1 | FIRST = 1 | ||||||
| import models |  | ||||||
| from .basehandler import BaseHandler | from .basehandler import BaseHandler | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StatusHandler(BaseHandler): | class StatusHandler(BaseHandler): | ||||||
|     """Shows a short status of the program.""" |     """Shows a short status of the program.""" | ||||||
|  |      | ||||||
|     def __init__(self, entry_string): |     def __init__(self, entry_string): | ||||||
|         self.start_time = datetime.datetime.now() |         self.start_time = datetime.datetime.now() | ||||||
|         self.entry_string = entry_string |         self.entry_string = entry_string | ||||||
| @@ -39,6 +35,7 @@ class StatusHandler(BaseHandler): | |||||||
|         reply_markup = InlineKeyboardMarkup(keyboard) |         reply_markup = InlineKeyboardMarkup(keyboard) | ||||||
|  |  | ||||||
|         delta = str(datetime.datetime.now() - self.start_time) |         delta = str(datetime.datetime.now() - self.start_time) | ||||||
|  |         message = "BeebBop, this is Norbit\n" | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             ip = httpx.get('https://api.ipify.org').text |             ip = httpx.get('https://api.ipify.org').text | ||||||
| @@ -50,21 +47,18 @@ class StatusHandler(BaseHandler): | |||||||
|             ip = "not fetchable" |             ip = "not fetchable" | ||||||
|             local_ips = "not fetchable" |             local_ips = "not fetchable" | ||||||
|  |  | ||||||
|         message = f""" |         message += "Status: Running 🟢\n" | ||||||
|             BeebBop\! |         message += f"Version: `{os.getenv('BOT_VERSION', 'dev')}`\n" | ||||||
|             Status: Running 🟢 |         message += f"Uptime: `{delta[:delta.rfind('.')]}`\n" | ||||||
|             Version: `{os.getenv('BOT_VERSION', 'dev')}` and`prod={models.IS_PRODUCTION}` |         message += f"IP \(public\): `{ip}`\n" | ||||||
|             Uptime: `{delta[:delta.rfind('.')]}` |         message += f"IP \(private\): `{local_ips}`\n" | ||||||
|             IP \(public\): `{ip}` |         message += f"Chat ID: `{update.effective_chat.id}`\n" | ||||||
|             IP \(private\): `{local_ips}` |  | ||||||
|             Chat ID: `{update.effective_chat.id}` |  | ||||||
|         """.strip() # remove trailing whitespace |  | ||||||
|  |  | ||||||
|         if update.message: |         if update.message: | ||||||
|             await update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) |             await update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) | ||||||
|         else: |         else: | ||||||
|             await update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) |             await update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) | ||||||
|  |          | ||||||
|         return FIRST |         return FIRST | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ class SetChatPhotoJob(): | |||||||
|     def __init__(self, bot: ExtBot, job_queue): |     def __init__(self, bot: ExtBot, job_queue): | ||||||
|         self.bot = bot |         self.bot = bot | ||||||
|         self.logger = logging.getLogger(self.__class__.__name__) |         self.logger = logging.getLogger(self.__class__.__name__) | ||||||
|  |          | ||||||
|         if not models.IS_PRODUCTION: |         if os.getenv("DOCKERIZED", "false") != "true": | ||||||
|             # when running locally, annoy the programmer every 60 seconds <3 |             # when running locally, annoy the programmer every 60 seconds <3 | ||||||
|             job_queue.run_repeating(self.callback_photo, interval=60) |             job_queue.run_repeating(self.callback_photo, interval=60) | ||||||
|         else: |         else: | ||||||
| @@ -24,13 +24,13 @@ class SetChatPhotoJob(): | |||||||
|  |  | ||||||
|  |  | ||||||
|     async def callback_photo(self, context): |     async def callback_photo(self, context): | ||||||
|  |          | ||||||
|         # last_seen of memory must be older than 10 days in past or None |         # last_seen of memory must be older than 10 days in past or None | ||||||
|         with models.db: |         with models.db: | ||||||
|             possible_photos = models.JournalEntry.select().where( |             possible_photos = models.JournalEntry.select().where( | ||||||
|                 models.JournalEntry.media_path != None |                 models.JournalEntry.media_path != None | ||||||
|             ).order_by(fn.Random()) |             ).order_by(fn.Random()) | ||||||
|  |          | ||||||
|             try: |             try: | ||||||
|                 chosen_entry = possible_photos.get() |                 chosen_entry = possible_photos.get() | ||||||
|             except: |             except: | ||||||
|   | |||||||
| @@ -1,92 +0,0 @@ | |||||||
| import os |  | ||||||
| from telegram.ext import ExtBot |  | ||||||
| from telegram.constants import ParseMode |  | ||||||
| import logging |  | ||||||
| from datetime import time, timedelta, timezone, datetime, date |  | ||||||
| from peewee import fn |  | ||||||
| import models |  | ||||||
| from telegram.ext import JobQueue |  | ||||||
|  |  | ||||||
|  |  | ||||||
| RANKING_TEMPLATE = """ |  | ||||||
| <b>Journal Leaderboard</b> |  | ||||||
| This week: 📈{week_leader_name} - {week_leader_count} 📉{week_last_name} - {week_last_count} |  | ||||||
| This month: 📈{month_leader_name} - {month_leader_count} 📉{month_last_name} - {month_last_count} |  | ||||||
| This year: 📈{year_leader_name} - {year_leader_count} 📉{year_last_name} - {year_last_count} |  | ||||||
|  |  | ||||||
| 🏆 Leader: {leader_name} |  | ||||||
| """ |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_author_ranking(since_days): |  | ||||||
|     """Returns the query for the top authors by counting their journal entries. An additional field for the count is added.""" |  | ||||||
|  |  | ||||||
|     cutoff_date = date.today() - timedelta(days=since_days) |  | ||||||
|     with models.db: |  | ||||||
|         return models.JournalEntry.select( |  | ||||||
|             models.JournalEntry.author, |  | ||||||
|             fn.Count(models.JournalEntry.id).alias('message_count') |  | ||||||
|         ).where( |  | ||||||
|             models.JournalEntry.date >= cutoff_date |  | ||||||
|         ).group_by( |  | ||||||
|             models.JournalEntry.author |  | ||||||
|         ).order_by( |  | ||||||
|             fn.Count(models.JournalEntry.id).desc() |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SendLeaderboard(): |  | ||||||
|     def __init__(self, bot: ExtBot, job_queue: JobQueue): |  | ||||||
|         self.bot = bot |  | ||||||
|         self.logger = logging.getLogger(self.__class__.__name__) |  | ||||||
|  |  | ||||||
|         if not models.IS_PRODUCTION: |  | ||||||
|             # when running locally, just run once after 10 seconds |  | ||||||
|             job_queue.run_once(self.callback_leaderboard, when=10) |  | ||||||
|         else: |  | ||||||
|             # set the message sending time; include UTC shift +2 |  | ||||||
|             sending_time = time(hour=12, minute=0, second=0, tzinfo=timezone(timedelta(hours=2))) |  | ||||||
|             job_queue.run_daily(self.callback_leaderboard, time=sending_time, days=(0,)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     async def callback_leaderboard(self, context): |  | ||||||
|         """Send a weakly leaderboard to the chat.""" |  | ||||||
|  |  | ||||||
|         # get the top contributions of the past week, month and year: |  | ||||||
|         ranking_week = get_author_ranking(7) |  | ||||||
|         ranking_month = get_author_ranking(30) |  | ||||||
|         ranking_year = get_author_ranking(365) |  | ||||||
|  |  | ||||||
|         week_leader, week_last = ranking_week.first(n=2) |  | ||||||
|         month_leader, month_last = ranking_month.first(n=2) |  | ||||||
|         year_leader, year_last = ranking_year.first(n=2) |  | ||||||
|  |  | ||||||
|         leader = year_leader |  | ||||||
|  |  | ||||||
|         message_text = RANKING_TEMPLATE.format( |  | ||||||
|             week_leader_name=week_leader.author, |  | ||||||
|             week_leader_count=week_leader.message_count, |  | ||||||
|             week_last_name=week_last.author, |  | ||||||
|             week_last_count=week_last.message_count, |  | ||||||
|             month_leader_name=month_leader.author, |  | ||||||
|             month_leader_count=month_leader.message_count, |  | ||||||
|             month_last_name=month_last.author, |  | ||||||
|             month_last_count=month_last.message_count, |  | ||||||
|             year_leader_name=year_leader.author, |  | ||||||
|             year_leader_count=year_leader.message_count, |  | ||||||
|             year_last_name=year_last.author, |  | ||||||
|             year_last_count=year_last.message_count, |  | ||||||
|             leader_name=leader.author |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         print(message_text) |  | ||||||
|  |  | ||||||
|         chat_id = os.getenv("CHAT_ID") |  | ||||||
|         await self.bot.send_message( |  | ||||||
|             chat_id = chat_id, |  | ||||||
|             text = message_text, |  | ||||||
|             parse_mode=ParseMode.HTML |  | ||||||
|         ) |  | ||||||
| @@ -4,14 +4,13 @@ import os | |||||||
| from peewee import fn | from peewee import fn | ||||||
| import logging | import logging | ||||||
| import models | import models | ||||||
| from telegram.ext import ExtBot |  | ||||||
|  |  | ||||||
| class RandomMemoryJob(): | class RandomMemoryJob(): | ||||||
|     def __init__(self, bot: ExtBot, job_queue): |     def __init__(self, bot, job_queue): | ||||||
|         self.bot = bot |         self.bot = bot | ||||||
|         self.logger = logging.getLogger(self.__class__.__name__) |         self.logger = logging.getLogger(self.__class__.__name__) | ||||||
|  |          | ||||||
|         if not models.IS_PRODUCTION: |         if os.getenv("DOCKERIZED", "false") != "true": | ||||||
|             # when running locally, annoy the programmer every 60 seconds <3 |             # when running locally, annoy the programmer every 60 seconds <3 | ||||||
|             job_queue.run_repeating(self.callback_memory, interval=3600) |             job_queue.run_repeating(self.callback_memory, interval=3600) | ||||||
|             self.min_age = 0 # do not filter messages: show them all |             self.min_age = 0 # do not filter messages: show them all | ||||||
| @@ -23,14 +22,14 @@ class RandomMemoryJob(): | |||||||
|  |  | ||||||
|  |  | ||||||
|     async def callback_memory(self, context): |     async def callback_memory(self, context): | ||||||
|  |          | ||||||
|         # last_seen of memory must be older than 10 days in past or None |         # last_seen of memory must be older than 10 days in past or None | ||||||
|         with models.db: |         with models.db: | ||||||
|             possible_entries = models.JournalEntry.select().where( |             possible_entries = models.JournalEntry.select().where( | ||||||
|                 (models.JournalEntry.last_shown <= datetime.today().date() - timedelta(days=self.min_age)) | \ |                 (models.JournalEntry.last_shown <= datetime.today().date() - timedelta(days=self.min_age)) | \ | ||||||
|                 (models.JournalEntry.last_shown == None) |                 (models.JournalEntry.last_shown == None) | ||||||
|             ).order_by(fn.Random()) |             ).order_by(fn.Random()) | ||||||
|  |          | ||||||
|             try: |             try: | ||||||
|                 chosen_entry = possible_entries.get() |                 chosen_entry = possible_entries.get() | ||||||
|             except: |             except: | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import logging | |||||||
| import models | import models | ||||||
| from commands import journal, status, turtle, memory, advent | from commands import journal, status, turtle, memory, advent | ||||||
| from commands.list import list | from commands.list import list | ||||||
| from cronjob import chat_photo, random_memory, leaderboard | from cronjob import chat_photo, random_memory | ||||||
|  |  | ||||||
| logging.basicConfig( | logging.basicConfig( | ||||||
|     format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", |     format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", | ||||||
| @@ -32,7 +32,6 @@ def main() -> None: | |||||||
|  |  | ||||||
|     random_memory.RandomMemoryJob(application.bot, application.job_queue) |     random_memory.RandomMemoryJob(application.bot, application.job_queue) | ||||||
|     chat_photo.SetChatPhotoJob(application.bot, application.job_queue) |     chat_photo.SetChatPhotoJob(application.bot, application.job_queue) | ||||||
|     leaderboard.SendLeaderboard(application.bot, application.job_queue) |  | ||||||
|  |  | ||||||
|     # Run the bot until the user presses Ctrl-C |     # Run the bot until the user presses Ctrl-C | ||||||
|     application.run_polling() |     application.run_polling() | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ from peewee import * | |||||||
| from pathlib import Path | from pathlib import Path | ||||||
| import re | import re | ||||||
| import os | import os | ||||||
| import socket | import datetime | ||||||
|  |  | ||||||
| ID_MAPPINGS = { | ID_MAPPINGS = { | ||||||
|     "Lia": 5603036217, |     "Lia": 5603036217, | ||||||
| @@ -11,9 +11,9 @@ ID_MAPPINGS = { | |||||||
| ID_MAPPINGS_REV = dict((v, k) for k, v in ID_MAPPINGS.items()) | ID_MAPPINGS_REV = dict((v, k) for k, v in ID_MAPPINGS.items()) | ||||||
|  |  | ||||||
| RATING_MAPPING = { | RATING_MAPPING = { | ||||||
|     1: "🙁", |     1: "😵", | ||||||
|     2: "😐", |     2: "☹️", | ||||||
|     3: "🙂", |     3: "😐", | ||||||
|     4: "😃", |     4: "😃", | ||||||
|     5: "🥰" |     5: "🥰" | ||||||
| } | } | ||||||
| @@ -21,11 +21,6 @@ RATING_MAPPING = { | |||||||
| MEDIA_DIR = Path(os.getenv("MEDIA_DIR")) | MEDIA_DIR = Path(os.getenv("MEDIA_DIR")) | ||||||
| MEDIA_DIR.mkdir(parents=True, exist_ok=True) | MEDIA_DIR.mkdir(parents=True, exist_ok=True) | ||||||
|  |  | ||||||
| # check if we are running on a cluster |  | ||||||
| IS_PRODUCTION = os.getenv('KUBERNETES_SERVICE_HOST') is not None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| db = DatabaseProxy() | db = DatabaseProxy() | ||||||
|  |  | ||||||
| class BaseModel(Model): | class BaseModel(Model): | ||||||
| @@ -42,11 +37,11 @@ class JournalEntry(BaseModel): | |||||||
|     media_path = TextField(null=True) |     media_path = TextField(null=True) | ||||||
|     last_shown = DateField(null=True) |     last_shown = DateField(null=True) | ||||||
|     rating = IntegerField(null=True) # mapped by RATING_MAPPING |     rating = IntegerField(null=True) # mapped by RATING_MAPPING | ||||||
|  |      | ||||||
|     @property |     @property | ||||||
|     def media(self): |     def media(self): | ||||||
|         return Path(self.media_path).open('rb') |         return Path(self.media_path).open('rb') | ||||||
|  |      | ||||||
|     def save_media(self, media: bytearray, file_name: str): |     def save_media(self, media: bytearray, file_name: str): | ||||||
|         ext = Path(file_name).suffix |         ext = Path(file_name).suffix | ||||||
|         file_name = f"{self.date.isoformat()}-media{ext}" |         file_name = f"{self.date.isoformat()}-media{ext}" | ||||||
| @@ -79,20 +74,20 @@ class JournalEntry(BaseModel): | |||||||
|         """Returns the text with all the frisky details hidden away""" |         """Returns the text with all the frisky details hidden away""" | ||||||
|         new_text = self.text.replace("<", "<").replace(">", ">").replace("&", "&") |         new_text = self.text.replace("<", "<").replace(">", ">").replace("&", "&") | ||||||
|         pattern = re.compile( |         pattern = re.compile( | ||||||
|             r"(" |             "(" | ||||||
|             r"(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence |             "(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence | ||||||
|             r"([^\.\!\?])+" # any character being part of a sentence |             "([^\.\!\?])+" # any character being part of a sentence | ||||||
|             r"((\:\))|😇|😈|[Ss]ex)" # the smiley |             "((\:\))|😇|😈|[Ss]ex)" # the smiley | ||||||
|             r"([^\.\!\?])*" # continuation of sentence |             "([^\.\!\?])*" # continuation of sentence | ||||||
|             r"(\.|\!|\?|\,|$)" # end of the sentence |             "(\.|\!|\?|\,|$)" # end of the sentence | ||||||
|             r")" |             ")" | ||||||
|         ) |         ) | ||||||
|         matches = pattern.findall(new_text) |         matches = pattern.findall(new_text) | ||||||
|         for match in matches: |         for match in matches: | ||||||
|             group_to_replace = match[0] |             group_to_replace = match[0] | ||||||
|             new_text = new_text.replace(group_to_replace, f"<tg-spoiler>{group_to_replace}</tg-spoiler>") |             new_text = new_text.replace(group_to_replace, f"<tg-spoiler>{group_to_replace}</tg-spoiler>") | ||||||
|         return new_text |         return new_text | ||||||
|  |          | ||||||
|  |  | ||||||
| def set_db(db_path): | def set_db(db_path): | ||||||
|     db.initialize(SqliteDatabase(db_path)) |     db.initialize(SqliteDatabase(db_path)) | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								default.nix
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								default.nix
									
									
									
									
									
								
							| @@ -1,29 +0,0 @@ | |||||||
| { pkgs ? import <nixpkgs> {} }: |  | ||||||
| pkgs.mkShell { |  | ||||||
|     name = "journal-bot-shell"; |  | ||||||
|  |  | ||||||
|     buildInputs = with pkgs; [ |  | ||||||
|         # python |  | ||||||
|         python313 |  | ||||||
|         # setuptools as downloaded by uv seems to be broken on nixos |  | ||||||
|         python313Packages.setuptools |  | ||||||
|         uv |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # fix library dependencies: |  | ||||||
|     env.LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ |  | ||||||
|         # required by numpy and the likes |  | ||||||
|         pkgs.stdenv.cc.cc.lib |  | ||||||
|         pkgs.libz |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     # tell UV where to put the virtualenv: |  | ||||||
|     # env.UV_PROJECT_ENVIRONMENT = ".cache/uv-venvs/thesis"; |  | ||||||
|  |  | ||||||
|     # install the python packages through uv: |  | ||||||
|     shellHook = " |  | ||||||
|         uv sync |  | ||||||
|         source .venv/bin/activate |  | ||||||
|     "; |  | ||||||
| } |  | ||||||
| @@ -17,7 +17,7 @@ spec: | |||||||
|     spec: |     spec: | ||||||
|       containers: |       containers: | ||||||
|         - name: journal |         - name: journal | ||||||
|           image: journal |           image: mollre/journal-bot:1.0.19 | ||||||
|           envFrom: |           envFrom: | ||||||
|             - secretRef: |             - secretRef: | ||||||
|                 name: journal-secret-env |                 name: journal-secret-env | ||||||
| @@ -33,3 +33,31 @@ spec: | |||||||
|         - name: journal-nfs |         - name: journal-nfs | ||||||
|           persistentVolumeClaim: |           persistentVolumeClaim: | ||||||
|             claimName: journal-data-nfs |             claimName: journal-data-nfs | ||||||
|  | --- | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: PersistentVolume | ||||||
|  | metadata: | ||||||
|  |   name: "journal-data-nfs" | ||||||
|  | spec: | ||||||
|  |   storageClassName: "" | ||||||
|  |   capacity: | ||||||
|  |     storage: "5Gi" | ||||||
|  |   accessModes: | ||||||
|  |     - ReadWriteOnce | ||||||
|  |   nfs: | ||||||
|  |     path: /export/kluster/journal-bot | ||||||
|  |     server: 192.168.1.157 | ||||||
|  | --- | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: PersistentVolumeClaim | ||||||
|  | metadata: | ||||||
|  |   name: "journal-data-nfs" | ||||||
|  | spec: | ||||||
|  |   storageClassName: "" | ||||||
|  |   accessModes: | ||||||
|  |     - ReadWriteOnce | ||||||
|  |   resources: | ||||||
|  |     requests: | ||||||
|  |       storage: "5Gi" | ||||||
|  |   volumeName: journal-data-nfs | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,15 +1,9 @@ | |||||||
| apiVersion: kustomize.config.k8s.io/v1beta1 | apiVersion: kustomize.config.k8s.io/v1beta1 | ||||||
| kind: Kustomization | kind: Kustomization | ||||||
|  |  | ||||||
| resources: | resources: | ||||||
| - ./namespace.yaml | - ./namespace.yaml | ||||||
| - ./deployment.yaml | - ./deployment.yaml | ||||||
| - ./sealedsecret.yaml | - ./sealedsecret.yaml | ||||||
| - ./pvc.yaml |  | ||||||
|  |  | ||||||
| namespace: journal |  | ||||||
|  |  | ||||||
| images: | images: | ||||||
| - name: journal | - name: mollre/journal-bot | ||||||
|   newName: git.kluster.moll.re/remoll/journal-bot |   newTag: 1.0.68 | ||||||
|   newTag: 1.1.0-17 |  | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| apiVersion: v1 | apiVersion: v1 | ||||||
| kind: Namespace | kind: Namespace | ||||||
| metadata: | metadata: | ||||||
|   name: placeholder |   name: journal | ||||||
|  |   labels: | ||||||
|  |     name: journal | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| apiVersion: v1 |  | ||||||
| kind: PersistentVolume |  | ||||||
| metadata: |  | ||||||
|   name: "journal-data-nfs" |  | ||||||
| spec: |  | ||||||
|   storageClassName: "" |  | ||||||
|   capacity: |  | ||||||
|     storage: "5Gi" |  | ||||||
|   accessModes: |  | ||||||
|     - ReadWriteOnce |  | ||||||
|   nfs: |  | ||||||
|     path: /export/kluster/journal-bot |  | ||||||
|     server: 192.168.1.157 |  | ||||||
| --- |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: PersistentVolumeClaim |  | ||||||
| metadata: |  | ||||||
|   name: "journal-data-nfs" |  | ||||||
| spec: |  | ||||||
|   storageClassName: "" |  | ||||||
|   accessModes: |  | ||||||
|     - ReadWriteOnce |  | ||||||
|   resources: |  | ||||||
|     requests: |  | ||||||
|       storage: "5Gi" |  | ||||||
|   volumeName: journal-data-nfs |  | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								dev.env
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								dev.env
									
									
									
									
									
								
							| @@ -1,3 +0,0 @@ | |||||||
| MEDIA_DIR="./.bot/media" |  | ||||||
| PERSISTENCE_DIR="./.bot/persistence" |  | ||||||
| DB_PATH="./.bot/db.sqlite" |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| [project] |  | ||||||
| name = "journal-bot" |  | ||||||
| version = "0.1.0" |  | ||||||
| description = "Add your description here" |  | ||||||
| readme = "README.md" |  | ||||||
| requires-python = ">=3.13" |  | ||||||
| dependencies = [ |  | ||||||
|     "anyio>=4.9.0", |  | ||||||
|     "peewee>=3.18.2", |  | ||||||
|     "python-telegram-bot[job-queue]>=22.3", |  | ||||||
| ] |  | ||||||
							
								
								
									
										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]" | ||||||
|  | } | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| { |  | ||||||
|   "$schema": "https://docs.renovatebot.com/renovate-schema.json", |  | ||||||
|   "dependencyDashboard": true, |  | ||||||
|   "packageRules": [ |  | ||||||
|     // Fully automatically update the container version referenced in the deployment |  | ||||||
|     { |  | ||||||
|       "matchPackageNames": ["@kubernetes-sigs/kustomize"], |  | ||||||
|       "automerge": true, |  | ||||||
|       "automergeType": "branch", |  | ||||||
|       "ignoreTests": true, |  | ||||||
|       "commitMessagePrefix": "[skip ci]", |  | ||||||
|       "registryUrls": [ |  | ||||||
|         "https://git.kluster.moll.re" |  | ||||||
|       ], |  | ||||||
|     } |  | ||||||
|   ], |  | ||||||
| } |  | ||||||
							
								
								
									
										153
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										153
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,153 +0,0 @@ | |||||||
| version = 1 |  | ||||||
| revision = 2 |  | ||||||
| requires-python = ">=3.13" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "anyio" |  | ||||||
| version = "4.9.0" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "idna" }, |  | ||||||
|     { name = "sniffio" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "apscheduler" |  | ||||||
| version = "3.11.0" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "tzlocal" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/4e/00/6d6814ddc19be2df62c8c898c4df6b5b1914f3bd024b780028caa392d186/apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133", size = 107347, upload-time = "2024-11-24T19:39:26.463Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004, upload-time = "2024-11-24T19:39:24.442Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "certifi" |  | ||||||
| version = "2025.7.14" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "h11" |  | ||||||
| version = "0.16.0" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "httpcore" |  | ||||||
| version = "1.0.9" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "certifi" }, |  | ||||||
|     { name = "h11" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "httpx" |  | ||||||
| version = "0.28.1" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "anyio" }, |  | ||||||
|     { name = "certifi" }, |  | ||||||
|     { name = "httpcore" }, |  | ||||||
|     { name = "idna" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "idna" |  | ||||||
| version = "3.10" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "journal-bot" |  | ||||||
| version = "0.1.0" |  | ||||||
| source = { virtual = "." } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "anyio" }, |  | ||||||
|     { name = "peewee" }, |  | ||||||
|     { name = "python-telegram-bot", extra = ["job-queue"] }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [package.metadata] |  | ||||||
| requires-dist = [ |  | ||||||
|     { name = "anyio", specifier = ">=4.9.0" }, |  | ||||||
|     { name = "peewee", specifier = ">=3.18.2" }, |  | ||||||
|     { name = "python-telegram-bot", extras = ["job-queue"], specifier = ">=22.3" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "peewee" |  | ||||||
| version = "3.18.2" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/04/89/76f6f1b744c8608e0d416b588b9d63c2a500ff800065ae610f7c80f532d6/peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0", size = 949220, upload-time = "2025-07-08T12:52:03.941Z" } |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "python-telegram-bot" |  | ||||||
| version = "22.3" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "httpx" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/db/fc/0196e0d7ad247011a560788db204e0a28d76ab75b3d7c7131878f8fb5a06/python_telegram_bot-22.3.tar.gz", hash = "sha256:513d5ab9db96dcf25272dad0a726555e80edf60d09246a7d0d425b77115f5440", size = 1464513, upload-time = "2025-07-20T20:03:09.805Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e5/54/0955bd46a1e046169500e129c7883664b6675d580074d68823485e4d5de1/python_telegram_bot-22.3-py3-none-any.whl", hash = "sha256:88fab2d1652dbfd5379552e8b904d86173c524fdb9270d3a8685f599ffe0299f", size = 717115, upload-time = "2025-07-20T20:03:07.261Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [package.optional-dependencies] |  | ||||||
| job-queue = [ |  | ||||||
|     { name = "apscheduler" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "sniffio" |  | ||||||
| version = "1.3.1" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "tzdata" |  | ||||||
| version = "2025.2" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "tzlocal" |  | ||||||
| version = "5.3.1" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "tzdata", marker = "sys_platform == 'win32'" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, |  | ||||||
| ] |  | ||||||
		Reference in New Issue
	
	Block a user