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 @@

-
+ Image showing Graven development discord logo
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 | drawing [Gamingdy](https://github.com/Gamingdy), drawing [Lindwen](https://github.com/Lindwen), drawing [Loïc R](https://github.com/Lramelot), drawing [GabHas](https://github.com/TheRealGabHas), drawing [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 [Alessevan](https://github.com/Alessevan), Alexandre-josepavel [Alexandre-josepavel](https://github.com/alexandre-josepavel), Antoinejt [Antoinejt](https://github.com/AntoineJT), Flenderrax [Flenderrax](https://github.com/FlenderrAX), Gamingdy [Gamingdy](https://github.com/gamingdy), Lindwen [Lindwen](https://github.com/Lindwen), Lramelot [Lramelot](https://github.com/Lramelot), Mityno [Mityno](https://github.com/Mityno), Redstom [Redstom](https://github.com/RedsTom), Therealgabhas [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)