diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ae1ecfe..ce02597 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,14 +6,19 @@ updates:
interval: "weekly"
cooldown:
default-days: 7
-
+
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
cooldown:
default-days: 7
-
+ ignore:
+ - dependency-name: "python"
+ update-types:
+ - "version-update:semver-minor"
+ - "version-update:semver-major"
+
- package-ecosystem: "pip"
directory: "/"
schedule:
diff --git a/.github/scripts/update_readme.py b/.github/scripts/update_readme.py
new file mode 100644
index 0000000..f50c1ca
--- /dev/null
+++ b/.github/scripts/update_readme.py
@@ -0,0 +1,178 @@
+import json
+import os
+import re
+import subprocess
+import urllib.request
+from pathlib import Path
+
+README = Path("README.md")
+
+CONTRIBUTORS_START = ""
+CONTRIBUTORS_END = ""
+
+VERSION_START = ""
+VERSION_END = ""
+
+GITHUB_REPOSITORY = os.environ["GITHUB_REPOSITORY"]
+
+
+def github_api(url: str):
+ request = urllib.request.Request(
+ url,
+ headers={
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2026-03-10",
+ "User-Agent": "README-metadata-updater",
+ },
+ )
+
+ with urllib.request.urlopen(request) as response:
+ return json.load(response)
+
+
+def get_contributors():
+ """Fetch all repository collaborators, handling pagination."""
+ contributors = []
+ page = 1
+
+ while True:
+ url = (
+ f"https://api.github.com/repos/"
+ f"{GITHUB_REPOSITORY}/contributors"
+ f"?per_page=100&page={page}"
+ )
+
+ users = github_api(url)
+
+ if not users:
+ break
+
+ contributors.extend(users)
+
+ if len(users) < 100:
+ break
+
+ page += 1
+
+ return contributors
+
+
+def generate_contributors(contributors):
+ """Generate the Markdown for the contributors."""
+ contributors.sort(key=lambda user: user["login"].lower())
+
+ entries = []
+
+ for user in contributors:
+ if user.get("type") == "Bot":
+ continue
+ login = user["login"].capitalize()
+ avatar_url = user["avatar_url"]
+ profile_url = user["html_url"]
+
+ entries.append(
+ f'
'
+ f"[{login}]({profile_url})"
+ )
+
+ return ", ".join(entries)
+
+
+def get_latest_tag():
+ """
+ Return the highest version tag according to Git's version sorting.
+
+ Examples:
+ v1.10.0 > v1.9.0
+ v2.0.0 > v1.99.0
+ """
+
+ result = subprocess.run(
+ [
+ "git",
+ "tag",
+ "--sort=-v:refname",
+ ],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+
+ tags = [tag.strip() for tag in result.stdout.splitlines() if tag.strip()]
+
+ if not tags:
+ raise RuntimeError("No Git tags found.")
+
+ return tags[0]
+
+
+def replace_section(
+ content: str,
+ start_marker: str,
+ end_marker: str,
+ replacement: str,
+):
+ """Replace the content between two README markers."""
+
+ pattern = re.escape(start_marker) + r".*?" + re.escape(end_marker)
+
+ replacement_text = f"{start_marker} {replacement} {end_marker}"
+
+ updated, count = re.subn(
+ pattern,
+ replacement_text,
+ content,
+ count=1,
+ flags=re.DOTALL,
+ )
+
+ if count != 1:
+ raise RuntimeError(
+ f"Could not find README markers:\n" f"{start_marker}\n" f"...\n" f"{end_marker}"
+ )
+
+ return updated
+
+
+def main():
+ if not README.exists():
+ raise FileNotFoundError("README.md not found.")
+
+ content = README.read_text(encoding="utf-8")
+
+ # Fetch contributors once.
+ contributors_data = get_contributors()
+ contributors = generate_contributors(contributors_data)
+
+ # Get the latest version tag.
+ latest_tag = get_latest_tag()
+
+ # Update contributors.
+ content = replace_section(
+ content,
+ CONTRIBUTORS_START,
+ CONTRIBUTORS_END,
+ contributors,
+ )
+
+ # Update version.
+ content = replace_section(
+ content,
+ VERSION_START,
+ VERSION_END,
+ latest_tag,
+ )
+
+ README.write_text(
+ content,
+ encoding="utf-8",
+ )
+
+ print(f"Found {len(contributors_data)} contributors.")
+ print(f"Latest tag: {latest_tag}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml
new file mode 100644
index 0000000..78dbab9
--- /dev/null
+++ b/.github/workflows/update-readme.yml
@@ -0,0 +1,52 @@
+name: Update README metadata
+
+on:
+ push:
+ tags:
+ - "*"
+
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ update-readme:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout default branch
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ ref: ${{ github.event.repository.default_branch }}
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Fetch all tags
+ run: git fetch --tags --force
+
+ - name: Set up Python
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
+ with:
+ python-version: "3.x"
+
+ - name: Update README
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ run: python .github/scripts/update_readme.py
+
+ - name: Commit README changes
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ git add README.md
+
+ if git diff --cached --quiet; then
+ echo "README is already up to date."
+ exit 0
+ fi
+
+ git commit -m "docs: update README metadata"
+
+ git push origin HEAD:${{ github.event.repository.default_branch }}
\ No newline at end of file
diff --git a/README.md b/README.md
index 03d3896..0478542 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
- 
+ 
DJ4H
This project is under the GNU GPL v3 license
@@ -17,11 +17,11 @@ Most of the contributors are part of the staff but the members are also allowed
## Global information
-| Global information | |
-|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Description | DJ4H is a Discord bot designed to enhance server engagement. It includes features for tracking activity engagement, providing a dynamic and interactive experience for communities. |
-| Collaborators |
[Gamingdy](https://github.com/Gamingdy),
[Lindwen](https://github.com/Lindwen),
[Loïc R](https://github.com/Lramelot),
[GabHas](https://github.com/TheRealGabHas),
[AntoineJT](https://github.com/AntoineJT) |
-| Version | 1.0.0 |
+| Global information | |
+|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Description | DJ4H is a Discord bot designed to enhance server engagement. It includes features for tracking activity engagement, providing a dynamic and interactive experience for communities. |
+| Contributors |
[Alessevan](https://github.com/Alessevan),
[Alexandre-josepavel](https://github.com/alexandre-josepavel),
[Antoinejt](https://github.com/AntoineJT),
[Flenderrax](https://github.com/FlenderrAX),
[Gamingdy](https://github.com/gamingdy),
[Lindwen](https://github.com/Lindwen),
[Lramelot](https://github.com/Lramelot),
[Mityno](https://github.com/Mityno),
[Redstom](https://github.com/RedsTom),
[Therealgabhas](https://github.com/TheRealGabHas) |
+| Version | v1.5.5 |
---
@@ -59,18 +59,18 @@ Required command parameters are shown in brackets (`[required]`) while optional
### Configuration (Admin only)
-| Command | Description |
-| --- | --- |
+| Command | Description |
+|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `/config [channel] [delay]` | Sets the game channel and the required delay to score a point. Time prefixes: `s` (seconds), `m` (minutes), `h` (hours), `d` (days). |
-| `/set [member] [score]` | Sets a member's score to the specified value. |
-| `/dump_log` | Retrieves the bot's log file. |
+| `/set [member] [score]` | Sets a member's score to the specified value. |
+| `/dump_log` | Retrieves the bot's log file. |
### Player
-| Command | Description |
-| --- | --- |
+| Command | Description |
+|----------------|--------------------------------------------|
| `/leaderboard` | Displays an image with the top 10 players. |
-| `/score` | Shows your own score. |
+| `/score` | Shows your own score. |
## Installation
@@ -97,7 +97,8 @@ Required command parameters are shown in brackets (`[required]`) while optional
cp .env.example .env
```
-Then fill in `BOT_TOKEN`. Every variable is documented in `.env.example`; the commented-out ones are optional and show their default value.
+Then fill in `BOT_TOKEN`. Every variable is documented in `.env.example`; the commented-out ones are optional and show
+their default value.
## Usage
@@ -116,6 +117,7 @@ docker compose -f compose.prod.yaml up -d
```
Make a deployment
+
```bash
# clone repository
git checkout master
@@ -148,7 +150,7 @@ DJ4H/
├── config.py # Configuration and logging
├── commands/ # Discord commands
│ ├── cogs/game.py # /leaderboard and /score commands
-�� └── handler/events.py # Main game logic
+│ └── handler/events.py # Main game logic
├── utils/
│ ├── database/ # Database layer
│ │ ├── connection.py # SQLite connection
diff --git a/alembic/versions/534babd298b0_fix_guild_id_and_user_id_primary_.py b/alembic/versions/534babd298b0_fix_guild_id_and_user_id_primary_.py
new file mode 100644
index 0000000..c441904
--- /dev/null
+++ b/alembic/versions/534babd298b0_fix_guild_id_and_user_id_primary_.py
@@ -0,0 +1,55 @@
+"""Fix guild_id and user_id primary between them for jd4h
+
+Revision ID: 534babd298b0
+Revises: c818d80153fa
+Create Date: 2026-09-30 23:29:56.655212
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = '534babd298b0'
+down_revision: Union[str, Sequence[str], None] = 'c818d80153fa'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ op.create_table(
+ "users_new",
+ sa.Column("user_id", sa.BigInteger(), nullable=False),
+ sa.Column("guild_id", sa.BigInteger(), nullable=False),
+ sa.Column("score", sa.BigInteger(), nullable=False),
+ sa.PrimaryKeyConstraint("user_id", "guild_id"),
+ )
+
+ op.execute("""
+ INSERT INTO users_new (user_id, guild_id, score)
+ SELECT user_id, guild_id, score
+ FROM users
+ """)
+
+ op.drop_table("users")
+ op.rename_table("users_new", "users")
+
+
+def downgrade() -> None:
+ op.create_table(
+ "users_old",
+ sa.Column("user_id", sa.BigInteger(), nullable=False),
+ sa.Column("guild_id", sa.BigInteger(), nullable=False),
+ sa.Column("score", sa.BigInteger(), nullable=False),
+ sa.PrimaryKeyConstraint("user_id"),
+ )
+
+ op.execute("""
+ INSERT INTO users_old (user_id, guild_id, score)
+ SELECT user_id, guild_id, score
+ FROM users
+ """)
+
+ op.drop_table("users")
+ op.rename_table("users_old", "users")
diff --git a/config.py b/config.py
index e8deb9c..fe0e4fe 100644
--- a/config.py
+++ b/config.py
@@ -11,11 +11,10 @@
env_debug_guild_id = os.getenv("DEBUG_GUILD_ID", None)
try:
- DEBUG_GUILD_ID = int(env_debug_guild_id) if env_debug_guild_id is not None else 0
+ DEBUG_GUILD_ID = [int(env_debug_guild_id)] if env_debug_guild_id is not None else 0
except ValueError:
raise ValueError("DEBUG_GUILD_ID must be an integer representing a guild ID.")
-
DATABASE_PATH = os.getenv("DATABASE_PATH", "dj4h.db")
MAGIC_COLOR = 5220337
# RNGdle sync interval in seconds (default 12 * 60 * 60s = 12 hours)
diff --git a/main.py b/main.py
index 069df7b..abd7afa 100644
--- a/main.py
+++ b/main.py
@@ -14,7 +14,7 @@
bot = discord.AutoShardedBot(
intents=discord.Intents.default(),
help_command=None, # Disable the default help command
- debug_guilds=[DEBUG_GUILD_ID] if DEBUG_GUILD_ID else None,
+ debug_guilds=DEBUG_GUILD_ID if DEBUG_GUILD_ID else None,
)
diff --git a/utils/database/schema.py b/utils/database/schema.py
index ffbbead..b4163f9 100644
--- a/utils/database/schema.py
+++ b/utils/database/schema.py
@@ -24,7 +24,7 @@ class User(Base):
__tablename__ = "users"
user_id = Column(BigInteger, primary_key=True, nullable=False)
- guild_id = Column(BigInteger, nullable=False)
+ guild_id = Column(BigInteger, primary_key=True, nullable=False)
score = Column(BigInteger, nullable=False)