Skip to content

Commit ab76fa0

Browse files
authored
feat: /widget endpoint to replace widget.dumpus.app (#66)
1 parent 800ec73 commit ab76fa0

7 files changed

Lines changed: 75 additions & 5 deletions

File tree

.github/workflows/infra.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ jobs:
4343
env:
4444
TF_VAR_diswho_jwt_secret: ${{ secrets.DISWHO_JWT_SECRET }}
4545
TF_VAR_wh_url: ${{ secrets.WH_URL }}
46+
TF_VAR_discord_bot_token: ${{ secrets.DISCORD_BOT_TOKEN }}
4647
run: tofu plan -var-file=terraform.ci.tfvars -no-color | tee /tmp/plan.txt
4748

4849
- name: tofu apply
4950
if: inputs.action == 'apply'
5051
env:
5152
TF_VAR_diswho_jwt_secret: ${{ secrets.DISWHO_JWT_SECRET }}
5253
TF_VAR_wh_url: ${{ secrets.WH_URL }}
54+
TF_VAR_discord_bot_token: ${{ secrets.DISCORD_BOT_TOKEN }}
5355
run: tofu apply -auto-approve -var-file=terraform.ci.tfvars
5456

5557
- name: Outputs

infra/terraform/iam.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ data "aws_iam_policy_document" "api" {
3939
aws_secretsmanager_secret.postgres_url.arn,
4040
aws_secretsmanager_secret.diswho_jwt_secret.arn,
4141
aws_secretsmanager_secret.wh_url.arn,
42+
aws_secretsmanager_secret.discord_bot_token.arn,
4243
]
4344
}
4445

@@ -106,6 +107,7 @@ data "aws_iam_policy_document" "worker" {
106107
aws_secretsmanager_secret.postgres_url.arn,
107108
aws_secretsmanager_secret.diswho_jwt_secret.arn,
108109
aws_secretsmanager_secret.wh_url.arn,
110+
aws_secretsmanager_secret.discord_bot_token.arn,
109111
]
110112
}
111113

infra/terraform/lambda.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ locals {
1111
# Sensitive values are NOT in env. The Lambda's secrets_loader.py reads
1212
# them from Secrets Manager at startup using the ARNs below.
1313
SECRETS_ARN_MAP = jsonencode({
14-
POSTGRES_URL = aws_secretsmanager_secret.postgres_url.arn
15-
DISWHO_JWT_SECRET = aws_secretsmanager_secret.diswho_jwt_secret.arn
16-
WH_URL = aws_secretsmanager_secret.wh_url.arn
14+
POSTGRES_URL = aws_secretsmanager_secret.postgres_url.arn
15+
DISWHO_JWT_SECRET = aws_secretsmanager_secret.diswho_jwt_secret.arn
16+
WH_URL = aws_secretsmanager_secret.wh_url.arn
17+
DISCORD_BOT_TOKEN = aws_secretsmanager_secret.discord_bot_token.arn
1718
})
1819

1920
# Encrypted package blobs live here; API generates presigned URLs.

infra/terraform/secrets.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,17 @@ resource "aws_secretsmanager_secret_version" "wh_url" {
6565
ignore_changes = [secret_string]
6666
}
6767
}
68+
69+
resource "aws_secretsmanager_secret" "discord_bot_token" {
70+
name = "${local.name}/app/discord-bot-token"
71+
recovery_window_in_days = 7
72+
}
73+
74+
resource "aws_secretsmanager_secret_version" "discord_bot_token" {
75+
secret_id = aws_secretsmanager_secret.discord_bot_token.id
76+
secret_string = var.discord_bot_token
77+
78+
lifecycle {
79+
ignore_changes = [secret_string]
80+
}
81+
}

infra/terraform/variables.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ variable "wh_url" {
158158
default = ""
159159
}
160160

161+
variable "discord_bot_token" {
162+
description = "Discord bot token. Used by /widget to fetch guild info via the bot API."
163+
type = string
164+
sensitive = true
165+
default = ""
166+
}
167+
161168
variable "diswho_jwt_secret" {
162169
type = string
163170
sensitive = true

src/app.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from sqlite import generate_demo_database
1414

15-
from util import check_discord_link, check_whitelisted_link, extract_package_id_from_discord_link, extract_package_id_from_upn, fetch_diswho_user
15+
from util import check_discord_link, check_whitelisted_link, extract_package_id_from_discord_link, extract_package_id_from_upn, fetch_diswho_user, fetch_discord_guild, discord_icon_url
1616

1717
from wh import send_internal_notification
1818

@@ -317,6 +317,27 @@ def get_avatar(package_id, user_id):
317317
'username': user['username'] if 'username' in user and user['username'] else None,
318318
}), 200
319319

320+
321+
@app.route('/widget/<guild_id>', methods=['GET'])
322+
def get_widget(guild_id):
323+
"""Replacement for the legacy widget.dumpus.app proxy.
324+
325+
Returns name + icon_url + member_count for a guild the bot can see.
326+
"""
327+
if not guild_id.isdigit():
328+
return jsonify({'error': 'invalid guild id'}), 400
329+
330+
guild = fetch_discord_guild(guild_id)
331+
if not guild:
332+
return jsonify({'error': 'guild not found or bot lacks access'}), 404
333+
334+
return jsonify({
335+
'name': guild['name'],
336+
'icon_url': discord_icon_url(guild_id, guild.get('icon')),
337+
'member_count': guild.get('approximate_member_count', 0),
338+
}), 200
339+
340+
320341
@app.errorhandler(404)
321342
def page_not_found(e):
322343
return jsonify({'error': 'Route not found.'}), 404

src/util.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def fetch_diswho_user(user_id):
107107
auth = f"Bearer {diswho_jwt}"
108108
else:
109109
base_url = os.getenv('DISCORD_BASE_URL', 'https://discord.com/api/v8/users/')
110-
auth = f"Bot {os.getenv('DISCORD_SECRET')}"
110+
auth = f"Bot {os.getenv('DISCORD_BOT_TOKEN')}"
111111

112112
headers = {
113113
'authorization': auth
@@ -118,3 +118,26 @@ def fetch_diswho_user(user_id):
118118
return r.json()
119119
else:
120120
return None
121+
122+
123+
def fetch_discord_guild(guild_id):
124+
"""Fetch a guild via the Discord bot API. Returns the parsed JSON or None on error."""
125+
bot_token = os.getenv('DISCORD_BOT_TOKEN')
126+
if not bot_token:
127+
return None
128+
r = requests.get(
129+
f'https://discord.com/api/v10/guilds/{guild_id}',
130+
params={'with_counts': 'true'},
131+
headers={'Authorization': f'Bot {bot_token}'},
132+
timeout=5,
133+
)
134+
if r.status_code != 200:
135+
return None
136+
return r.json()
137+
138+
139+
def discord_icon_url(guild_id, icon_hash):
140+
if not icon_hash:
141+
return None
142+
ext = 'gif' if icon_hash.startswith('a_') else 'png'
143+
return f'https://cdn.discordapp.com/icons/{guild_id}/{icon_hash}.{ext}'

0 commit comments

Comments
 (0)