Merge branch 'release-v0.5' into 'main' #3
@@ -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
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Release Multi-Arch Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
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: Build and push multi-arch image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }}
|
||||
ghcr.io/${{ github.repository }}:latest
|
||||
@@ -4,7 +4,6 @@ config/*.ini
|
||||
pastebin.txt
|
||||
.DS_Store
|
||||
test*.py
|
||||
.dockerignore
|
||||
docker-*
|
||||
.vscode
|
||||
|
||||
|
||||
+22
@@ -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"]
|
||||
+4
-2
@@ -1,3 +1,5 @@
|
||||
discord.py==2.4.0
|
||||
praw>=7.8.0
|
||||
colorama==0.4.6
|
||||
asyncpraw==7.8.1
|
||||
colorama==0.4.6
|
||||
aiohttp==3.11.11
|
||||
pillow==11.1.0
|
||||
@@ -1,33 +0,0 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from cogs.base_cog import Base_Cog
|
||||
from cogs.maintenance import Maintenance_Command
|
||||
|
||||
import logging
|
||||
from utils.portal import Portal
|
||||
|
||||
class About_Command(Base_Cog):
|
||||
def __init__(self, bot:commands.Bot):
|
||||
self.__bot = bot
|
||||
super().__init__(logging.getLogger("cmds.about"))
|
||||
|
||||
@app_commands.command(name = "about", description = "Provides general information about the bot")
|
||||
@app_commands.check(Maintenance_Command.handle_check)
|
||||
async def about(self, ctx: discord.Interaction):
|
||||
portal:Portal = Portal.instance()
|
||||
embed = discord.Embed(title=f"POST IT - `{portal.PROGRAM_VERSION}`",
|
||||
description="### Übersicht der Befehle\n- `/post` Veröffentlicht einen Post einer anderen Plattform, mit beliebigen Bildern als Anhang\n- `/help` Zeigt diese Übersicht an\n- `/settings` Persönliche Einstellungen für das verhalten des Bots\n- `/stats` Zeigt persönliche und globale Statistiken\n- `/debug` Erweiterte Informationen zum Betriebszustandes des Bots",
|
||||
colour=0x5a5a5a)
|
||||
|
||||
embed.add_field(name="Status des Bots",
|
||||
value="Fehlerfrei :green_circle:",
|
||||
inline=True)
|
||||
embed.add_field(name="Besitzer der Bot Instanz",
|
||||
value=f"<@{portal.bot_config['DISCORD']['OWNER_ID']}>",
|
||||
inline=True)
|
||||
|
||||
await ctx.response.send_message(embed = embed, ephemeral = True)
|
||||
|
||||
async def setup(bot:commands.Bot):
|
||||
await bot.add_cog(About_Command(bot))
|
||||
@@ -2,7 +2,6 @@ import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from cogs.base_cog import Base_Cog
|
||||
from cogs.maintenance import Maintenance_Command
|
||||
|
||||
import logging
|
||||
from utils.portal import Portal
|
||||
@@ -16,7 +15,6 @@ class Debug_Command(Base_Cog):
|
||||
super().__init__(logging.getLogger("cmds.debug"))
|
||||
|
||||
@app_commands.command(name = "debug", description = "Provides debug informations about the bot, useful for troubleshooting")
|
||||
@app_commands.check(Maintenance_Command.handle_check)
|
||||
@log_command_execution
|
||||
async def debug(self, ctx:discord.Interaction):
|
||||
portal:Portal = Portal.instance()
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
from cogs.base_cog import Base_Cog
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class Maintenance_Command(Base_Cog):
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
self.__maintenance_mode_enabled = False
|
||||
super().__init__(logging.getLogger("cmds.maintenance"))
|
||||
|
||||
def enable_global_maintenance(self):
|
||||
self.__maintenance_mode_enabled = True
|
||||
|
||||
def disable_gloabal_maintenance(self):
|
||||
self.__maintenance_mode_enabled = False
|
||||
|
||||
@staticmethod
|
||||
async def handle_check(ctx: discord.Integration):
|
||||
"""Check function called for every command"""
|
||||
instance = ctx.client.get_cog("Maintenance_Command")
|
||||
if instance and instance.__maintenance_mode_enabled:
|
||||
await instance.startup(ctx)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def startup(self, ctx: discord.Interaction):
|
||||
"""Returns an message, explaining that the bot is still starting up"""
|
||||
embed = discord.Embed(
|
||||
title = ":construction: Bot is still starting up! :construction:",
|
||||
description = "While the bot is starting, all commands are locked and unable to be executed",
|
||||
color = 0xED4337)
|
||||
await ctx.response.send_message(embed = embed)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx, error):
|
||||
if isinstance(error, app_commands.CheckFailure):
|
||||
# Suppress the traceback and inform the user about maintenance mode
|
||||
return # Already handled in the `startup` method
|
||||
|
||||
cmds_group = app_commands.Group(name="maintenance", description="Put the bot into maintenance mode and control its alternative behaviour")
|
||||
@cmds_group.command(name = "enable", description = "Enables the maintenance mode globally")
|
||||
async def maintenance_enable(self, ctx: discord.Interaction):
|
||||
if self.__maintenance_mode_enabled:
|
||||
await ctx.response.send_message("Maintenance mode was allready enabled", ephemeral = True)
|
||||
else:
|
||||
await ctx.response.send_message("Maintenance mode is now enabled", ephemeral = True)
|
||||
self.__maintenance_mode_enabled = True
|
||||
|
||||
async def maintenance_disable(self, ctx: discord.Interaction):
|
||||
if self.__maintenance_mode_enabled:
|
||||
await ctx.response.send_message("Maintenance mode is now disabled", ephemeral = True)
|
||||
else:
|
||||
await ctx.response.send_message("Maintenance mode was allready disabled", ephemeral = True)
|
||||
self.__maintenance_mode_enabled = False
|
||||
|
||||
async def setup(bot:commands.Bot):
|
||||
await bot.add_cog(Maintenance_Command(bot))
|
||||
+153
-48
@@ -2,12 +2,19 @@ import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from cogs.base_cog import Base_Cog
|
||||
from cogs.maintenance import Maintenance_Command
|
||||
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
from praw.models import Submission, Subreddit
|
||||
from asyncpraw.models import Submission
|
||||
from utils.portal import Portal
|
||||
import aiohttp
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
from datetime import datetime
|
||||
from utils.datetime_tools import get_elapsed_time_milliseconds
|
||||
|
||||
class NoMediaFound(Exception):
|
||||
pass
|
||||
|
||||
class Post_Command(Base_Cog):
|
||||
def __init__(self, bot:commands.Bot):
|
||||
@@ -15,58 +22,156 @@ class Post_Command(Base_Cog):
|
||||
super().__init__(logging.getLogger("cmds.post"))
|
||||
|
||||
@app_commands.command(name = "post", description = "Post an embed in the Current Channel with a link to the content")
|
||||
@app_commands.check(Maintenance_Command.handle_check)
|
||||
async def post(self, ctx:discord.Interaction, url:str):
|
||||
domain_info = urlparse(url)
|
||||
portal = Portal.instance()
|
||||
toplevel_domain = '.'.join(domain_info.netloc.split('.')[-2:])
|
||||
match toplevel_domain:
|
||||
case "reddit.com":
|
||||
subm:Submission = portal.reddit_adapter.fetch(url)
|
||||
embeds = []
|
||||
@app_commands.describe(url = "URL to the post", custom_note = "Describe the post with your own note", use_title = "Display the title of the post", quality = "Specifies the quality of the converted image, closer to 100 is better")
|
||||
@app_commands.choices(quality = [
|
||||
app_commands.Choice(name = "Poor (60)", value = 60),
|
||||
app_commands.Choice(name = "Fair (70)", value = 70),
|
||||
app_commands.Choice(name = "Good (80)", value = 80),
|
||||
app_commands.Choice(name = "Very Good (85)", value = 85),
|
||||
app_commands.Choice(name = "Excellent (90)", value = 90),
|
||||
app_commands.Choice(name = "Superior (95)", value = 95),
|
||||
app_commands.Choice(name = "Perfect (100)", value = 100)
|
||||
])
|
||||
async def post(self, ctx:discord.Interaction, url:str, custom_note:str = None, use_title:bool = True, quality:app_commands.Choice[int] = 95):
|
||||
try:
|
||||
domain_info = urlparse(url)
|
||||
portal = Portal.instance()
|
||||
toplevel_domain = '.'.join(domain_info.netloc.split('.')[-2:])
|
||||
begin_process = datetime.now().timestamp()
|
||||
match toplevel_domain:
|
||||
case "reddit.com":
|
||||
self._logger.debug(f"Recieved command by {ctx.user} ({ctx.user.id}) for reddit ({url})")
|
||||
|
||||
image_urls = []
|
||||
# Check if submission has a gallery
|
||||
if hasattr(subm, "media_metadata"):
|
||||
for media_id, media in subm.media_metadata.items():
|
||||
file_extension = media["m"].split("/")[1]
|
||||
image_urls.append(f"https://i.redd.it/{media_id}.{file_extension}")
|
||||
else:
|
||||
image_urls.append(subm.url)
|
||||
self._logger.debug(f"Found {len(image_urls)} image urls for the post")
|
||||
subm:Submission = await portal.reddit_adapter.fetch(url)
|
||||
image_urls = []
|
||||
# Check if submission has a gallery
|
||||
if hasattr(subm, "media_metadata"):
|
||||
for media_id, media in subm.media_metadata.items():
|
||||
file_extension = media["m"].split("/")[1]
|
||||
if file_extension not in ("jpg", "jpeg", "png", "webp", "heic", "heif"):
|
||||
continue
|
||||
image_urls.append(f"https://i.redd.it/{media_id}.{file_extension}")
|
||||
else:
|
||||
image_urls.append(subm.url)
|
||||
image_count = len(image_urls)
|
||||
if image_count == 0:
|
||||
raise NoMediaFound
|
||||
self._logger.debug(f"Found {image_count} image urls for the post")
|
||||
|
||||
embed = discord.Embed(url = "https://discord.com/humans.txt", color = int(portal.platforms_config["REDDIT"]["embed_color"], 16))
|
||||
embed.set_author(name = subm.title)
|
||||
embeds.append(embed)
|
||||
progress_title = f"`{image_count}` images are going to be converted, it may take a while."
|
||||
progress_temp = progress_title + f"\n`0` of `{image_count}` have already been loaded"
|
||||
await ctx.response.send_message(progress_temp, ephemeral = True)
|
||||
|
||||
embed.add_field(
|
||||
name = "Author",
|
||||
value = f"[u/{subm.author}](https://www.reddit.com/user/{subm.author})",
|
||||
inline = True)
|
||||
embed.add_field(
|
||||
name = "Subreddit",
|
||||
value = f"[r/{subm.subreddit.display_name}]({url})",
|
||||
inline = True)
|
||||
if subm.selftext:
|
||||
embed.add_field(
|
||||
name = "Discription",
|
||||
value = subm.selftext,
|
||||
inline = False)
|
||||
if isinstance(quality, app_commands.Choice):
|
||||
quality_value = quality.value
|
||||
else:
|
||||
quality_value = quality
|
||||
|
||||
# Download and convert each image
|
||||
image_files:list[discord.File] = []
|
||||
begin_conversion = datetime.now().timestamp()
|
||||
async with aiohttp.ClientSession() as session:
|
||||
index = 0
|
||||
for image_url in image_urls:
|
||||
async with session.get(image_url) as response:
|
||||
response.raise_for_status()
|
||||
image_data = await response.read()
|
||||
|
||||
original_image = Image.open(BytesIO(image_data))
|
||||
webp_buffer = BytesIO()
|
||||
original_image.save(webp_buffer, format = "WEBP", quality = quality_value)
|
||||
webp_buffer.seek(0)
|
||||
image_file = discord.File(webp_buffer, filename = f"image_{index}.webp")
|
||||
image_files.append(image_file)
|
||||
index += 1
|
||||
|
||||
progress_temp = progress_title + f"\n`{index}` of `{image_count}` have already been loaded"
|
||||
await ctx.edit_original_response(content = progress_temp)
|
||||
|
||||
self._logger.debug(f"Downloaded and converted {len(image_files)} images in {get_elapsed_time_milliseconds(datetime.now().timestamp() - begin_conversion)}")
|
||||
|
||||
author = subm.author.name if subm.author else "Author not found"
|
||||
content = f":copyright: [{author}]({url})"
|
||||
if use_title:
|
||||
content += f"\n# {subm.title}"
|
||||
|
||||
if custom_note:
|
||||
content += f"\n> {custom_note}"
|
||||
|
||||
await ctx.delete_original_response()
|
||||
message = await ctx.followup.send(
|
||||
content = content,
|
||||
suppress_embeds = True,
|
||||
files = image_files
|
||||
)
|
||||
|
||||
self._logger.info(f"Successfully processed the command executed by {ctx.user.name} ({ctx.user.id}) after {get_elapsed_time_milliseconds(datetime.now().timestamp() - begin_process)} (ID of message: {message.id})")
|
||||
|
||||
for image_url in image_urls:
|
||||
embed = discord.Embed(url = "https://discord.com/humans.txt")
|
||||
embed.set_image(url = image_url)
|
||||
embeds.append(embed)
|
||||
# No domain for seperation found
|
||||
case _:
|
||||
if toplevel_domain == "":
|
||||
toplevel_domain = "not_found"
|
||||
|
||||
embed = discord.Embed(
|
||||
title = "Domain not found",
|
||||
description = f"The requested domain `{toplevel_domain}` is currently not supported\nOpen [an issue](https://github.com/official-Cromatin/Post-It/issues/new?assignees=&labels=feature-request&projects=&template=feature_request.yml) to request support for it.\n\nCurrently supported plattforms:\n- Reddit",
|
||||
color = 0xED4337)
|
||||
|
||||
await ctx.response.send_message(embeds = embeds)
|
||||
|
||||
# No domain for seperation found
|
||||
case _:
|
||||
embed = discord.Embed(
|
||||
title = "Domain not found",
|
||||
description = f"The requested domain `{toplevel_domain}` is currently not supported\nOpen [an issue](https://github.com/official-Cromatin/Post-It/issues/new?assignees=&labels=feature-request&projects=&template=feature_request.yml) to request support for it.\n\nSee an list of supported platforms with the `/platforms` command",
|
||||
color = 0xED4337)
|
||||
await ctx.response.send_message(embed = embed, ephemeral = True)
|
||||
|
||||
except NoMediaFound:
|
||||
self._logger.error(f"Aborted issued command by {ctx.user.name} ({ctx.user.id}). Post had no media attatched")
|
||||
|
||||
# Delete the original response, if existing
|
||||
try:
|
||||
await ctx.delete_original_response()
|
||||
except discord.NotFound:
|
||||
pass
|
||||
embed = discord.Embed(
|
||||
title = "No media found",
|
||||
description = "The post had no media attatched, or was in an unsupported format\nVideos are not supported! (yet)",
|
||||
color = 0xED4337
|
||||
)
|
||||
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:
|
||||
self._logger.error(f"Could not complete command by {ctx.user.name} ({ctx.user.id})")
|
||||
self._logger.exception(error, stack_info = True)
|
||||
|
||||
# Delete the original response, if existing
|
||||
try:
|
||||
await ctx.delete_original_response()
|
||||
except discord.NotFound:
|
||||
pass
|
||||
|
||||
# Explain to the user what the error was
|
||||
embed = discord.Embed(
|
||||
title = "Error while processing",
|
||||
description = f"While we processed your request, the following exception occured: `{error}`",
|
||||
color = 0xED4337
|
||||
)
|
||||
if ctx.response.is_done():
|
||||
await ctx.followup.send(embed = embed, ephemeral = True)
|
||||
else:
|
||||
await ctx.response.send_message(embed = embed)
|
||||
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from cogs.base_cog import Base_Cog
|
||||
from cogs.maintenance import Maintenance_Command
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from utils.datetime_tools import get_elapsed_time_milliseconds
|
||||
import traceback
|
||||
from utils.truncate_str import truncate_message_with_notice
|
||||
|
||||
class Reload_Command(Base_Cog):
|
||||
def __init__(self, bot:commands.Bot):
|
||||
self.__bot = bot
|
||||
self.__cached_cog_names:list[str] = None
|
||||
self.__reload_running = False
|
||||
super().__init__(logging.getLogger("cmds.reload"))
|
||||
|
||||
async def autocomplete_cog(self, ctx: discord.Interaction, cog_name_stw:str):
|
||||
"""Helpermethod to recommend the right cog name to the user"""
|
||||
# Cache the cog names if not cached yet
|
||||
if not self.__cached_cog_names:
|
||||
self.__cached_cog_names = list(self.__bot.extensions.keys())
|
||||
|
||||
if cog_name_stw == "":
|
||||
# Return first 25 cog names, if provided name is empty
|
||||
matching_cogs = sorted(self.__cached_cog_names)
|
||||
else:
|
||||
matching_cogs = []
|
||||
for cog_name in self.__cached_cog_names:
|
||||
if cog_name.startswith(cog_name_stw.lower()):
|
||||
matching_cogs.append(cog_name)
|
||||
|
||||
choices = []
|
||||
for matching_cog in matching_cogs[:25]:
|
||||
choices.append(app_commands.Choice(name = matching_cog.removeprefix("cogs.").capitalize(), value = matching_cog))
|
||||
|
||||
return choices
|
||||
|
||||
@app_commands.command(name = "reload_all", description = "Reloads all cogs")
|
||||
@app_commands.check(Maintenance_Command.handle_check)
|
||||
async def reload_all(self, ctx: discord.Interaction):
|
||||
if self.__reload_running:
|
||||
await ctx.response.send_message("Reload is allready being executed", ephemeral = True)
|
||||
else:
|
||||
self.__reload_running = True
|
||||
task_start = datetime.now().timestamp()
|
||||
cog_names = list(self.__bot.extensions.keys())
|
||||
self._logger.debug(f"Reloading all {len(cog_names)} cogs ...")
|
||||
try:
|
||||
embed_cog_stats = "Reloaded the following cogs:\n"
|
||||
for extension_name in cog_names:
|
||||
start_reload = datetime.now().timestamp()
|
||||
await self.__bot.reload_extension(extension_name)
|
||||
embed_cog_stats += f"- {extension_name.removeprefix('cogs.').capitalize()} (`{get_elapsed_time_milliseconds(datetime.now().timestamp() - start_reload)}`)\n"
|
||||
|
||||
except Exception as error:
|
||||
embed_cog_stats += f"- {extension_name.removeprefix('cogs.').capitalize()} <--"
|
||||
traceback_str = truncate_message_with_notice(traceback.format_exc(), 1800, "\nSee console for more details on the full traceback")
|
||||
|
||||
embed = discord.Embed(
|
||||
title = "Reloading All Cogs",
|
||||
description = f"{embed_cog_stats} \n\nDuring the reload the following exception occured:\n```{traceback_str}```",
|
||||
color = 0xED4337)
|
||||
|
||||
raise error
|
||||
else:
|
||||
elapsed_time = get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)
|
||||
embed = discord.Embed(
|
||||
title = "Reloading All Cogs",
|
||||
description = f"{embed_cog_stats}Total time spend: `{elapsed_time}`",
|
||||
color = 0x4BB543)
|
||||
|
||||
self._logger.info(f"Cogs successfully reloaded after {elapsed_time}")
|
||||
finally:
|
||||
await ctx.response.send_message(embed = embed, ephemeral = True)
|
||||
self.__reload_running = False
|
||||
|
||||
|
||||
@app_commands.command(name = "reload", description = "Reload specific cog")
|
||||
@app_commands.check(Maintenance_Command.handle_check)
|
||||
@app_commands.autocomplete(cog_name = autocomplete_cog)
|
||||
async def reload(self, ctx: discord.Interaction, cog_name:str):
|
||||
if self.__reload_running:
|
||||
await ctx.response.send_message("Reload is allready being executed", ephemeral = True)
|
||||
else:
|
||||
self.__reload_running = True
|
||||
task_start = datetime.now().timestamp()
|
||||
self._logger.debug(f"Reloading {cog_name} cog ...")
|
||||
try:
|
||||
await self.__bot.reload_extension(cog_name)
|
||||
except Exception as error:
|
||||
traceback_str = truncate_message_with_notice(traceback.format_exc(), 1800, "\nSee console for more details on the full traceback")
|
||||
embed = discord.Embed(
|
||||
title = f"Reloading `{cog_name.removeprefix('cogs.').capitalize()}` cog",
|
||||
description = f"Reload failed with the following exception:\n```{traceback_str}```",
|
||||
color = 0xED4337)
|
||||
|
||||
raise error
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title = f"Reloading `{cog_name.removeprefix('cogs.').capitalize()}` cog",
|
||||
description = f"Total time spend: `{get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}`",
|
||||
color = 0x4BB543)
|
||||
|
||||
self._logger.info(f"Cog {cog_name} successfully reloaded after {get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}")
|
||||
finally:
|
||||
await ctx.response.send_message(embed = embed, ephemeral = True)
|
||||
self.__reload_running = False
|
||||
|
||||
# Clear the cache
|
||||
self.__cached_cog_names = None
|
||||
|
||||
async def setup(bot:commands.Bot):
|
||||
await bot.add_cog(Reload_Command(bot))
|
||||
+21
-30
@@ -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
|
||||
startup = datetime.now().timestamp()
|
||||
|
||||
# Initialize the logger
|
||||
from utils.logger.custom_logging import Custom_Logger
|
||||
Custom_Logger.initialize()
|
||||
|
||||
import logging
|
||||
app_logger = logging.getLogger("app")
|
||||
startup_logger = logging.getLogger("app.startup")
|
||||
|
||||
from utils.adv_configparser import Advanced_ConfigParser
|
||||
from utils.datetime_tools import get_elapsed_time_smal, get_elapsed_time_big, get_elapsed_time_milliseconds
|
||||
import discord
|
||||
@@ -32,7 +13,23 @@ from utils.portal import Portal
|
||||
import asyncio
|
||||
from typing import Union
|
||||
from platforms.reddit import Reddit_Adapter
|
||||
from cogs.maintenance import Maintenance_Command
|
||||
|
||||
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()
|
||||
base_path = source_path.parents[1]
|
||||
@@ -52,20 +49,18 @@ class MyBot(commands.Bot):
|
||||
|
||||
async def setup_hook(self):
|
||||
# Register cogs to handle commands
|
||||
for cog_name in ["maintenance", "about", "debug", "reload", "post"]:
|
||||
for cog_name in ["debug", "post"]:
|
||||
await self.load_extension(f"cogs.{cog_name}")
|
||||
await self.tree.sync()
|
||||
|
||||
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"""
|
||||
self.__portal.no_succeeded_commands += 1
|
||||
print("Command succeeded")
|
||||
|
||||
async def on_interaction(self, interaction: discord.Interaction):
|
||||
"""Called when an interaction happened"""
|
||||
match interaction.type.name:
|
||||
case discord.InteractionType.application_command.name:
|
||||
print("Interaction with bot", interaction.command.name)
|
||||
self.__portal.no_executed_commands += 1
|
||||
case discord.InteractionType.ping.name:
|
||||
print("App got pinged by discord")
|
||||
@@ -79,29 +74,25 @@ class MyBot(commands.Bot):
|
||||
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"""
|
||||
if not self.__first_on_ready:
|
||||
instance: Maintenance_Command = self.get_cog("Maintenance_Command")
|
||||
instance.enable_global_maintenance()
|
||||
startup_logger.info(f"Beginning startup routine ...")
|
||||
startup_logger.info("Beginning startup routine ...")
|
||||
routine_begin = datetime.now().timestamp()
|
||||
await self.change_presence(status = discord.Status.dnd, activity = discord.CustomActivity("Executing pre startup routine"))
|
||||
|
||||
# Create the adapters for the platforms
|
||||
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"))
|
||||
portal.platforms_config = platforms_config
|
||||
startup_logger.info(f"Loaded platforms config after {get_elapsed_time_milliseconds(datetime.now().timestamp() - task_start)}")
|
||||
|
||||
# Create platforms adapter
|
||||
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"])
|
||||
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)
|
||||
startup_logger.info(f"Startup routine finished after {get_elapsed_time_milliseconds(datetime.now().timestamp() - routine_begin)}")
|
||||
|
||||
instance.disable_gloabal_maintenance()
|
||||
self.__first_on_ready = True
|
||||
else:
|
||||
startup_logger.info("Startup routine allready executed, omitting this execution")
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import praw
|
||||
import praw.models
|
||||
import asyncpraw
|
||||
import asyncpraw.models
|
||||
import logging
|
||||
from utils.event_counter import Event_Counter
|
||||
from utils.datetime_tools import get_elapsed_time_milliseconds
|
||||
from typing import Union
|
||||
from datetime import datetime
|
||||
|
||||
class Reddit_Adapter(praw.Reddit):
|
||||
"""A class that extends and abstracts the functionality of the `praw.Reddit` class by adding
|
||||
class Reddit_Adapter(asyncpraw.Reddit):
|
||||
"""A class that extends and abstracts the functionality of the `asyncpraw.Reddit` class by adding
|
||||
logging and request tracking capabilities."""
|
||||
VERSION = "1.0"
|
||||
number_of_instances = 0
|
||||
@@ -25,11 +24,11 @@ class Reddit_Adapter(praw.Reddit):
|
||||
self.__events = Event_Counter(1000)
|
||||
self.__logger = logging.getLogger(f"pltfm.reddit.{self.__instance_number}")
|
||||
|
||||
def fetch(self, post_url:str) -> praw.models.Submission:
|
||||
async def fetch(self, post_url:str) -> asyncpraw.models.Submission:
|
||||
"""Fetches specified submission (post) and returns it"""
|
||||
start_time = datetime.now().timestamp()
|
||||
self.__events.increment()
|
||||
subm = self.submission(url = post_url)
|
||||
subm = await self.submission(url = post_url)
|
||||
self.__logger.debug(f"Submission for post (URL: {post_url}), successfully fetched after {get_elapsed_time_milliseconds(datetime.now().timestamp() - start_time)}")
|
||||
return subm
|
||||
|
||||
|
||||
Reference in New Issue
Block a user