Changed action #5
24
.gitea/workflows/cleanup_registry.yaml
Normal file
24
.gitea/workflows/cleanup_registry.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Cleanup old Docker images
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * *' # Täglich um 6 Uhr
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install crane (OCI Registry tool)
|
||||||
|
run: |
|
||||||
|
curl -LO https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz
|
||||||
|
tar -xzf go-containerregistry_Linux_x86_64.tar.gz
|
||||||
|
chmod +x crane
|
||||||
|
sudo mv crane /usr/local/bin/
|
||||||
|
|
||||||
|
- name: List Docker tags
|
||||||
|
run: |
|
||||||
|
crane ls git.0day.agency/${{ secrets.REGISTRY_USERNAME }}/novelai-discord-bot \
|
||||||
|
--username ${{ secrets.REGISTRY_USERNAME }} \
|
||||||
|
--password ${{ secrets.REGISTRY_TOKEN }}
|
||||||
80
src/main.py
80
src/main.py
@ -1,7 +1,9 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import random
|
import random
|
||||||
import zipfile
|
import zipfile
|
||||||
|
import logging
|
||||||
import discord
|
import discord
|
||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
@ -11,6 +13,14 @@ NOVELAI_API_TOKEN = os.getenv("NOVELAI_API_TOKEN")
|
|||||||
BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN")
|
BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN")
|
||||||
ALLOWED_CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID", "0")) # falls nicht gesetzt → 0
|
ALLOWED_CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID", "0")) # falls nicht gesetzt → 0
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
stream=sys.stdout,
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if not all([NOVELAI_API_TOKEN, BOT_TOKEN, ALLOWED_CHANNEL_ID]):
|
if not all([NOVELAI_API_TOKEN, BOT_TOKEN, ALLOWED_CHANNEL_ID]):
|
||||||
raise ValueError("Environment-Variablen NOVELAI_API_TOKEN, DISCORD_BOT_TOKEN und DISCORD_CHANNEL_ID müssen gesetzt sein.")
|
raise ValueError("Environment-Variablen NOVELAI_API_TOKEN, DISCORD_BOT_TOKEN und DISCORD_CHANNEL_ID müssen gesetzt sein.")
|
||||||
|
|
||||||
@ -19,33 +29,58 @@ bot = commands.Bot(command_prefix=commands.when_mentioned, intents=intents)
|
|||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
print(f"Bot gestartet als {bot.user}")
|
print(f"Bot started as {bot.user}")
|
||||||
try:
|
try:
|
||||||
synced = await bot.tree.sync()
|
synced = await bot.tree.sync()
|
||||||
print(f"{len(synced)} Slash-Commands synchronisiert.")
|
logging.info(f"{len(synced)} Slash-Commands synchronized.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
logging.error(e)
|
||||||
|
|
||||||
@bot.tree.command(name="generate", description="Generiere ein Bild mit NovelAI v4")
|
@bot.tree.command(name="generate", description="Generate image with NovelAI v4 Full")
|
||||||
@app_commands.describe(
|
@app_commands.describe(
|
||||||
prompt="Was soll dargestellt werden?",
|
prompt="What should be generated?",
|
||||||
undesired_prompt="Was soll vermieden werden? (optional)",
|
undesired_prompt="What should be avoided? (optional)",
|
||||||
orientation="portrait oder landscape (Standard: portrait)"
|
orientation="portrait or landscape (Standard: portrait)",
|
||||||
|
seed="Optional seed (integer). If empty, a random one will be generated"
|
||||||
)
|
)
|
||||||
async def generate(
|
async def generate(
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
undesired_prompt: str = "",
|
undesired_prompt: str = "",
|
||||||
orientation: str = "portrait"
|
orientation: str = "portrait",
|
||||||
|
seed: int = None
|
||||||
):
|
):
|
||||||
|
|
||||||
|
default_negative = (
|
||||||
|
"blurry, lowres, error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, "
|
||||||
|
"very displeasing, chromatic aberration, multiple views, logo, too many watermarks, white blank page, "
|
||||||
|
"blank page, watermarks, watermark, text, "
|
||||||
|
)
|
||||||
|
|
||||||
if interaction.channel.id != ALLOWED_CHANNEL_ID:
|
if interaction.channel.id != ALLOWED_CHANNEL_ID:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
"Dieser Befehl ist in diesem Kanal nicht erlaubt.",
|
"This command isn't allowed here.",
|
||||||
ephemeral=True
|
ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
MAX_PROMPT_LENGTH = 500
|
||||||
|
|
||||||
|
if len(prompt) > MAX_PROMPT_LENGTH:
|
||||||
|
await interaction.followup.send(f"Prompt too long! Maximum {MAX_PROMPT_LENGTH} chars allowed.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(default_negative + undesired_prompt.strip()) > MAX_PROMPT_LENGTH:
|
||||||
|
await interaction.followup.send(f"Negative Prompt too long! Maximum {MAX_PROMPT_LENGTH} chars allowed.")
|
||||||
|
return
|
||||||
|
|
||||||
await interaction.response.defer(thinking=True)
|
await interaction.response.defer(thinking=True)
|
||||||
|
logger.info(
|
||||||
|
"User %s (%s, %s) requested image",
|
||||||
|
interaction.user.display_name,
|
||||||
|
interaction.user.name,
|
||||||
|
interaction.user.id
|
||||||
|
)
|
||||||
|
|
||||||
# Auflösung wählen
|
# Auflösung wählen
|
||||||
if orientation.lower() == "landscape":
|
if orientation.lower() == "landscape":
|
||||||
@ -53,18 +88,8 @@ async def generate(
|
|||||||
else:
|
else:
|
||||||
width, height = 832, 1216
|
width, height = 832, 1216
|
||||||
|
|
||||||
default_negative = (
|
if seed is None:
|
||||||
"blurry, lowres, error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, "
|
seed = random.randint(0, 2**32 - 1)
|
||||||
"very displeasing, chromatic aberration, multiple views, logo, too many watermarks, white blank page, "
|
|
||||||
"blank page, "
|
|
||||||
)
|
|
||||||
negative = undesired_prompt.strip() or (
|
|
||||||
"blurry, lowres, error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, "
|
|
||||||
"very displeasing, chromatic aberration, multiple views, logo, too many watermarks, white blank page, "
|
|
||||||
"blank page"
|
|
||||||
)
|
|
||||||
|
|
||||||
seed = random.randint(0, 2**32 - 1)
|
|
||||||
|
|
||||||
# API-Payload zusammenbauen
|
# API-Payload zusammenbauen
|
||||||
payload = {
|
payload = {
|
||||||
@ -93,7 +118,7 @@ async def generate(
|
|||||||
"use_coords": False,
|
"use_coords": False,
|
||||||
"legacy_uc": False,
|
"legacy_uc": False,
|
||||||
"normalize_reference_strength_multiple": True,
|
"normalize_reference_strength_multiple": True,
|
||||||
#"seed": seed,
|
"seed": seed,
|
||||||
"characterPrompts": [],
|
"characterPrompts": [],
|
||||||
"v4_prompt": {
|
"v4_prompt": {
|
||||||
"caption": {
|
"caption": {
|
||||||
@ -132,9 +157,9 @@ async def generate(
|
|||||||
response_bytes = response.content
|
response_bytes = response.content
|
||||||
|
|
||||||
param_text = f"""```Prompt: {prompt}
|
param_text = f"""```Prompt: {prompt}
|
||||||
Negativer Prompt: {negative}
|
Undesired prompt: {default_negative + undesired_prompt.strip()}
|
||||||
Seed: {seed}
|
Seed: {seed}
|
||||||
Auflösung: {width}x{height}
|
Resolution: {width}x{height}
|
||||||
Sampler: k_euler_ancestral
|
Sampler: k_euler_ancestral
|
||||||
Steps: 28
|
Steps: 28
|
||||||
Scale: 3.7
|
Scale: 3.7
|
||||||
@ -155,7 +180,7 @@ Model: nai-diffusion-4-full```"""
|
|||||||
await interaction.followup.send(content=param_text, file=file)
|
await interaction.followup.send(content=param_text, file=file)
|
||||||
else:
|
else:
|
||||||
await interaction.followup.send(
|
await interaction.followup.send(
|
||||||
"ZIP erhalten, aber kein PNG-Bild gefunden."
|
"Found no PNG inside archive"
|
||||||
)
|
)
|
||||||
|
|
||||||
elif response_bytes[:4] == b'\x89PNG':
|
elif response_bytes[:4] == b'\x89PNG':
|
||||||
@ -166,7 +191,7 @@ Model: nai-diffusion-4-full```"""
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
await interaction.followup.send(
|
await interaction.followup.send(
|
||||||
"Die API hat Daten gesendet, aber es war weder ein Bild noch ein gültiges ZIP-Archiv."
|
"API didn't send any file"
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -175,11 +200,10 @@ Model: nai-diffusion-4-full```"""
|
|||||||
except Exception:
|
except Exception:
|
||||||
error_data = {"error": response.text}
|
error_data = {"error": response.text}
|
||||||
|
|
||||||
error_message = f"Fehler {response.status_code} bei der API-Anfrage.\n"
|
error_message = f"Error {response.status_code} at API-request.\n"
|
||||||
for key, value in error_data.items():
|
for key, value in error_data.items():
|
||||||
error_message += f"**{key}**: {value}\n"
|
error_message += f"**{key}**: {value}\n"
|
||||||
|
|
||||||
await interaction.followup.send(error_message)
|
await interaction.followup.send(error_message)
|
||||||
|
|
||||||
|
|
||||||
bot.run(BOT_TOKEN)
|
bot.run(BOT_TOKEN)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user