Compare commits
	
		
			31 Commits
		
	
	
		
			c75fb0b7a3
			...
			renovate/p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d16e844204 | |||
| cfcc88f8d4 | |||
| 3d69f9b5d8 | |||
| 0ee2e3ac72 | |||
| 840fd90b75 | |||
| 5e514bcc20 | |||
| 7b91ddbcb8 | |||
| 8583ccd249 | |||
| c43bd2b7ec | |||
| ea8b6fc8f7 | |||
| f7478fb1e3 | |||
| 29d951427d | |||
| b12eb62b41 | |||
| f5c3d767c0 | |||
| d8407bac65 | |||
| 2f9d94406c | |||
| 1ca13a9451 | |||
| 0cdc359463 | |||
| 2d923df965 | |||
| 421c3a7e1f | |||
| 
						 | 
					9eafa55dd8 | ||
| 2a344817f7 | |||
| 27656c21ae | |||
| 
						 | 
					b733d1040c | ||
| 6d9c60b0d7 | |||
| 84fb43e836 | |||
| 
						 | 
					cfcea80a64 | ||
| 7daf30f851 | |||
| 
						 | 
					a030d06641 | ||
| 15304d565e | |||
| 
						 | 
					6094112f48 | 
							
								
								
									
										29
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -1,29 +0,0 @@
 | 
				
			|||||||
kind: pipeline
 | 
					 | 
				
			||||||
type: kubernetes
 | 
					 | 
				
			||||||
name: docker-build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
node_selector:
 | 
					 | 
				
			||||||
  kubernetes.io/arch: amd64
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
steps:
 | 
					 | 
				
			||||||
- name: docker  
 | 
					 | 
				
			||||||
  image: plugins/docker
 | 
					 | 
				
			||||||
  settings:
 | 
					 | 
				
			||||||
    username: 
 | 
					 | 
				
			||||||
      from_secret: docker_uname
 | 
					 | 
				
			||||||
    password:
 | 
					 | 
				
			||||||
      from_secret: docker_pw
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    repo: mollre/journal-bot
 | 
					 | 
				
			||||||
    tags: 
 | 
					 | 
				
			||||||
      - 1.0.${DRONE_BUILD_NUMBER}
 | 
					 | 
				
			||||||
      - latest
 | 
					 | 
				
			||||||
    build_args: "BOT_VERSION=1.0.${DRONE_BUILD_NUMBER}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
trigger:
 | 
					 | 
				
			||||||
  branch:
 | 
					 | 
				
			||||||
  - main
 | 
					 | 
				
			||||||
  event:
 | 
					 | 
				
			||||||
  - push
 | 
					 | 
				
			||||||
							
								
								
									
										50
									
								
								.gitea/workflows/build_container.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								.gitea/workflows/build_container.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					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@v5
 | 
				
			||||||
 | 
					      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,9 +1,13 @@
 | 
				
			|||||||
 | 
					# Nix shell files
 | 
				
			||||||
 | 
					.direnv/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Secrets
 | 
					# Secrets
 | 
				
			||||||
dev.env
 | 
					*.secret.yaml
 | 
				
			||||||
secret.yaml
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Static data
 | 
					# Static data
 | 
				
			||||||
.bot_storage/
 | 
					.bot/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ---> Python
 | 
					# ---> Python
 | 
				
			||||||
# Byte-compiled / optimized / DLL files
 | 
					# Byte-compiled / optimized / DLL files
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.python-version
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.python-version
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					3.14
 | 
				
			||||||
							
								
								
									
										13
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -4,18 +4,9 @@
 | 
				
			|||||||
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 | 
					    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 | 
				
			||||||
    "version": "0.2.0",
 | 
					    "version": "0.2.0",
 | 
				
			||||||
    "configurations": [
 | 
					    "configurations": [
 | 
				
			||||||
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            "name": "Python: Current File",
 | 
					 | 
				
			||||||
            "type": "python",
 | 
					 | 
				
			||||||
            "request": "launch",
 | 
					 | 
				
			||||||
            "program": "${file}",
 | 
					 | 
				
			||||||
            "console": "integratedTerminal",
 | 
					 | 
				
			||||||
            "justMyCode": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "name": "Python: Current project",
 | 
					            "name": "Python: Current project",
 | 
				
			||||||
            "type": "python",
 | 
					            "type": "debugpy",
 | 
				
			||||||
            "request": "launch",
 | 
					            "request": "launch",
 | 
				
			||||||
            "program": "${workspaceFolder}/bot/main.py",
 | 
					            "program": "${workspaceFolder}/bot/main.py",
 | 
				
			||||||
            "console": "integratedTerminal",
 | 
					            "console": "integratedTerminal",
 | 
				
			||||||
@@ -23,4 +14,4 @@
 | 
				
			|||||||
            "envFile": "${workspaceFolder}/dev.env",
 | 
					            "envFile": "${workspaceFolder}/dev.env",
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,17 +1,14 @@
 | 
				
			|||||||
FROM python:3-slim
 | 
					FROM docker.io/python:3.13-alpine
 | 
				
			||||||
ENV DOCKERIZED=true
 | 
					# use the latest version of uv, independently of the python version
 | 
				
			||||||
ARG BOT_VERSION
 | 
					COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
 | 
				
			||||||
# 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 Pipfile Pipfile.lock ./
 | 
					# copy the rest of the code
 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN pipenv install --system --deploy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY bot .
 | 
					COPY bot .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CMD ["python", "main.py"]
 | 
					CMD ["uv", "run", "main.py"]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,7 +0,0 @@
 | 
				
			|||||||
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
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Pipfile
									
									
									
									
									
								
							@@ -1,14 +0,0 @@
 | 
				
			|||||||
[[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
									
									
									
								
							
							
						
						
									
										131
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@@ -1,131 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
    "_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": {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +1,9 @@
 | 
				
			|||||||
# journal-bot
 | 
					# journal-bot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Sharing memories, the digital way...
 | 
					Sharing memories, the digital way...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Migration 10.03.24
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					ALTER TABLE journalentry ADD COLUMN rating INTEGER;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,9 @@ from .basehandler import BaseHandler
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
 | 
					MEDIA_DIR = Path(os.getenv("MEDIA_DIR"))
 | 
				
			||||||
GIF_LOCATION = MEDIA_DIR / "advent" / "gifs"
 | 
					GIF_LOCATION = MEDIA_DIR / "advent" / "gifs"
 | 
				
			||||||
GIFS = list(GIF_LOCATION.glob("*.mp4"))
 | 
					# GIFS = list(GIF_LOCATION.glob("*.mp4"))
 | 
				
			||||||
STICKER_LOCATION = MEDIA_DIR / "advent" / "stickers"
 | 
					STICKER_LOCATION = MEDIA_DIR / "advent" / "stickers"
 | 
				
			||||||
STICKERS = list(STICKER_LOCATION.glob("*.tgs"))
 | 
					# STICKERS = list(STICKER_LOCATION.glob("*.tgs"))
 | 
				
			||||||
PICTURE_LOCATION = MEDIA_DIR / "advent" / "pretty_pictures"
 | 
					PICTURE_LOCATION = MEDIA_DIR / "advent" / "pretty_pictures"
 | 
				
			||||||
PICTURES = list(PICTURE_LOCATION.glob("*.jpg"))
 | 
					PICTURES = list(PICTURE_LOCATION.glob("*.jpg"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,7 +80,6 @@ class AdventsHandler(BaseHandler):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return ACTION
 | 
					        return ACTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def kuesschen(self, update: Update, context: CallbackContext):
 | 
					    async def kuesschen(self, update: Update, context: CallbackContext):
 | 
				
			||||||
        query = update.callback_query
 | 
					        query = update.callback_query
 | 
				
			||||||
        await query.answer()
 | 
					        await query.answer()
 | 
				
			||||||
@@ -126,9 +125,9 @@ class AdventsHandler(BaseHandler):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        reply_markup = InlineKeyboardMarkup(keyboard)
 | 
					        reply_markup = InlineKeyboardMarkup(keyboard)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        #if tuer_nummer <= int(date.today().strftime("%d")):
 | 
					        datum_tuer_heute = date(2023, 12, tuer_nummer)
 | 
				
			||||||
        if (tuer_nummer <= int(date.today().strftime("%d"))) and (int(date.today().strftime("%m"))==12):
 | 
					        if (date.today() - datum_tuer_heute).days >= 0:
 | 
				
			||||||
            await update.message.reply_document(document=GIFS[tuer_nummer-1], caption=f"Türchen für den {tuer_nummer}. Dezember: \n" + self.tuerchen_texte[tuerchen], reply_markup=reply_markup)
 | 
					            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:
 | 
					        else:
 | 
				
			||||||
            await update.message.reply_sticker(sticker=STICKER_LOCATION/"stickerangry.tgs")
 | 
					            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)
 | 
					            await update.message.reply_text(text="Hey, nicht schummeln! Dieses Türchen darfst du noch nicht sehen.", reply_markup=reply_markup)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup
 | 
				
			|||||||
from telegram.constants import ParseMode
 | 
					from telegram.constants import ParseMode
 | 
				
			||||||
import models
 | 
					import models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRY_OPTIONS, CONTENT_ENTRY = range(2)
 | 
					ENTRY_OPTIONS, CONTENT_ENTRY, DAY_RATING = range(3)
 | 
				
			||||||
BUTTON_COUNT = 5
 | 
					BUTTON_COUNT = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,6 +26,9 @@ class JournalHandler(BaseHandler):
 | 
				
			|||||||
                CONTENT_ENTRY: [
 | 
					                CONTENT_ENTRY: [
 | 
				
			||||||
                    MessageHandler(filters.ALL, self.content_save),
 | 
					                    MessageHandler(filters.ALL, self.content_save),
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
 | 
					                DAY_RATING: [
 | 
				
			||||||
 | 
					                    CallbackQueryHandler(self.day_rating_save),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            fallbacks=[],
 | 
					            fallbacks=[],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -35,27 +38,18 @@ 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 os.getenv("DOCKERIZED", "false") == "true" and os.getenv("CHAT_ID") != str(update.message.chat_id):
 | 
					        if models.IS_PRODUCTION 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dates = [(datetime.datetime.now() - datetime.timedelta(days = i)).date() for i in range(BUTTON_COUNT + 2)][::-1]
 | 
					        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
 | 
					        # since there are two buttons additional buttons, we need to have two more days
 | 
				
			||||||
        names = [d.strftime("%d.%m.") for d in dates]
 | 
					        names = get_names(dates)
 | 
				
			||||||
        callbacks = [d.strftime("%d%m%Y") for d in dates]
 | 
					        callbacks = [d.strftime("%d%m%Y") for d in dates]
 | 
				
			||||||
        names[-1] = "Today"
 | 
					 | 
				
			||||||
        names[-2] = "Yesterday"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        options = [
 | 
					        options = [
 | 
				
			||||||
            [
 | 
					                [InlineKeyboardButton(n, callback_data=c)] for n,c in zip(names[::-1], callbacks[::-1])
 | 
				
			||||||
                InlineKeyboardButton(names[-1], callback_data=callbacks[-1])
 | 
					        ] + [
 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            [
 | 
					 | 
				
			||||||
                InlineKeyboardButton(names[-2], callback_data=callbacks[-2])
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            [
 | 
					 | 
				
			||||||
                InlineKeyboardButton(n, callback_data=c) for n,c in zip(names[:-2], callbacks[:-2])
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                InlineKeyboardButton("<<", callback_data=BUTTON_COUNT + 2)
 | 
					                InlineKeyboardButton("<<", callback_data=BUTTON_COUNT + 2)
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
@@ -85,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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,16 +89,16 @@ class JournalHandler(BaseHandler):
 | 
				
			|||||||
        delta = int(query.data)
 | 
					        delta = int(query.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dates = [(datetime.datetime.now() - datetime.timedelta(days = i + delta)).date() for i in range(BUTTON_COUNT)][::-1]
 | 
					        dates = [(datetime.datetime.now() - datetime.timedelta(days = i + delta)).date() for i in range(BUTTON_COUNT)][::-1]
 | 
				
			||||||
        names = [d.strftime("%d.%m.") for d in dates]
 | 
					        names = get_names(dates)
 | 
				
			||||||
        callbacks = [d.strftime("%d%m%Y") for d in dates]
 | 
					        callbacks = [d.strftime("%d%m%Y") for d in dates]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        options = [
 | 
					        options = [
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                InlineKeyboardButton(">>", callback_data=delta - BUTTON_COUNT)
 | 
					                InlineKeyboardButton(">>", callback_data=delta - BUTTON_COUNT)
 | 
				
			||||||
            ],
 | 
					            ]
 | 
				
			||||||
            [
 | 
					        ] + [
 | 
				
			||||||
                InlineKeyboardButton(n, callback_data=c) for n,c in zip(names, callbacks)
 | 
					            [InlineKeyboardButton(n, callback_data=c)] for n,c in zip(names[::-1], callbacks[::-1])
 | 
				
			||||||
            ],
 | 
					        ] + [
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                InlineKeyboardButton("<<", callback_data=delta + BUTTON_COUNT)
 | 
					                InlineKeyboardButton("<<", callback_data=delta + BUTTON_COUNT)
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
@@ -119,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(
 | 
				
			||||||
@@ -164,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)
 | 
				
			||||||
@@ -173,7 +167,23 @@ class JournalHandler(BaseHandler):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            self.current_model.save()
 | 
					            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
 | 
					        return ConversationHandler.END
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -190,3 +200,22 @@ class JournalHandler(BaseHandler):
 | 
				
			|||||||
            self.current_model.delete_instance()
 | 
					            self.current_model.delete_instance()
 | 
				
			||||||
        context.chat_data["delete"] = False
 | 
					        context.chat_data["delete"] = False
 | 
				
			||||||
        await update.message.reply_text(text="Entry deleted ✅")
 | 
					        await update.message.reply_text(text="Entry deleted ✅")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,7 +46,8 @@ class ListEntryModel(BaseModel):
 | 
				
			|||||||
    done = BooleanField(default=None, null=True)
 | 
					    done = BooleanField(default=None, null=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_db(db_path):
 | 
					def set_db(db_path: Path):
 | 
				
			||||||
 | 
					    db_path.parent.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
    db.initialize(SqliteDatabase(db_path))
 | 
					    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 os.getenv("DOCKERIZED", "false") == "true" and os.getenv("CHAT_ID") != str(update.message.chat_id):
 | 
					        if models.IS_PRODUCTION 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):
 | 
				
			||||||
@@ -69,21 +69,22 @@ class MemoryHandler(BaseHandler):
 | 
				
			|||||||
        matching_models = context.chat_data["kept_matches"]
 | 
					        matching_models = context.chat_data["kept_matches"]
 | 
				
			||||||
        chosen_match = matching_models[ind]
 | 
					        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:
 | 
					        if chosen_match.media_path:
 | 
				
			||||||
            # context.bot.sendPhoto()
 | 
					            # context.bot.sendPhoto()
 | 
				
			||||||
            await update.effective_message.reply_photo(
 | 
					            await update.effective_message.reply_photo(
 | 
				
			||||||
                photo = chosen_match.media_path,
 | 
					                photo = chosen_match.media_path,
 | 
				
			||||||
                caption=
 | 
					                caption = message_text,
 | 
				
			||||||
                    f"On {chosen_match.date_pretty}, "
 | 
					 | 
				
			||||||
                    f"{chosen_match.author} wrote: \n"
 | 
					 | 
				
			||||||
                    f"{chosen_match.spoiler_text}",
 | 
					 | 
				
			||||||
                parse_mode=ParseMode.HTML
 | 
					                parse_mode=ParseMode.HTML
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            await query.edit_message_text(
 | 
					            await query.edit_message_text(
 | 
				
			||||||
                f"On {chosen_match.date_pretty}, "
 | 
					                message_text,
 | 
				
			||||||
                f"{chosen_match.author} wrote: \n"
 | 
					 | 
				
			||||||
                f"{chosen_match.spoiler_text}",
 | 
					 | 
				
			||||||
                parse_mode=ParseMode.HTML
 | 
					                parse_mode=ParseMode.HTML
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,10 +7,14 @@ 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
 | 
				
			||||||
@@ -35,7 +39,6 @@ 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
 | 
				
			||||||
@@ -47,18 +50,21 @@ class StatusHandler(BaseHandler):
 | 
				
			|||||||
            ip = "not fetchable"
 | 
					            ip = "not fetchable"
 | 
				
			||||||
            local_ips = "not fetchable"
 | 
					            local_ips = "not fetchable"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        message += "Status: Running 🟢\n"
 | 
					        message = f"""
 | 
				
			||||||
        message += f"Version: `{os.getenv('BOT_VERSION', 'dev')}`\n"
 | 
					            BeebBop\!
 | 
				
			||||||
        message += f"Uptime: `{delta[:delta.rfind('.')]}`\n"
 | 
					            Status: Running 🟢
 | 
				
			||||||
        message += f"IP \(public\): `{ip}`\n"
 | 
					            Version: `{os.getenv('BOT_VERSION', 'dev')}` and`prod={models.IS_PRODUCTION}`
 | 
				
			||||||
        message += f"IP \(private\): `{local_ips}`\n"
 | 
					            Uptime: `{delta[:delta.rfind('.')]}`
 | 
				
			||||||
        message += f"Chat ID: `{update.effective_chat.id}`\n"
 | 
					            IP \(public\): `{ip}`
 | 
				
			||||||
 | 
					            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 os.getenv("DOCKERIZED", "false") != "true":
 | 
					        if not models.IS_PRODUCTION:
 | 
				
			||||||
            # 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:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										92
									
								
								bot/cronjob/leaderboard.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								bot/cronjob/leaderboard.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					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,13 +4,14 @@ 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, 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 os.getenv("DOCKERIZED", "false") != "true":
 | 
					        if not models.IS_PRODUCTION:
 | 
				
			||||||
            # 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
 | 
				
			||||||
@@ -22,14 +23,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:
 | 
				
			||||||
@@ -42,23 +43,24 @@ class RandomMemoryJob():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        chat_id = os.getenv("CHAT_ID")
 | 
					        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:
 | 
					        if chosen_entry.media_path:
 | 
				
			||||||
            await self.bot.send_photo(
 | 
					            await self.bot.send_photo(
 | 
				
			||||||
                chat_id = chat_id,
 | 
					                chat_id = chat_id,
 | 
				
			||||||
                photo = chosen_entry.media_path,
 | 
					                photo = chosen_entry.media_path,
 | 
				
			||||||
                caption =
 | 
					                caption = message_text,
 | 
				
			||||||
                    f"On {chosen_entry.date_pretty}, "
 | 
					 | 
				
			||||||
                    f"{chosen_entry.author} wrote: \n"
 | 
					 | 
				
			||||||
                    f"{chosen_entry.spoiler_text}",
 | 
					 | 
				
			||||||
                parse_mode=ParseMode.HTML
 | 
					                parse_mode=ParseMode.HTML
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            await self.bot.send_message(
 | 
					            await self.bot.send_message(
 | 
				
			||||||
                chat_id = chat_id,
 | 
					                chat_id = chat_id,
 | 
				
			||||||
                text =
 | 
					                text = message_text,
 | 
				
			||||||
                    f"On {chosen_entry.date_pretty}, "
 | 
					 | 
				
			||||||
                    f"{chosen_entry.author} wrote: \n"
 | 
					 | 
				
			||||||
                    f"{chosen_entry.spoiler_text}",
 | 
					 | 
				
			||||||
                parse_mode=ParseMode.HTML
 | 
					                parse_mode=ParseMode.HTML
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					from cronjob import chat_photo, random_memory, leaderboard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logging.basicConfig(
 | 
					logging.basicConfig(
 | 
				
			||||||
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 | 
					    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 | 
				
			||||||
@@ -32,6 +32,7 @@ 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 datetime
 | 
					import socket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ID_MAPPINGS = {
 | 
					ID_MAPPINGS = {
 | 
				
			||||||
    "Lia": 5603036217,
 | 
					    "Lia": 5603036217,
 | 
				
			||||||
@@ -10,9 +10,22 @@ 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 = {
 | 
				
			||||||
 | 
					    1: "🙁",
 | 
				
			||||||
 | 
					    2: "😐",
 | 
				
			||||||
 | 
					    3: "🙂",
 | 
				
			||||||
 | 
					    4: "😃",
 | 
				
			||||||
 | 
					    5: "🥰"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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):
 | 
				
			||||||
@@ -28,12 +41,12 @@ class JournalEntry(BaseModel):
 | 
				
			|||||||
    text = TextField(null=True)
 | 
					    text = TextField(null=True)
 | 
				
			||||||
    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    @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}"
 | 
				
			||||||
@@ -66,20 +79,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"("
 | 
				
			||||||
            "(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence
 | 
					            r"(((?<=(\.|\!|\?)\s)[A-Z])|(^[A-Z]))" # beginning of a sentence
 | 
				
			||||||
            "([^\.\!\?])+" # any character being part of a sentence
 | 
					            r"([^\.\!\?])+" # any character being part of a sentence
 | 
				
			||||||
            "((\:\))|😇|😈)" # the smiley
 | 
					            r"((\:\))|😇|😈|[Ss]ex)" # the smiley
 | 
				
			||||||
            "([^\.\!\?])*" # continuation of sentence
 | 
					            r"([^\.\!\?])*" # continuation of sentence
 | 
				
			||||||
            "(\.|\!|\?|\,|$)" # end of the sentence
 | 
					            r"(\.|\!|\?|\,|$)" # 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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								default.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					{ 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: mollre/journal-bot:1.0.19
 | 
					          image: journal
 | 
				
			||||||
          envFrom:
 | 
					          envFrom:
 | 
				
			||||||
            - secretRef:
 | 
					            - secretRef:
 | 
				
			||||||
                name: journal-secret-env
 | 
					                name: journal-secret-env
 | 
				
			||||||
@@ -33,29 +33,3 @@ 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: fast
 | 
					 | 
				
			||||||
  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: "fast"
 | 
					 | 
				
			||||||
  accessModes:
 | 
					 | 
				
			||||||
    - ReadWriteOnce
 | 
					 | 
				
			||||||
  resources:
 | 
					 | 
				
			||||||
    requests:
 | 
					 | 
				
			||||||
      storage: "5Gi"
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,15 @@
 | 
				
			|||||||
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: mollre/journal-bot
 | 
					- name: journal
 | 
				
			||||||
  newTag: 1.0.60
 | 
					  newName: git.kluster.moll.re/remoll/journal-bot
 | 
				
			||||||
 | 
					  newTag: 1.1.0-17
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,4 @@
 | 
				
			|||||||
apiVersion: v1
 | 
					apiVersion: v1
 | 
				
			||||||
kind: Namespace
 | 
					kind: Namespace
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: journal
 | 
					  name: placeholder
 | 
				
			||||||
  labels:
 | 
					 | 
				
			||||||
    name: journal
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								deployment/pvc.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								deployment/pvc.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: PersistentVolume
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: "journal-data-nfs"
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  storageClassName: ""
 | 
				
			||||||
 | 
					  capacity:
 | 
				
			||||||
 | 
					    storage: "5Gi"
 | 
				
			||||||
 | 
					  accessModes:
 | 
				
			||||||
 | 
					    - ReadWriteOnce
 | 
				
			||||||
 | 
					  nfs:
 | 
				
			||||||
 | 
					    path: /export/kluster/journal-bot
 | 
				
			||||||
 | 
					    server: 192.168.1.157
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: PersistentVolumeClaim
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: "journal-data-nfs"
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  storageClassName: ""
 | 
				
			||||||
 | 
					  accessModes:
 | 
				
			||||||
 | 
					    - ReadWriteOnce
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					    requests:
 | 
				
			||||||
 | 
					      storage: "5Gi"
 | 
				
			||||||
 | 
					  volumeName: journal-data-nfs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								dev.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								dev.env
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					MEDIA_DIR="./.bot/media"
 | 
				
			||||||
 | 
					PERSISTENCE_DIR="./.bot/persistence"
 | 
				
			||||||
 | 
					DB_PATH="./.bot/db.sqlite"
 | 
				
			||||||
							
								
								
									
										11
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					[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",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										17
									
								
								renovate.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								renovate.json5
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$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
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								uv.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					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