Compare commits
10 Commits
f0c7a0374f
...
bd0c66e0fd
Author | SHA1 | Date | |
---|---|---|---|
bd0c66e0fd
|
|||
76c09763ce
|
|||
4e052cb511
|
|||
32d8d0aae6
|
|||
0e0ce99a08
|
|||
fb3b127eea
|
|||
b24effaa95
|
|||
63b466c9ab
|
|||
66c0132c1b
|
|||
9ab2ba8aec
|
19
.gitea/workflows/docker-check.yml
Normal file
19
.gitea/workflows/docker-check.yml
Normal file
@ -0,0 +1,19 @@
|
||||
name: Docker Build Check
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
|
||||
jobs:
|
||||
docker-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Docker Build Check
|
||||
run: docker build --check .
|
67
.gitea/workflows/docker-publish.yml
Normal file
67
.gitea/workflows/docker-publish.yml
Normal file
@ -0,0 +1,67 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "@daily"
|
||||
|
||||
cache:
|
||||
enabled: true
|
||||
dir: ""
|
||||
host: "192.168.1.127"
|
||||
port: 8088
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DISCORD_TOKEN: "0"
|
||||
OPENAI_TOKEN: "0"
|
||||
if: gitea.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- uses: https://github.com/docker/setup-qemu-action@v3
|
||||
- uses: https://github.com/docker/setup-buildx-action@v3
|
||||
- uses: https://github.com/astral-sh/ruff-action@v3
|
||||
|
||||
- run: docker build --check .
|
||||
- run: ruff check --exit-non-zero-on-fix --verbose
|
||||
- run: ruff format --check --verbose
|
||||
|
||||
- id: meta
|
||||
uses: https://github.com/docker/metadata-action@v5
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/thelovinator1/anewdawn
|
||||
git.lovinator.space/thelovinator/anewdawn
|
||||
tags: type=raw,value=latest,enable=${{ gitea.ref == format('refs/heads/{0}', 'master') }}
|
||||
|
||||
# GitHub Container Registry
|
||||
- uses: https://github.com/docker/login-action@v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: thelovinator1
|
||||
password: ${{ secrets.PACKAGES_WRITE_GITHUB_TOKEN }}
|
||||
|
||||
# Gitea Container Registry
|
||||
- uses: https://github.com/docker/login-action@v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: git.lovinator.space
|
||||
username: thelovinator
|
||||
password: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- uses: https://github.com/docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ gitea.event_name != 'pull_request' }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
11
.github/SECURITY.md
vendored
11
.github/SECURITY.md
vendored
@ -1,11 +0,0 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
tl;dr: [open a draft security advisory](https://github.com/TheLovinator1/anewdawn/security/advisories/new).
|
||||
|
||||
---
|
||||
|
||||
You can also email me at [tlovinator@gmail.com](mailto:tlovinator@gmail.com).
|
||||
|
||||
I am also available on Discord at `TheLovinator#9276`.
|
||||
|
||||
Thanks :-)
|
36
.github/copilot-instructions.md
vendored
Normal file
36
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# Custom Instructions for GitHub Copilot
|
||||
|
||||
## Project Overview
|
||||
This is a Python project named ANewDawn. It uses Docker for containerization (`Dockerfile`, `docker-compose.yml`). Key files include `main.py` and `settings.py`.
|
||||
|
||||
## Development Environment
|
||||
- **Operating System:** Windows
|
||||
- **Default Shell:** PowerShell (`pwsh.exe`). Please generate terminal commands compatible with PowerShell.
|
||||
|
||||
## Coding Standards
|
||||
- **Linting & Formatting:** We use `ruff` for linting and formatting. Adhere to `ruff` standards. Configuration is in `.github/workflows/ruff.yml` and possibly `pyproject.toml` or `ruff.toml`.
|
||||
- **Python Version:** 3.13
|
||||
- **Dependencies:** Managed using `uv` and listed in `pyproject.toml`. Commands include:
|
||||
- `uv run pytest` for testing.
|
||||
- `uv add <package_name>` for package installation.
|
||||
- `uv sync --upgrade` for dependency updates.
|
||||
- `uv run python main.py` to run the project.
|
||||
|
||||
## General Guidelines
|
||||
- Follow Python best practices.
|
||||
- Write clear, concise code.
|
||||
- Add comments only for complex logic.
|
||||
- Ensure compatibility with the Docker environment.
|
||||
- Use `uv` commands for package management and scripts.
|
||||
- Use `docker` and `docker-compose` for container tasks:
|
||||
- Build: `docker build -t <image_name> .`
|
||||
- Run: `docker run <image_name>` or `docker-compose up`.
|
||||
- Stop/Remove: `docker stop <container_id>` and `docker rm <container_id>`.
|
||||
|
||||
## Discord Bot Functionality
|
||||
- **Chat Interaction:** Responds to messages containing "lovibot" or its mention (`<@345000831499894795>`) using the OpenAI chat API (`gpt-4o-mini`). See `on_message` event handler and `misc.chat` function.
|
||||
- **Slash Commands:**
|
||||
- `/ask <text>`: Directly ask the AI a question. Uses `misc.chat`.
|
||||
- **Context Menu Commands:**
|
||||
- `Enhance Image`: Right-click on a message with an image to enhance it using OpenCV methods (`enhance_image1`, `enhance_image2`, `enhance_image3`).
|
||||
- **User Restrictions:** Interaction is limited to users listed in `misc.get_allowed_users()`. Image creation has additional restrictions.
|
19
.github/workflows/docker-check.yml
vendored
Normal file
19
.github/workflows/docker-check.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Docker Build Check
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
|
||||
jobs:
|
||||
docker-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Docker Build Check
|
||||
run: docker build --check .
|
35
.github/workflows/docker-publish.yml
vendored
35
.github/workflows/docker-publish.yml
vendored
@ -1,35 +0,0 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
DISCORD_TOKEN: "0"
|
||||
OPENAI_TOKEN: "0"
|
||||
if: github.event_name != 'pull_request'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: |
|
||||
ghcr.io/thelovinator1/anewdawn:latest
|
14
Dockerfile
14
Dockerfile
@ -1,9 +1,6 @@
|
||||
# Install uv
|
||||
FROM python:3.13-slim
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
# Install git
|
||||
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
||||
# syntax=docker/dockerfile:1
|
||||
# check=error=true;experimental=all
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim@sha256:73c021c3fe7264924877039e8a449ad3bb380ec89214282301affa9b2f863c5d
|
||||
|
||||
# Change the working directory to the `app` directory
|
||||
WORKDIR /app
|
||||
@ -13,11 +10,8 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --no-install-project
|
||||
|
||||
# Uninstall git to reduce image size
|
||||
RUN apt-get purge -y --auto-remove git
|
||||
|
||||
# Copy the application files
|
||||
ADD main.py misc.py settings.py /app/
|
||||
COPY main.py misc.py settings.py /app/
|
||||
|
||||
# Set the environment variables
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
69
main.py
69
main.py
@ -4,7 +4,7 @@ import datetime
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import discord
|
||||
@ -18,11 +18,6 @@ from openai import OpenAI
|
||||
from misc import chat, get_allowed_users
|
||||
from settings import Settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openai.types import ImagesResponse
|
||||
from openai.types.image import Image
|
||||
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn="https://ebbd2cdfbd08dba008d628dad7941091@o4505228040339456.ingest.us.sentry.io/4507630719401984",
|
||||
send_default_pii=True,
|
||||
@ -51,12 +46,7 @@ class LoviBotClient(discord.Client):
|
||||
self.tree = app_commands.CommandTree(self)
|
||||
|
||||
async def setup_hook(self) -> None:
|
||||
"""Setup the bot client."""
|
||||
# Copy the global commands to all the guilds so we don't have to wait 1 hour for the commands to be available
|
||||
self.tree.copy_global_to(guild=discord.Object(id=98905546077241344)) # KillYoy's server
|
||||
self.tree.copy_global_to(guild=discord.Object(id=341001473661992962)) # TheLovinator's server
|
||||
|
||||
# Sync commands globally
|
||||
"""Sync commands globaly."""
|
||||
await self.tree.sync()
|
||||
|
||||
async def on_ready(self) -> None:
|
||||
@ -189,61 +179,6 @@ async def ask(interaction: discord.Interaction, text: str) -> None:
|
||||
await interaction.followup.send(f"I forgor how to think 💀\nText: {text}")
|
||||
|
||||
|
||||
@client.tree.command(name="create_image", description="Create an image using a prompt.")
|
||||
@app_commands.describe(prompt="The prompt to generate the image.")
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
async def create_image(interaction: discord.Interaction, prompt: str) -> None:
|
||||
"""A command to create an image using the AI."""
|
||||
await interaction.response.defer()
|
||||
|
||||
if not prompt:
|
||||
logger.error("No prompt provided.")
|
||||
await interaction.followup.send("You need to provide a prompt.", ephemeral=True)
|
||||
return
|
||||
|
||||
# Only allow certain users to interact with the bot
|
||||
allowed_users: list[str] = ["thelovinator", "killyoy"]
|
||||
|
||||
user_name_lowercase: str = interaction.user.name.lower()
|
||||
logger.info("Received image creation command from: %s", user_name_lowercase)
|
||||
|
||||
if user_name_lowercase not in allowed_users:
|
||||
logger.info("Ignoring image creation command from: %s", user_name_lowercase)
|
||||
await interaction.followup.send("You are not allowed to use this command.", ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
response: ImagesResponse = openai_client.images.generate(prompt=prompt, model="dall-e-3", quality="hd")
|
||||
data: list[Image] = response.data
|
||||
if not data:
|
||||
await interaction.followup.send("No image data found in the response.")
|
||||
return
|
||||
|
||||
image_url: str | None = data[0].url
|
||||
if not image_url:
|
||||
await interaction.followup.send("No image URL found in the response.")
|
||||
return
|
||||
|
||||
# Download the image with httpx
|
||||
async with httpx.AsyncClient() as client:
|
||||
image_response: httpx.Response = await client.get(image_url)
|
||||
image_response.raise_for_status()
|
||||
|
||||
# Send the image as a file
|
||||
image_bytes: bytes = image_response.content
|
||||
|
||||
iso8601_timestamp: str = datetime.datetime.now(tz=datetime.UTC).isoformat()
|
||||
|
||||
file = discord.File(fp=io.BytesIO(image_bytes), filename=f"image-{iso8601_timestamp}.png")
|
||||
await interaction.followup.send(file=file)
|
||||
|
||||
except openai.OpenAIError as e:
|
||||
logger.exception("An error occurred while creating the image.")
|
||||
await interaction.followup.send(f"An error occurred: {e}")
|
||||
return
|
||||
|
||||
|
||||
type ImageType = np.ndarray[Any, np.dtype[np.integer[Any] | np.floating[Any]]] | cv2.Mat
|
||||
|
||||
|
||||
|
@ -18,13 +18,16 @@ dependencies = [
|
||||
dev = ["pytest", "ruff"]
|
||||
|
||||
[tool.ruff]
|
||||
# https://docs.astral.sh/ruff/linter/
|
||||
preview = true
|
||||
|
||||
# Enable all rules
|
||||
fix = true
|
||||
unsafe-fixes = true
|
||||
lint.select = ["ALL"]
|
||||
lint.fixable = ["ALL"]
|
||||
lint.pydocstyle.convention = "google"
|
||||
lint.isort.required-imports = ["from __future__ import annotations"]
|
||||
lint.pycodestyle.ignore-overlong-task-comments = true
|
||||
line-length = 120
|
||||
|
||||
# Ignore some rules
|
||||
lint.ignore = [
|
||||
"CPY001", # Checks for the absence of copyright notices within Python files.
|
||||
"D100", # Checks for undocumented public module definitions.
|
||||
@ -51,19 +54,8 @@ lint.ignore = [
|
||||
"W191", # Checks for indentation that uses tabs.
|
||||
]
|
||||
|
||||
# https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
|
||||
lint.pydocstyle.convention = "google"
|
||||
|
||||
# Add "from __future__ import annotations" to all files
|
||||
lint.isort.required-imports = ["from __future__ import annotations"]
|
||||
|
||||
lint.pycodestyle.ignore-overlong-task-comments = true
|
||||
|
||||
# Default is 88 characters
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff.format]
|
||||
# https://docs.astral.sh/ruff/formatter/
|
||||
docstring-code-format = true
|
||||
docstring-code-line-length = 20
|
||||
|
||||
@ -78,11 +70,8 @@ docstring-code-line-length = 20
|
||||
|
||||
# https://pytest-django.readthedocs.io/en/latest/
|
||||
[tool.pytest.ini_options]
|
||||
# Enable logging in the console.
|
||||
log_cli = true
|
||||
log_cli_level = "INFO"
|
||||
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
||||
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
# Only test files with the following suffixes.
|
||||
python_files = "test_*.py *_test.py *_tests.py"
|
||||
|
Reference in New Issue
Block a user