Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ BOT_OWNER_COOLDOWN=5


# OpenAI (GPT) API key
BOT_OPENAI_MODEL=gpt-5.5
BOT_OPENAI_MODEL=gpt-6-astra
BOT_OPENAI_API_KEY=

# Anthropic (Claude) API key
BOT_ANTHROPIC_MODEL=claude-opus-4-8
BOT_ANTHROPIC_MODEL=claude-fable-5-1
BOT_ANTHROPIC_API_KEY=

# Google (Gemini) API key
Expand Down
2 changes: 1 addition & 1 deletion docker-compose-localdb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ services:
- postgres_network
command: >
bash -c "
docker-entrypoint.sh postgres &
docker-entrypoint.sh postgres -c log_min_messages=warning -c log_statement=ddl -c log_min_duration_statement=1000 -c log_hostname=off &
until pg_isready -U ${POSTGRESQL_USER} -h localhost; do sleep 1; done;
PGPASSWORD=${POSTGRESQL_PASSWORD} psql -U ${POSTGRESQL_USER} -d postgres -tc \"SELECT 1 FROM pg_database WHERE datname = '${POSTGRESQL_DATABASE}'\" | grep -q 1 || PGPASSWORD=${POSTGRESQL_PASSWORD} psql -U ${POSTGRESQL_USER} -d postgres -c \"CREATE DATABASE ${POSTGRESQL_DATABASE}\";
wait
Expand Down
25 changes: 12 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "DiscordBot"
version = "3.0.19"
version = "3.0.20"
description = "A simple Discord bot with multi-provider AI commands (OpenAI / Claude / Gemini) and server administration tools"
urls.Repository = "https://github.com/ddc/DiscordBot"
urls.Homepage = "https://ddc.github.io/DiscordBot"
Expand All @@ -17,7 +17,6 @@ keywords = [
"bot", "discord",
"DiscordBot", "discord-bot",
"discordpy", "discord-py", "discord-py-bot",

]
classifiers = [
"Topic :: Communications :: Chat",
Expand All @@ -30,34 +29,34 @@ classifiers = [
"Intended Audience :: Developers",
"Natural Language :: English",
]
requires-python = ">=3.14.7"
requires-python = ">=3.14"
dependencies = [
"alembic>=1.19.1",
"anthropic>=0.122.0",
"alembic>=1.20.0",
"anthropic>=1.6.0",
"beautifulsoup4>=4.15.0",
"better-profanity>=0.7.0",
"ddcdatabases[postgres]>=5.0.0",
"ddcdatabases[postgres]>=5.0.1",
"discord-py>=2.7.1",
"google-genai>=2.18.1",
"google-genai>=2.24.0",
"gTTS>=2.5.4",
"openai>=3.2.0",
"openai>=3.16.0",
"PyNaCl>=1.6.2",
"pythonLogs>=7.0.2",
"uuid-utils>=0.17.0",
"uuid-utils>=1.0.0",
]

[dependency-groups]
dev = [
"coverage>=7.15.4",
"coverage>=7.16.1",
"poethepoet>=0.48.0",
"pytest-asyncio>=1.4.0",
"ruff>=0.16.3",
"ruff>=0.16.8",
"testcontainers[postgres]>=4.15.0",
]

[tool.poe.tasks]
linter.shell = "uv run ruff check --fix . && uv run ruff format ."
snyk-export.shell = "uv export --no-hashes --no-annotate --format requirements-txt > requirements.txt && uvx pre-commit run --all-files || uvx pre-commit run --all-files"
snyk-export.shell = "uv export --no-hashes --no-annotate --all-extras --all-groups --format requirements-txt > requirements.txt && uvx pre-commit run --all-files || uvx pre-commit run --all-files"
snyk-container.shell = "docker build -t discordbot:snyk-scan . && snyk container test discordbot:snyk-scan --file=Dockerfile; docker rmi discordbot:snyk-scan"
snyk.sequence = ["snyk-export", { shell = "uv pip install pip && snyk test --file=requirements.txt && snyk code test; uv pip uninstall pip" }, "snyk-container"]
profile = "uv run python -m cProfile -o cprofile_unit.prof -m pytest tests/unit"
Expand Down Expand Up @@ -122,8 +121,8 @@ exclude_lines = [
]

[tool.ruff]
line-length = 120
target-version = "py314"
line-length = 120

[tool.ruff.lint]
select = ["E", "W", "F", "I", "B", "C4", "UP", "S", "SLF"]
Expand Down
4 changes: 3 additions & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ sonar.exclusions=\

sonar.coverage.exclusions=\
.github/**/*,\
tests/**/*
tests/**/*,\
src/database/migrations/**/*,\
**/__init__.py
6 changes: 3 additions & 3 deletions src/database/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ddcdatabases import get_postgresql_settings
from logging.config import fileConfig
from sqlalchemy import create_engine, engine_from_config, pool, text
from sqlalchemy.schema import SchemaItem
from sqlalchemy.schema import CreateSchema, SchemaItem
from src.bot.constants.settings import get_bot_settings
from src.database.models import BotBase
from typing import Any, Literal
Expand Down Expand Up @@ -113,7 +113,7 @@ def run_migrations_offline() -> None:
with context.begin_transaction():
for s in _schemas:
if s != "public":
context.execute(f"CREATE SCHEMA IF NOT EXISTS {s}")
context.execute(CreateSchema(s, if_not_exists=True))
context.run_migrations()


Expand All @@ -133,7 +133,7 @@ def run_migrations_online() -> None:
with connectable.connect() as connection:
for s in _schemas:
if s != "public":
connection.execute(text(f"CREATE SCHEMA IF NOT EXISTS {s}"))
connection.execute(CreateSchema(s, if_not_exists=True))
connection.commit()

context.configure(
Expand Down
5 changes: 3 additions & 2 deletions src/database/migrations/versions/0001_create_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from alembic import op
from collections.abc import Sequence
from ddcdatabases.postgresql import get_postgresql_settings
from sqlalchemy.schema import CreateSchema, DropSchema

revision: str = "0001"
down_revision: str | None = None
Expand All @@ -31,11 +32,11 @@ def upgrade() -> None:
# Create each non-public schema
for s in _schemas:
if s != "public":
op.execute(f"CREATE SCHEMA IF NOT EXISTS {s}")
op.execute(CreateSchema(s, if_not_exists=True))


def downgrade() -> None:
op.execute("DROP FUNCTION IF EXISTS updated_at_column_func")
for s in _schemas:
if s != "public":
op.execute(f"DROP SCHEMA IF EXISTS {s} CASCADE")
op.execute(DropSchema(s, if_exists=True, cascade=True))
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0002_bot_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def upgrade() -> None:
sa.Column("author_id", sa.BigInteger(), server_default=variables.AUTHOR_ID, nullable=False),
sa.Column("url", sa.String(), server_default=variables.BOT_WEBPAGE_URL, nullable=False),
sa.Column("description", sa.String(), server_default=variables.DESCRIPTION, nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
)
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0003_servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def upgrade() -> None:
sa.Column("block_invis_members", sa.Boolean(), server_default="0", nullable=False),
sa.Column("bot_word_reactions", sa.Boolean(), server_default="1", nullable=False),
sa.Column("updated_by", sa.BigInteger(), nullable=True),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
)
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0004_custom_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def upgrade() -> None:
sa.Column("description", sa.String(), nullable=False),
sa.Column("created_by", sa.BigInteger(), nullable=True),
sa.Column("updated_by", sa.BigInteger(), nullable=True),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0005_profanity_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def upgrade() -> None:
sa.Column("channel_id", sa.BigInteger(), nullable=False),
sa.Column("channel_name", sa.String(), nullable=False),
sa.Column("created_by", sa.BigInteger(), nullable=True),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0006_dice_rolls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def upgrade() -> None:
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("roll", sa.Integer(), nullable=False),
sa.Column("dice_size", sa.Integer(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0007_gw2_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def upgrade() -> None:
sa.Column("server", sa.String(), nullable=False),
sa.Column("permissions", sa.String(), nullable=False),
sa.Column("key", sa.String(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
sa.UniqueConstraint("user_id"),
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0008_gw2_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def upgrade() -> None:
sa.Column("server_id", sa.BigInteger(), nullable=False),
sa.Column("session", sa.Boolean(), server_default="0", nullable=False),
sa.Column("updated_by", sa.BigInteger(), nullable=True),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.ForeignKeyConstraint(["server_id"], ["public.servers.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0009_gw2_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def upgrade() -> None:
sa.Column("acc_name", sa.String(), nullable=False),
sa.Column("start", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column("end", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
schema="gw2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def upgrade() -> None:
sa.Column("profession", sa.String(), nullable=False),
sa.Column("start", sa.Integer(), nullable=False),
sa.Column("end", sa.Integer(), nullable=True),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.ForeignKeyConstraint(
["session_id"],
["gw2.gw2_sessions.id"],
Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/versions/0011_embed_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def upgrade() -> None:
sa.Column("author_id", sa.BigInteger(), nullable=False),
sa.Column("current_page", sa.Integer(), server_default="0", nullable=False),
sa.Column("pages", JSONB(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.timezone("utc", sa.func.now()), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id"),
sa.UniqueConstraint("message_id"),
Expand Down
7 changes: 3 additions & 4 deletions src/database/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import importlib
from datetime import datetime
from pathlib import Path
from sqlalchemy import DateTime
from sqlalchemy import DateTime, func
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.sql import text


class BotBase(AsyncAttrs, DeclarativeBase):
"""Base model class with common timestamp fields."""

updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("(now() at time zone 'utc')"))
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("(now() at time zone 'utc')"))
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.timezone("utc", func.now()))
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.timezone("utc", func.now()))


# Auto-import all model modules to register them with SQLAlchemy
Expand Down
2 changes: 0 additions & 2 deletions tests/unit/bot/constants/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ def test_partial_env_var_overrides(self):
assert settings.admin_cooldown == 35

# Default values for non-overridden fields
assert settings.openai_model == "gpt-5.5"
# Note: openai_api_key might have a value from actual env, so we'll check it's set
assert settings.embed_color == "green"
assert settings.config_cooldown == 20
assert settings.owner_cooldown == 5
Expand Down
Loading
Loading