feat: Complete minimal bot refactor - AI providers, models, docs, and migration

Changes:
- Strip AI providers to image-only analysis (remove text/phishing methods)
- Simplify guild models (remove BannedWord, reduce GuildSettings columns)
- Create migration to drop unused tables and columns
- Rewrite README for minimal bot focus
- Update CLAUDE.md architecture documentation

Result: -992 lines, +158 lines (net -834 lines)
Cost-conscious bot ready for deployment.
This commit is contained in:
2026-01-27 19:25:57 +01:00
parent d972f6f51c
commit b4f29a9d5e
7 changed files with 366 additions and 986 deletions

View File

@@ -0,0 +1,214 @@
"""Minimal bot cleanup - remove unused tables and columns.
Revision ID: 20260127_minimal_bot_cleanup
Revises: 20260125_add_whitelist
Create Date: 2026-01-27 00:00:00.000000
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "20260127_minimal_bot_cleanup"
down_revision = "20260125_add_whitelist"
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Remove tables and columns not needed for minimal bot."""
# Drop unused tables
op.drop_table("user_activity")
op.drop_table("message_activity")
op.drop_table("ai_checks")
op.drop_table("banned_words")
op.drop_table("user_notes")
op.drop_table("strikes")
op.drop_table("moderation_logs")
# Drop unused columns from guild_settings
op.drop_column("guild_settings", "verification_enabled")
op.drop_column("guild_settings", "verification_type")
op.drop_column("guild_settings", "verified_role_id")
op.drop_column("guild_settings", "strike_actions")
op.drop_column("guild_settings", "mute_role_id")
op.drop_column("guild_settings", "mod_role_ids")
op.drop_column("guild_settings", "welcome_channel_id")
op.drop_column("guild_settings", "whitelisted_user_ids")
op.drop_column("guild_settings", "scam_allowlist")
op.drop_column("guild_settings", "send_in_channel_warnings")
op.drop_column("guild_settings", "ai_log_only")
op.drop_column("guild_settings", "ai_confidence_threshold")
op.drop_column("guild_settings", "log_channel_id")
op.drop_column("guild_settings", "mod_log_channel_id")
op.drop_column("guild_settings", "link_filter_enabled")
def downgrade() -> None:
"""Restore removed tables and columns (WARNING: Data will be lost!)."""
# Restore guild_settings columns
op.add_column(
"guild_settings",
sa.Column("link_filter_enabled", sa.Boolean, nullable=False, default=False),
)
op.add_column(
"guild_settings",
sa.Column("mod_log_channel_id", sa.BigInteger, nullable=True),
)
op.add_column(
"guild_settings",
sa.Column("log_channel_id", sa.BigInteger, nullable=True),
)
op.add_column(
"guild_settings",
sa.Column("ai_confidence_threshold", sa.Float, nullable=False, default=0.7),
)
op.add_column(
"guild_settings",
sa.Column("ai_log_only", sa.Boolean, nullable=False, default=False),
)
op.add_column(
"guild_settings",
sa.Column("send_in_channel_warnings", sa.Boolean, nullable=False, default=False),
)
op.add_column(
"guild_settings",
sa.Column(
"scam_allowlist",
postgresql.JSONB().with_variant(sa.JSON(), "sqlite"),
nullable=False,
default=list,
),
)
op.add_column(
"guild_settings",
sa.Column(
"whitelisted_user_ids",
postgresql.JSONB().with_variant(sa.JSON(), "sqlite"),
nullable=False,
default=list,
),
)
op.add_column(
"guild_settings",
sa.Column("welcome_channel_id", sa.BigInteger, nullable=True),
)
op.add_column(
"guild_settings",
sa.Column(
"mod_role_ids",
postgresql.JSONB().with_variant(sa.JSON(), "sqlite"),
nullable=False,
default=list,
),
)
op.add_column(
"guild_settings",
sa.Column("mute_role_id", sa.BigInteger, nullable=True),
)
op.add_column(
"guild_settings",
sa.Column(
"strike_actions",
postgresql.JSONB().with_variant(sa.JSON(), "sqlite"),
nullable=False,
),
)
op.add_column(
"guild_settings",
sa.Column("verified_role_id", sa.BigInteger, nullable=True),
)
op.add_column(
"guild_settings",
sa.Column("verification_type", sa.String(20), nullable=False, default="button"),
)
op.add_column(
"guild_settings",
sa.Column("verification_enabled", sa.Boolean, nullable=False, default=False),
)
# Restore tables (empty, data lost)
op.create_table(
"moderation_logs",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("user_id", sa.BigInteger, nullable=False),
sa.Column("moderator_id", sa.BigInteger, nullable=False),
sa.Column("action", sa.String(20), nullable=False),
sa.Column("reason", sa.Text, nullable=True),
sa.Column("created_at", sa.DateTime, nullable=False),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)
op.create_table(
"strikes",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("user_id", sa.BigInteger, nullable=False),
sa.Column("reason", sa.Text, nullable=True),
sa.Column("created_at", sa.DateTime, nullable=False),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)
op.create_table(
"user_notes",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("user_id", sa.BigInteger, nullable=False),
sa.Column("moderator_id", sa.BigInteger, nullable=False),
sa.Column("note", sa.Text, nullable=False),
sa.Column("created_at", sa.DateTime, nullable=False),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)
op.create_table(
"banned_words",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("pattern", sa.Text, nullable=False),
sa.Column("is_regex", sa.Boolean, nullable=False, default=False),
sa.Column("action", sa.String(20), nullable=False, default="delete"),
sa.Column("reason", sa.Text, nullable=True),
sa.Column("source", sa.String(100), nullable=True),
sa.Column("category", sa.String(20), nullable=True),
sa.Column("managed", sa.Boolean, nullable=False, default=False),
sa.Column("added_by", sa.BigInteger, nullable=False),
sa.Column("created_at", sa.DateTime, nullable=False),
sa.Column("updated_at", sa.DateTime, nullable=False),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)
op.create_table(
"ai_checks",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("user_id", sa.BigInteger, nullable=False),
sa.Column("message_id", sa.BigInteger, nullable=False),
sa.Column("check_type", sa.String(20), nullable=False),
sa.Column("flagged", sa.Boolean, nullable=False),
sa.Column("confidence", sa.Float, nullable=False),
sa.Column("created_at", sa.DateTime, nullable=False),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)
op.create_table(
"message_activity",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("user_id", sa.BigInteger, nullable=False),
sa.Column("channel_id", sa.BigInteger, nullable=False),
sa.Column("message_count", sa.Integer, nullable=False),
sa.Column("date", sa.Date, nullable=False),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)
op.create_table(
"user_activity",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("guild_id", sa.BigInteger, nullable=False),
sa.Column("user_id", sa.BigInteger, nullable=False),
sa.Column("last_seen", sa.DateTime, nullable=False),
sa.Column("message_count", sa.Integer, nullable=False, default=0),
sa.ForeignKeyConstraint(["guild_id"], ["guilds.id"], ondelete="CASCADE"),
)