23 Commits

Author SHA1 Message Date
lars-winzer 1d67cb2d92 Merge pull request 'Configure Git LFS for common image formats and gradle wrapper' (#13) from chore/setup-git-lfs into main
Code Analysis (Bandit) / bandit (push) Failing after 3s
Linting (Ruff) / ruff (push) Failing after 2s
Reviewed-on: #13
2026-06-01 19:36:23 +02:00
lars-winzer c3eff5ed7f Add: .gitattributes to store common image formats and gradle wrapper in lfs
Code Analysis (Bandit) / bandit (push) Failing after 38s
Linting (Ruff) / ruff (push) Failing after 2s
Linting (Ruff) / ruff (pull_request) Failing after 2s
2026-06-01 18:47:25 +02:00
Cromatin a9cf3af487 Merge pull request #11 from 'feat-vscode-tasks' into 'main'
Change runner type from self-hosted to ubuntu-latest for overlooked file
2025-12-08 14:48:28 +01:00
lars-winzer 01e3138f29 Change runner type from self-hosted to ubuntu-latest for overlooked file
Extends a41a483041
2025-12-08 14:45:51 +01:00
Cromatin 46fc7d0d60 Merge pull request #10 from 'feat-vscode-tasks' into 'main'
Remove renovate workflow and integrate docker build
2025-12-08 14:44:05 +01:00
lars-winzer a41a483041 Change runner type from self-hosted to ubuntu-latest 2025-12-08 14:41:34 +01:00
lars-winzer 917e90b48b Remove renovate.json 2025-10-09 10:54:13 +02:00
lars-winzer 467aebc3af Add bandit task to run on push 2025-10-08 16:02:41 +02:00
lars-winzer 818f4dfdae Remove renovate workflow 2025-10-08 15:51:45 +02:00
lars-winzer 0db5d4d31c Add ruff task to run on push and pull_request
Source: https://docs.astral.sh/ruff/integrations/#github-actions
2025-10-08 15:17:04 +02:00
lars-winzer 6967fa5358 Add task to run and attach to docker container 2025-10-01 15:03:10 +02:00
lars-winzer 70363033c1 Add docker-build task and update .gitignore 2025-09-28 18:26:48 +02:00
lars-winzer baf774f4b0 Fix "No repositories found" error for renovate action 2025-09-21 17:35:50 +02:00
lars-winzer 64215ffda5 Ditch docker approach and run renovate directly 2025-09-21 17:30:57 +02:00
lars-winzer 13a9075376 Modify renovate workflow to use docker-mount 2025-09-21 17:26:08 +02:00
Cromatin 05e72bf0dc feat: Integrated Renovate Bot as gh action (#9) 2025-09-21 17:08:16 +02:00
lars-winzer 654274f632 Enable renovate workflow schedule 2025-09-21 17:04:47 +02:00
lars-winzer 56d7a8155c Add required files for renovate bot workflow
As outlined in: https://github.com/renovatebot/github-action
2025-09-21 16:57:24 +02:00
Cromatin b82d6fa518 Merge pull request #8 from official-Cromatin:refractor/remove-portal-class
Refractor: Remove portal class
2025-09-21 16:38:37 +02:00
lars-winzer c4cb477637 Move portal attributes to Bot class 2025-09-21 15:23:14 +02:00
lars-winzer 2f33d840f3 Move version to independent VERSION file 2025-09-21 14:59:12 +02:00
lars-winzer f01b942818 Merge 'bug/issue-6' into 'main' 2025-09-18 15:34:39 +02:00
lars-winzer 8e1b4269d1 Fix .github folder being included in the docker image 2025-09-18 15:23:23 +02:00
13 changed files with 111 additions and 46 deletions
+1
View File
@@ -4,6 +4,7 @@
**/.dockerignore **/.dockerignore
**/.env **/.env
**/.git **/.git
**/.github
**/.gitignore **/.gitignore
**/.project **/.project
**/.settings **/.settings
+13
View File
@@ -0,0 +1,13 @@
###############################
# Git Large File System (LFS) #
###############################
# Images
*.gif filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
# Gradle wrapper
gradle/wrapper/gradle-wrapper.jar filter=lfs diff=lfs merge=lfs -text
+15
View File
@@ -0,0 +1,15 @@
name: Code Analysis (Bandit)
on: push
jobs:
bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mdegis/bandit-action@v1.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
path: "src/"
level: high
confidence: high
exit_zero: true
+1 -1
View File
@@ -10,7 +10,7 @@ permissions:
jobs: jobs:
build-and-push-docker: build-and-push-docker:
runs-on: self-hosted runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
+9
View File
@@ -0,0 +1,9 @@
name: Linting (Ruff)
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
+1 -1
View File
@@ -5,7 +5,7 @@ pastebin.txt
.DS_Store .DS_Store
test*.py test*.py
docker-* docker-*
.vscode .vscode/settings.json
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
+36
View File
@@ -0,0 +1,36 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Docker: Build Image",
"type": "docker-build",
"platform": "python",
"dockerBuild": {
"context": "${workspaceFolder}",
"dockerfile": "${workspaceFolder}/Dockerfile",
"tag": "post-it:dev"
}
},
{
"label": "Run post-it (interactive, show logs)",
"type": "shell",
"dependsOn": ["Docker: Build Image"],
"command": "docker",
"args": [
"run",
"--rm",
"-it",
"--name",
"post-it-dev",
"-v",
"${workspaceFolder}/config:/app/config",
"post-it:dev"
],
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
}
]
}
+1
View File
@@ -0,0 +1 @@
0.5.2
+4 -6
View File
@@ -4,7 +4,6 @@ from discord.ext import commands
from cogs.base_cog import Base_Cog from cogs.base_cog import Base_Cog
import logging import logging
from utils.portal import Portal
from datetime import datetime from datetime import datetime
from utils.datetime_tools import get_elapsed_time_big from utils.datetime_tools import get_elapsed_time_big
from utils.logger.decorator import log_command_execution from utils.logger.decorator import log_command_execution
@@ -17,17 +16,16 @@ class Debug_Command(Base_Cog):
@app_commands.command(name = "debug", description = "Provides debug informations about the bot, useful for troubleshooting") @app_commands.command(name = "debug", description = "Provides debug informations about the bot, useful for troubleshooting")
@log_command_execution @log_command_execution
async def debug(self, ctx:discord.Interaction): async def debug(self, ctx:discord.Interaction):
portal:Portal = Portal.instance()
embed = discord.Embed(title="Debug Information") embed = discord.Embed(title="Debug Information")
embed.add_field(name="Version", embed.add_field(name="Version",
value=f"`{portal.PROGRAM_VERSION}`", value=ctx.client.VERSION,
inline=True) inline=True)
embed.add_field(name="Uptime", embed.add_field(name="Uptime",
value=f"{get_elapsed_time_big(datetime.now().timestamp() - portal.STARTUP_TIMESTAMP)}", value=f"{get_elapsed_time_big(datetime.now().timestamp() - ctx.client.STARTUP_TIMESTAMP)}",
inline=True) inline=True)
embed.add_field(name="Bot Owner", embed.add_field(name="Bot Owner",
value=f"<@{portal.bot_config['DISCORD']['OWNER_ID']}>", value=f"<@{ctx.client.bot_config['DISCORD']['OWNER_ID']}>",
inline=True) inline=True)
embed.add_field(name="Latency to Gateway", embed.add_field(name="Latency to Gateway",
value=f"{round(self.__bot.latency * 1000, 2)}ms", value=f"{round(self.__bot.latency * 1000, 2)}ms",
@@ -38,7 +36,7 @@ class Debug_Command(Base_Cog):
embed.add_field(name="Number of Guilds", embed.add_field(name="Number of Guilds",
value=f"{len(self.__bot.guilds)}", value=f"{len(self.__bot.guilds)}",
inline=True) inline=True)
embed.add_field(name = "Number of executed commands", value=f"Total: {portal.no_executed_commands}\nSucceeded: {portal.no_succeeded_commands}\nFailed: {portal.no_failed_commands}") embed.add_field(name = "Number of executed commands", value=f"Total: {ctx.client.no_executed_commands}\nSucceeded: {ctx.client.no_succeeded_commands}\nFailed: {ctx.client.no_failed_commands}")
await ctx.response.send_message(embed=embed) await ctx.response.send_message(embed=embed)
+1 -4
View File
@@ -6,7 +6,6 @@ from cogs.base_cog import Base_Cog
import logging import logging
from urllib.parse import urlparse from urllib.parse import urlparse
from asyncpraw.models import Submission from asyncpraw.models import Submission
from utils.portal import Portal
import aiohttp import aiohttp
from PIL import Image from PIL import Image
from io import BytesIO from io import BytesIO
@@ -35,14 +34,12 @@ class Post_Command(Base_Cog):
async def post(self, ctx:discord.Interaction, url:str, custom_note:str = None, use_title:bool = True, quality:app_commands.Choice[int] = 95): async def post(self, ctx:discord.Interaction, url:str, custom_note:str = None, use_title:bool = True, quality:app_commands.Choice[int] = 95):
try: try:
domain_info = urlparse(url) domain_info = urlparse(url)
portal = Portal.instance()
toplevel_domain = '.'.join(domain_info.netloc.split('.')[-2:]) toplevel_domain = '.'.join(domain_info.netloc.split('.')[-2:])
begin_process = datetime.now().timestamp() begin_process = datetime.now().timestamp()
match toplevel_domain: match toplevel_domain:
case "reddit.com": case "reddit.com":
self._logger.debug(f"Recieved command by {ctx.user} ({ctx.user.id}) for reddit ({url})") self._logger.debug(f"Recieved command by {ctx.user} ({ctx.user.id}) for reddit ({url})")
subm:Submission = await ctx.client.reddit_adapter.fetch(url)
subm:Submission = await portal.reddit_adapter.fetch(url)
image_urls = [] image_urls = []
# Check if submission has a gallery # Check if submission has a gallery
if hasattr(subm, "media_metadata"): if hasattr(subm, "media_metadata"):
+8
View File
@@ -0,0 +1,8 @@
from pathlib import Path
# Read VERSION file
with open(Path(__file__).resolve().parent / "VERSION") as f:
VERSION = f.read().strip()
__all__ = VERSION
+21 -20
View File
@@ -9,16 +9,16 @@ from pathlib import Path
import re import re
import sys import sys
import traceback import traceback
from utils.portal import Portal
import asyncio import asyncio
from typing import Union from typing import Union
from platforms.reddit import Reddit_Adapter from platforms.reddit import Reddit_Adapter
from const import VERSION
print(" ____ ____ ___________ __________") print(" ____ ____ ___________ __________")
print(" / __ \/ __ \/ ___/_ __/ / _/_ __/") print(" / __ \/ __ \/ ___/_ __/ / _/_ __/")
print(" / /_/ / / / /\__ \ / /_____ / / / / ") print(" / /_/ / / / /\__ \ / /_____ / / / / ")
print(" / ____/ /_/ /___/ // /_____// / / / ") print(" / ____/ /_/ /___/ // /_____// / / / ")
print(" /_/ \____//____//_/ /___/ /_/ ") print(f" /_/ \____//____//_/ /___/ /_/ v{VERSION}")
print(" Copyright (c) 2024-2025 Lars Winzer") print(" Copyright (c) 2024-2025 Lars Winzer")
print() print()
print(" Source: https://github.com/official-Cromatin/Post-It") print(" Source: https://github.com/official-Cromatin/Post-It")
@@ -41,11 +41,17 @@ intents.messages = True
class MyBot(commands.Bot): class MyBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=None, help_command=None, intents=intents) super().__init__(command_prefix=None, help_command=None, intents=intents)
self.__portal:Portal
self.__first_on_ready = False self.__first_on_ready = False
def set_portal(self, portal:Portal): self.VERSION = VERSION
self.__portal = portal self.STARTUP_TIMESTAMP: float = None
self.platforms_config: Advanced_ConfigParser = None
self.bot_config: Advanced_ConfigParser = None
self.reddit_adapter: Reddit_Adapter = None
self.no_executed_commands:int = 0
self.no_succeeded_commands:int = 0
self.no_failed_commands:int = 0
async def setup_hook(self): async def setup_hook(self):
# Register cogs to handle commands # Register cogs to handle commands
@@ -55,13 +61,13 @@ 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.no_succeeded_commands += 1
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:
self.__portal.no_executed_commands += 1 self.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")
case discord.InteractionType.autocomplete.name: case discord.InteractionType.autocomplete.name:
@@ -82,13 +88,13 @@ class MyBot(commands.Bot):
task_start = datetime.now().timestamp() task_start = datetime.now().timestamp()
startup_logger.debug("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 self.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("Creating reddit adapter ...") startup_logger.debug("Creating reddit adapter ...")
portal.reddit_adapter = Reddit_Adapter(platforms_config["REDDIT"]["CLIENT_ID"], platforms_config["REDDIT"]["CLIENT_SECRET"]) self.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)}")
await self.change_presence(status = discord.Status.online, activity = None) await self.change_presence(status = discord.Status.online, activity = None)
@@ -101,22 +107,17 @@ class MyBot(commands.Bot):
app_logger.info(f"Successfully logged in (after {get_elapsed_time_smal(datetime.now().timestamp() - startup)}) as {self.user}") app_logger.info(f"Successfully logged in (after {get_elapsed_time_smal(datetime.now().timestamp() - startup)}) as {self.user}")
bot = MyBot() bot = MyBot()
bot_config = Advanced_ConfigParser(Path.joinpath(base_path, "config", "bot.ini")) bot.STARTUP_TIMESTAMP = startup
if re.match(r'[A-Za-z\d]{24}\.[\w-]{6}\.[\w-]{27}', bot_config["DISCORD"]["TOKEN"]): bot.bot_config = Advanced_ConfigParser(Path.joinpath(base_path, "config", "bot.ini"))
if re.match(r'[A-Za-z\d]{24}\.[\w-]{6}\.[\w-]{27}', bot.bot_config["DISCORD"]["TOKEN"]):
app_logger.critical("Bot (config/bot.ini) configuration invalid, please set a valid token") app_logger.critical("Bot (config/bot.ini) configuration invalid, please set a valid token")
quit(1) quit(1)
elif bot_config.compare_to_template() not in ("equal", "config_minus"): elif bot.bot_config.compare_to_template() not in ("equal", "config_minus"):
app_logger.critical("Bot (config/bot.ini) configuration is missing some parts. Make sure it at least has all the same keys as the template") app_logger.critical("Bot (config/bot.ini) configuration is missing some parts. Make sure it at least has all the same keys as the template")
quit(1) quit(1)
else: else:
app_logger.info("Bot configuration valid, continuing with startup") app_logger.info("Bot configuration valid, continuing with startup")
# Execute some housekeeping actions
portal = Portal.instance()
portal.bot_config = bot_config
portal.STARTUP_TIMESTAMP = startup
bot.set_portal(portal)
# Setup handlers to handle states of command execution # Setup handlers to handle states of command execution
@bot.tree.error @bot.tree.error
async def on_app_command_error(ctx:discord.Interaction, error): async def on_app_command_error(ctx:discord.Interaction, error):
@@ -124,10 +125,10 @@ async def on_app_command_error(ctx:discord.Interaction, error):
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr) print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
traceback.print_exception(type(error), error, error.__traceback__) traceback.print_exception(type(error), error, error.__traceback__)
portal.no_failed_commands += 1 bot.no_failed_commands += 1
try: try:
bot.run(bot_config["DISCORD"]["TOKEN"], log_handler = None) bot.run(bot.bot_config["DISCORD"]["TOKEN"], log_handler = None)
except discord.errors.LoginFailure: except discord.errors.LoginFailure:
app_logger.critical("Improper token has been passed. Aborting startup") app_logger.critical("Improper token has been passed. Aborting startup")
quit(1) quit(1)
-14
View File
@@ -1,14 +0,0 @@
from utils.singleton import Singleton
from utils.adv_configparser import Advanced_ConfigParser
from platforms.reddit import Reddit_Adapter
@Singleton
class Portal:
PROGRAM_VERSION = "0.3"
bot_config:Advanced_ConfigParser = None
platforms_config:Advanced_ConfigParser = None
STARTUP_TIMESTAMP:float = None
no_executed_commands:int = 0
no_succeeded_commands:int = 0
no_failed_commands:int = 0
reddit_adapter: Reddit_Adapter = None