13 Commits

Author SHA1 Message Date
Cromatin e1455de14b Merge pull request #5 from official-Cromatin:official-Cromatin/issue4
Official-Cromatin/issue4
2025-09-17 14:43:07 +02:00
lars-winzer e6195d9a88 Add required permissions 2025-09-17 14:42:09 +02:00
lars-winzer 1221035369 Explicitly use bash shell 2025-09-17 14:41:46 +02:00
lars-winzer 796f43d629 Fix docker workspace error #4 2025-09-17 14:34:12 +02:00
Cromatin 0b031d20f9 Merge pull request #3 from official-Cromatin/release-v0.5
Merge branch 'release-v0.5' into 'main'
2025-09-16 18:49:29 +02:00
lars-winzer bfd497898e Update copyright year 2025-09-16 18:30:07 +02:00
lars-winzer 077f845b94 Merge commit '42bd226a6007780debe6ee1ca6357185f409f70e' into release-v0.5 2025-09-16 18:28:05 +02:00
lars-winzer 26e3a4bb8e Update LICENSE 2025-09-16 18:13:56 +02:00
lars-winzer f56636c1ff Create workflow to build and release docker image on ghcr 2025-09-16 17:30:08 +02:00
lars-winzer f427d06e61 Create Dockerfile and .dockerignore for upcoming official docker support
- Update .gitignore
2025-09-16 16:07:07 +02:00
lars-winzer 42bd226a60 Update LICENSE 2025-09-16 16:03:13 +02:00
lars-winzer 4dd578c9a8 Refine '/post'-command error handling when file size exceeds upload limit 2025-09-16 14:13:02 +02:00
lars-winzer 01d90e6625 Move imports to the top of the document
- Remove unnecessary prints
- Remove unnecessary f-strings
2025-09-16 14:10:04 +02:00
7 changed files with 139 additions and 29 deletions
+27
View File
@@ -0,0 +1,27 @@
**/__pycache__
**/.venv
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+44
View File
@@ -0,0 +1,44 @@
name: Release Multi-Arch Docker Image
on:
release:
types: [published]
permissions:
contents: read
packages: write
jobs:
build-and-push-docker:
runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Transform 'github.repository' to lowercase
id: lowercase_repo
shell: bash
run: echo "repository=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT
- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/${{ steps.lowercase_repo.outputs.repository }}:${{ github.event.release.tag_name }}
ghcr.io/${{ steps.lowercase_repo.outputs.repository }}:latest
-1
View File
@@ -4,7 +4,6 @@ config/*.ini
pastebin.txt pastebin.txt
.DS_Store .DS_Store
test*.py test*.py
.dockerignore
docker-* docker-*
.vscode .vscode
+22
View File
@@ -0,0 +1,22 @@
# Base image (multi-arch via buildx)
FROM python:3.10-slim
# Prevents pyc files + enables unbuffered logging
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Install dependencies
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy only necessary files (via .dockerignore)
COPY . .
# Create non-root user
RUN adduser --disabled-password --no-create-home --gecos "" appuser \
&& chown -R appuser /app
USER appuser
# Default command
ENTRYPOINT ["python", "src/main.py"]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 Lars Winzer Copyright (c) 2024-2025 Lars Winzer
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+25 -3
View File
@@ -5,7 +5,7 @@ from cogs.base_cog import Base_Cog
import logging import logging
from urllib.parse import urlparse from urllib.parse import urlparse
from praw.models import Submission, Subreddit from asyncpraw.models import Submission
from utils.portal import Portal from utils.portal import Portal
import aiohttp import aiohttp
from PIL import Image from PIL import Image
@@ -93,7 +93,7 @@ class Post_Command(Base_Cog):
author = subm.author.name if subm.author else "Author not found" author = subm.author.name if subm.author else "Author not found"
content = f":copyright: [{author}]({url})" content = f":copyright: [{author}]({url})"
if use_title: if use_title:
content += f"\n## {subm.title}" content += f"\n# {subm.title}"
if custom_note: if custom_note:
content += f"\n> {custom_note}" content += f"\n> {custom_note}"
@@ -134,6 +134,25 @@ class Post_Command(Base_Cog):
) )
embed.set_footer(text = "Supported image formats: jpg, jpeg, png, webp, heic, heif") embed.set_footer(text = "Supported image formats: jpg, jpeg, png, webp, heic, heif")
except discord.errors.HTTPException as error:
self._logger.error(f"Could not complete command by {ctx.user.name} ({ctx.user.id})")
self._logger.exception(error, stack_info = True)
match error.code:
case 40005:
self._logger.error("Failed to upload images, payload too large")
embed = discord.Embed(
title = "Error while processing",
description = "The attachments exceed the upload limit of Discord,\nchoose a different quality level via the argument `quality`",
color = 0xED4337
)
if ctx.response.is_done():
await ctx.followup.send(embed = embed, ephemeral = True)
else:
await ctx.response.send_message(embed = embed)
except Exception as error: except Exception as error:
self._logger.error(f"Could not complete command by {ctx.user.name} ({ctx.user.id})") self._logger.error(f"Could not complete command by {ctx.user.name} ({ctx.user.id})")
self._logger.exception(error, stack_info = True) self._logger.exception(error, stack_info = True)
@@ -150,7 +169,10 @@ class Post_Command(Base_Cog):
description = f"While we processed your request, the following exception occured: `{error}`", description = f"While we processed your request, the following exception occured: `{error}`",
color = 0xED4337 color = 0xED4337
) )
await ctx.followup.send(embed = embed, ephemeral = True) if ctx.response.is_done():
await ctx.followup.send(embed = embed, ephemeral = True)
else:
await ctx.response.send_message(embed = embed)
async def setup(bot:commands.Bot): async def setup(bot:commands.Bot):
+20 -24
View File
@@ -1,25 +1,6 @@
print(" ____ ____ ___________ __________")
print(" / __ \/ __ \/ ___/_ __/ / _/_ __/")
print(" / /_/ / / / /\__ \ / /_____ / / / / ")
print(" / ____/ /_/ /___/ // /_____// / / / ")
print(" /_/ \____//____//_/ /___/ /_/ ")
print(" Copyright (c) 2024 Lars Winzer")
print()
print(" Source: https://github.com/official-Cromatin/Post-It")
print(" Report an Issue: https://github.com/official-Cromatin/Post-It/issues/new?assignees=&labels=bug&projects=&template=issue_report.yml")
print("\n")
from datetime import datetime from datetime import datetime
startup = datetime.now().timestamp()
# Initialize the logger
from utils.logger.custom_logging import Custom_Logger from utils.logger.custom_logging import Custom_Logger
Custom_Logger.initialize()
import logging import logging
app_logger = logging.getLogger("app")
startup_logger = logging.getLogger("app.startup")
from utils.adv_configparser import Advanced_ConfigParser from utils.adv_configparser import Advanced_ConfigParser
from utils.datetime_tools import get_elapsed_time_smal, get_elapsed_time_big, get_elapsed_time_milliseconds from utils.datetime_tools import get_elapsed_time_smal, get_elapsed_time_big, get_elapsed_time_milliseconds
import discord import discord
@@ -33,6 +14,23 @@ import asyncio
from typing import Union from typing import Union
from platforms.reddit import Reddit_Adapter from platforms.reddit import Reddit_Adapter
print(" ____ ____ ___________ __________")
print(" / __ \/ __ \/ ___/_ __/ / _/_ __/")
print(" / /_/ / / / /\__ \ / /_____ / / / / ")
print(" / ____/ /_/ /___/ // /_____// / / / ")
print(" /_/ \____//____//_/ /___/ /_/ ")
print(" Copyright (c) 2024-2025 Lars Winzer")
print()
print(" Source: https://github.com/official-Cromatin/Post-It")
print(" Report an Issue: https://github.com/official-Cromatin/Post-It/issues/new?assignees=&labels=bug&projects=&template=issue_report.yml")
print("\n")
startup = datetime.now().timestamp()
# Initialize the logger
Custom_Logger.initialize()
app_logger = logging.getLogger("app")
startup_logger = logging.getLogger("app.startup")
source_path = Path(__file__).resolve() source_path = Path(__file__).resolve()
base_path = source_path.parents[1] base_path = source_path.parents[1]
app_logger.info(f"Using the following path as entrypoint: '{base_path}'") app_logger.info(f"Using the following path as entrypoint: '{base_path}'")
@@ -58,13 +56,11 @@ class MyBot(commands.Bot):
async def on_app_command_completion(self, interaction: discord.Interaction, command: Union[discord.app_commands.Command, discord.app_commands.ContextMenu]): async def on_app_command_completion(self, interaction: discord.Interaction, command: Union[discord.app_commands.Command, discord.app_commands.ContextMenu]):
"""Called when a `app_commands.Command` or `app_commands.ContextMenu` has successfully completed without error""" """Called when a `app_commands.Command` or `app_commands.ContextMenu` has successfully completed without error"""
self.__portal.no_succeeded_commands += 1 self.__portal.no_succeeded_commands += 1
print("Command succeeded")
async def on_interaction(self, interaction: discord.Interaction): async def on_interaction(self, interaction: discord.Interaction):
"""Called when an interaction happened""" """Called when an interaction happened"""
match interaction.type.name: match interaction.type.name:
case discord.InteractionType.application_command.name: case discord.InteractionType.application_command.name:
print("Interaction with bot", interaction.command.name)
self.__portal.no_executed_commands += 1 self.__portal.no_executed_commands += 1
case discord.InteractionType.ping.name: case discord.InteractionType.ping.name:
print("App got pinged by discord") print("App got pinged by discord")
@@ -78,20 +74,20 @@ class MyBot(commands.Bot):
async def on_connect(self): async def on_connect(self):
"""A coroutine to be called to setup the bot, after the bot is logged in but before it has connected to the Websocket""" """A coroutine to be called to setup the bot, after the bot is logged in but before it has connected to the Websocket"""
if not self.__first_on_ready: if not self.__first_on_ready:
startup_logger.info(f"Beginning startup routine ...") startup_logger.info("Beginning startup routine ...")
routine_begin = datetime.now().timestamp() routine_begin = datetime.now().timestamp()
await self.change_presence(status = discord.Status.dnd, activity = discord.CustomActivity("Executing pre startup routine")) await self.change_presence(status = discord.Status.dnd, activity = discord.CustomActivity("Executing pre startup routine"))
# Create the adapters for the platforms # Create the adapters for the platforms
task_start = datetime.now().timestamp() task_start = datetime.now().timestamp()
startup_logger.debug(f"Loading platforms config ...") startup_logger.debug("Loading platforms config ...")
platforms_config = Advanced_ConfigParser(Path.joinpath(base_path, "config", "platforms.ini")) platforms_config = Advanced_ConfigParser(Path.joinpath(base_path, "config", "platforms.ini"))
portal.platforms_config = platforms_config portal.platforms_config = platforms_config
startup_logger.info(f"Loaded platforms config after {get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}") startup_logger.info(f"Loaded platforms config after {get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}")
# Create platforms adapter # Create platforms adapter
task_start = datetime.now().timestamp() task_start = datetime.now().timestamp()
startup_logger.debug(f"Creating reddit adapter ...") startup_logger.debug("Creating reddit adapter ...")
portal.reddit_adapter = Reddit_Adapter(platforms_config["REDDIT"]["CLIENT_ID"], platforms_config["REDDIT"]["CLIENT_SECRET"]) portal.reddit_adapter = Reddit_Adapter(platforms_config["REDDIT"]["CLIENT_ID"], platforms_config["REDDIT"]["CLIENT_SECRET"])
startup_logger.info(f"Created reddit adapter after {get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}") startup_logger.info(f"Created reddit adapter after {get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}")