Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
171 changes: 171 additions & 0 deletions .github/workflows/check-cogs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@

# For tests with nektos/act use:
# act -e .\.github\workflows\fixtures\push.json -W .\.github\workflows\check-cogs.yml --container-options "-v /c/privat/codex/d-cogs/.artifacts/dist:/tmp/dist:ro" --secret-file .secrets

# To test with local Red-DiscordBot build artifact, mount the host folder containing the built wheels to /tmp/dist in the container.
# docker run -it --rm -v /c/privat/codex/d-cogs/.artifacts/dist:/tmp/dist python:3.11 bash
name: "Check Cogs"

on:
pull_request:

env:
BUILD_ARTIFACT_NAME: "my-build-artifact"
COG_PATHS: "dworld" # comma-separated list of cog folder names
RPC_PORT: "6133"

jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# DEMO: how to install Red-DiscordBot, can install from PyPI directly!
- name: Build Red-DiscordBot
uses: nntin/d-flows/actions/build-red-discordbot@v1
with:
red_commit: "" # optional commit SHA
artifact_name: ${{ env.BUILD_ARTIFACT_NAME }} # optional artifact name

test-cogs:
runs-on: ubuntu-latest
needs: install
steps:
- name: Checkout repository
uses: actions/checkout@v4

# Skip artifact_name if installing from PyPI directly
- name: Install Red-DiscordBot
uses: nntin/d-flows/actions/install-red-discordbot@v1
with:
artifact_name: ${{ env.BUILD_ARTIFACT_NAME }} # same artifact name used for build

# Configure Red-DiscordBot with instance name "tinkerer"
# todo: add input for skipping --dry-run
- name: Configure Red-DiscordBot
uses: nntin/d-flows/actions/setup-red-discordbot@v1
with:
token: ${{ secrets.DISCORD_BOT_TOKEN }}
optional_args: "--no-cogs" # Example optional argument to run without loading any cogs
# --dry-run fails due to bug in Red-DiscordBot https://github.com/Cog-Creators/Red-DiscordBot/issues/6572
continue-on-error: true

# For dworld cog d-back and wtforms are required
- name: Install dependencies
run: |
uv pip install d-back wtforms --system

# Actual magic: test that cogs can be loaded/unloaded via RPC
- name: Test cogs via RPC
uses: nntin/d-flows/actions/test-red-discordbot@v1
with:
token: ${{ secrets.DISCORD_BOT_TOKEN }}
cog_paths: ${{ env.COG_PATHS }}
rpc_port: ${{ env.RPC_PORT }}

##################################################################
#### Build validation results for Discord notification ####
##################################################################
build-validation-fields:
name: Build Validation Fields
runs-on: ubuntu-latest
needs: [install, test-cogs]
if: always()
outputs:
discord_fields: ${{ steps.discord_fields.outputs.fields }}
validation_result: ${{ steps.validation_result.outputs.result }}

steps:
- name: Determine validation result
id: validation_result
run: |
BUILD_STATUS="${{ needs.install.result }}"
TEST_STATUS="${{ needs['test-cogs'].result }}"

if [[ "$BUILD_STATUS" == "success" && "$TEST_STATUS" == "success" ]]; then
echo "result=success" >> "$GITHUB_OUTPUT"
else
echo "result=failed" >> "$GITHUB_OUTPUT"
fi

- name: Build Discord Fields
id: discord_fields
run: |
BUILD_STATUS="${{ needs.install.result }}"
TEST_STATUS="${{ needs['test-cogs'].result }}"

BUILD_EMOJI=$([[ "$BUILD_STATUS" == "success" ]] && echo "✅" || echo "❌")
TEST_EMOJI=$([[ "$TEST_STATUS" == "success" ]] && echo "✅" || echo "❌")

FIELDS=$(jq -n \
--arg build_status "$BUILD_EMOJI $BUILD_STATUS" \
--arg test_status "$TEST_EMOJI $TEST_STATUS" \
--arg cogs "${{ env.COG_PATHS }}" \
--arg artifact "${{ env.BUILD_ARTIFACT_NAME }}" \
--arg actor "${{ github.actor }}" \
--arg pr_number "${{ github.event.pull_request.number }}" \
--arg pr_url "${{ github.event.pull_request.html_url }}" \
--arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'[
{"name": "Build Red-DiscordBot", "value": $build_status, "inline": true},
{"name": "Cog Tests", "value": $test_status, "inline": true},
{"name": "Cogs Tested", "value": $cogs, "inline": true},
{"name": "Artifact", "value": $artifact, "inline": true},
{"name": "PR Details", "value": ("[#" + $pr_number + "](" + $pr_url + ") by @" + $actor), "inline": false},
{"name": "Run Details", "value": ("[View Full Run](" + $run_url + ")"), "inline": false}
]')

delimiter="$(openssl rand -hex 8)"
{
echo "fields<<${delimiter}"
echo "$FIELDS"
echo "${delimiter}"
} >> "$GITHUB_OUTPUT"

##################################################################
#### Display validation results via Step Summary ####
##################################################################
display-validation-summary:
name: Display Validation Summary
runs-on: ubuntu-latest
needs: [install, test-cogs, build-validation-fields]
if: always()

steps:
- name: Write Step Summary
uses: nntin/d-flows/actions/step-summary@v1
with:
title: 'Cog Validation Results'
markdown: |
| Stage | Status |
|-------|--------|
| Build Red-DiscordBot | ${{ needs.install.result == 'success' && '✅ Passed' || '❌ Failed' }} |
| Cog Tests | ${{ needs['test-cogs'].result == 'success' && '✅ Passed' || '❌ Failed' }} |

**Artifact**: `${{ env.BUILD_ARTIFACT_NAME }}`

**Cogs Tested**: `${{ env.COG_PATHS }}`

**Overall Result**: ${{ needs.build-validation-fields.outputs.validation_result == 'success' && '✅ All checks passed' || '⚠️ Some checks failed or were skipped' }}
overwrite: false

##################################################################
#### Display validation results via Discord ####
##################################################################
notify-cog-validation:
name: Notify Validation Results
runs-on: ubuntu-latest
needs: [build-validation-fields, display-validation-summary]
if: always()

steps:
- name: Send Discord Notification
uses: nntin/d-flows/actions/discord-notify@v1
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
message_type: 'embed'
title: ${{ needs.build-validation-fields.outputs.validation_result == 'success' && '✅ Cog Validation Completed Successfully' || '⚠️ Cog Validation Completed with Issues' }}
description: >-
${{ needs.build-validation-fields.outputs.validation_result == 'success' && format('All cogs ({0}) passed build and RPC validation.', env.COG_PATHS) || format('One or more stages failed for cogs: {0}. Review the workflow logs.', env.COG_PATHS) }}
color: ${{ needs.build-validation-fields.outputs.validation_result == 'success' && '3066993' || '16776960' }}
fields: ${{ needs.build-validation-fields.outputs.discord_fields }}
30 changes: 28 additions & 2 deletions ampremover/ampremover.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import aiohttp
import asyncio
from .dashboard_integration import DashboardIntegration
import time

class AmputatorBot(DashboardIntegration, commands.Cog):
"""Cog to convert AMP URLs to canonical forms using the AmputatorBot API.
Expand All @@ -14,8 +15,17 @@ class AmputatorBot(DashboardIntegration, commands.Cog):

def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=492089091320446976) # Use a unique identifier for your cog
self.config.register_guild(opted_in=False) # Register a guild-specific variable for opted-in status
self.config = Config.get_conf(self, identifier=492089091320446976)
# Register a guild-specific variable for opted-in status and basic stats
self.config.register_guild(
opted_in=False,
stats={
"total_conversions": 0,
"total_urls_detected": 0,
"total_canonical_returned": 0,
"last_conversion_ts": 0,
},
)
self.opted_in_users = set()

async def initialize_config(self, guild):
Expand Down Expand Up @@ -54,6 +64,9 @@ async def convert_amp(self, ctx, *, message: str):
return

canonical_links = await self.fetch_canonical_links(urls)
# Update stats if invoked in a guild context
if ctx.guild is not None:
await self._update_guild_stats(ctx.guild, urls_detected=len(urls), canonical_returned=len(canonical_links))
if canonical_links:
if ctx.guild: # If in a server, respond in the channel
await ctx.send(f"Canonical URL(s): {'; '.join(canonical_links)}")
Expand Down Expand Up @@ -91,6 +104,8 @@ async def on_message(self, message):
urls = self.extract_urls(message.content)
if urls:
canonical_links = await self.fetch_canonical_links(urls)
# Update stats for guild automatic conversion checks
await self._update_guild_stats(message.guild, urls_detected=len(urls), canonical_returned=len(canonical_links))
if canonical_links:
await message.channel.send(f"Canonical URL(s): {'; '.join(canonical_links)}")
else: # DM context
Expand All @@ -101,6 +116,17 @@ async def on_message(self, message):
if canonical_links:
await message.author.send(f"Canonical URL(s): {'; '.join(canonical_links)}")

async def _update_guild_stats(self, guild, *, urls_detected: int, canonical_returned: int) -> None:
"""Update guild-level stats used by the dashboard.

Increments total conversion events, accumulates counts, and records the last conversion timestamp.
"""
async with self.config.guild(guild).stats() as stats:
stats["total_conversions"] = int(stats.get("total_conversions", 0)) + 1
stats["total_urls_detected"] = int(stats.get("total_urls_detected", 0)) + int(urls_detected)
stats["total_canonical_returned"] = int(stats.get("total_canonical_returned", 0)) + int(canonical_returned)
stats["last_conversion_ts"] = int(time.time())

@amputator.command(name='settings')
async def show_settings(self, ctx):
"""Display the current configuration settings for the AmputatorBot in this guild."""
Expand Down
71 changes: 37 additions & 34 deletions ampremover/dashboard_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,24 +197,37 @@ def __init__(self):
@dashboard_page(name="stats", description="View AMP URL conversion statistics", methods=("GET",))
async def stats_page(self, user: discord.User, guild: discord.Guild, **kwargs) -> typing.Dict[str, typing.Any]:
"""Dashboard page for viewing conversion statistics."""
# Get guild settings
# Get guild settings and stats
opted_in = await self.config.guild(guild).opted_in()

# For now, we'll show basic stats. You can expand this later with actual conversion tracking
stats = await self.config.guild(guild).stats()

total_conversions = int(stats.get("total_conversions", 0))
total_urls_detected = int(stats.get("total_urls_detected", 0))
total_canonical_returned = int(stats.get("total_canonical_returned", 0))
last_ts = int(stats.get("last_conversion_ts", 0))

# Compute simple rate
success_rate = 0.0
if total_urls_detected > 0:
success_rate = (total_canonical_returned / total_urls_detected) * 100.0

# Format last conversion time (Dashboard templates can format raw epoch too; keep simple here)
last_conversion = "Never" if last_ts == 0 else f"<code>{last_ts}</code>"

stats_html = f"""
<div class="container">
<div class=\"container\">
<h2>📊 AMP Remover Statistics</h2>
<p>Statistics for <strong>{guild.name}</strong></p>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">

<div class=\"row\">
<div class=\"col-md-6\">
<div class=\"card\">
<div class=\"card-header\">
<h5>Guild Settings</h5>
</div>
<div class="card-body">
<p><strong>Automatic Conversion:</strong>
<span class="badge badge-{'success' if opted_in else 'danger'}">
<div class=\"card-body\">
<p><strong>Automatic Conversion:</strong>
<span class=\"badge badge-{'success' if opted_in else 'danger'}\">
{'Enabled' if opted_in else 'Disabled'}
</span>
</p>
Expand All @@ -223,34 +236,24 @@ async def stats_page(self, user: discord.User, guild: discord.Guild, **kwargs) -
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Bot Information</h5>

<div class=\"col-md-6\">
<div class=\"card\">
<div class=\"card-header\">
<h5>Conversion Stats</h5>
</div>
<div class="card-body">
<p><strong>Bot Name:</strong> {self.bot.user.display_name}</p>
<p><strong>API Used:</strong> AmputatorBot API</p>
<p><strong>Commands Available:</strong></p>
<ul>
<li><code>[p]amputator convert</code> - Manual conversion</li>
<li><code>[p]amputator optin</code> - Enable auto-conversion</li>
<li><code>[p]amputator optout</code> - Disable auto-conversion</li>
<li><code>[p]amputator settings</code> - View settings</li>
<div class=\"card-body\">
<ul class=\"list-unstyled\">
<li><strong>Total conversion events:</strong> {total_conversions}</li>
<li><strong>Total URLs detected:</strong> {total_urls_detected}</li>
<li><strong>Total canonical returned:</strong> {total_canonical_returned}</li>
<li><strong>Success rate:</strong> {success_rate:.1f}%</li>
<li><strong>Last conversion (epoch):</strong> {last_conversion}</li>
</ul>
</div>
</div>
</div>
</div>

<div class="mt-3">
<div class="alert alert-info">
<h5>💡 Tip</h5>
<p>To track conversion statistics, you would need to add logging functionality to the main cog.
This could include tracking the number of URLs converted, success rates, and user activity.</p>
</div>
</div>
</div>
"""

Expand Down
4 changes: 2 additions & 2 deletions dank/info.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "Dank",
"author": ["yourname"],
"author": ["bencos17"],
"description": "Gay and Simp rating machine, Dank Memer style.",
"requirements": [],
"min_bot_version": "3.0.0",
"tags": ["fun", "meme", "rating"],
"hidden": false,
"disabled": false,
"type": "COG"
}
}
Loading
Loading