Discourse 每日挑战

:information_source: 摘要 运行带排行榜、自动帖子和徽章奖励的限时挑战。
:hammer_and_wrench: 仓库链接 GitHub - R23DPrinting/discourse-daily-challenge · GitHub
:open_book: 安装指南 如何在 Discourse 中安装插件

当前版本: 1.3.0(更新于 2026 年 3 月 27 日)
许可证: MIT


故事背景

我运营着一个健康和健身社区,最近从 Discord 迁移到了 Discourse。在 Discord 上,我构建了一个自定义机器人来运行月度健身挑战——成员会发布每日锻炼内容,机器人跟踪签到情况,发布每周排行榜,并在每月结束时宣布获胜者。这是我们要最受欢迎的功能之一。

迁移到 Discourse 后,成员们开始要求恢复挑战功能。我寻找现有的插件但一无所获,于是我自己开发了一个。

最初只是一个快速解决方案,结果却变成了一个功能齐全的挑战系统,实际上比原来的 Discord 机器人更好用。既然它可能对其他社区也有用,我在这里分享出来。


功能概述

discourse-daily-challenge 允许您直接在 Discourse 中运行结构化的限时挑战。成员通过在指定主题中使用标签或照片进行签到。该插件会自动跟踪所有数据。

核心功能

:white_check_mark: 灵活的签到方式
成员通过在挑战主题中发布带有配置好的标签或照片附件的帖子来进行签到。挑战可配置为每日或每周签到模式。签到窗口会根据挑战配置的时区进行评估,因此全球社区也能正常工作。

:bar_chart: 实时排行榜与连续签到
管理员仪表板显示实时排名,包括每位参与者的总签到数、当前连续签到次数和完成百分比。连续签到标签会自动反映挑战间隔(“连续签到天数”或“连续签到周数”)。

:robot: 自动排行榜帖子
设置每周自动发布排行榜的日期和时间,直接发布到挑战主题中。挑战结束后的第二天,也会自动生成最终结果帖子。

:sports_medal: 自定义徽章
定义完成挑战所需的最低签到次数。符合条件的参与者在挑战结束时会自动获得自定义 Discourse 徽章。徽章名称会自动从主题标题中填充。

:card_index_dividers: 同时运行多个挑战
您可以同时运行任意数量的挑战。每个挑战都有独立的主题、时间表、时区、徽章和排行榜。

:file_folder: 已归档的挑战
已完成的挑战会保留在仪表板的归档手风琴视图中,方便您回顾历史结果。

:hammer_and_wrench: 管理员签到管理
管理员可以手动添加或移除签到记录——适用于补录遗漏的帖子或解决争议。

:pushpin: 手动触发排行榜
“立即发布排行榜”按钮允许管理员在预定时间之外按需发布排行榜更新。

:police_officer: 版主权限
全站版主可以通过专用的侧边栏部分创建、编辑和管理挑战。可通过站点设置启用或禁用此功能。

:card_index_dividers: 分类版主权限
分类版主可以通过专用 /challenges 路由管理其指定分类范围内的挑战——无需管理员权限。可通过站点设置独立启用或禁用此功能。


安装

containers/app.ymlhooks > after_code > exec > cmd 下添加以下内容:

- git clone https://github.com/R23DPrinting/discourse-daily-challenge.git

然后重新构建:

cd /var/discourse && ./launcher rebuild app

初始安装后,除非在变更日志中另有说明,未来的更新可以通过面板内的“更新”按钮应用。


配置

创建挑战

导航至 管理员 → 插件 → 挑战 → Discourse 挑战 并点击 新建挑战

字段 说明
主题 ID 成员将进行签到的 Discourse 主题。主题标题将自动填充到徽章名称字段。
标签触发器 签到所需的标签(不含 #,例如 workout
签到间隔 每日或每周签到模式
周起始日 针对每周挑战:一周从哪一天开始(星期日、星期一或星期六)
开始日期 挑战的第一天(含)
结束日期 挑战的最后一天(含)
挑战时区 用于评估签到窗口的时区(可选,默认为 UTC)
所需签到次数 获得完成徽章所需的最低签到次数
描述 挑战的可选描述
启用每周排行榜帖子 切换是否自动发布每周排行榜
发布星期几 自动发布排行榜的星期几
发布小时 自动发布排行榜的小时(0–23,使用挑战时区)
颁发完成徽章 切换是否在挑战结束时自动颁发徽章
徽章名称 要创建和颁发的徽章名称——自动从主题标题填充

站点设置

设置 默认值 说明
daily_challenge_enabled true 全局启用或禁用插件
daily_challenge_mod_access_enabled true 允许全站版主管理挑战
daily_challenge_category_mod_access_enabled true 允许分类版主管理其指定分类中的挑战

签到机制

  1. 成员回复挑战主题
  2. 帖子必须包含配置的标签或照片附件
  3. 插件检查该成员在当前周期(根据挑战间隔为每日或每周)是否已经签到
  4. 如果未签到,则记录签到并更新排行榜
  5. 每个成员每个周期只允许签到一次

管理员可以从管理员仪表板查看和管理所有签到记录。


版主与分类版主权限

如果启用了 daily_challenge_mod_access_enabled,全站版主可以通过主侧边栏中的 CHALLENGES 部分访问挑战管理功能。

分类版主可以通过 /challenges/dashboard/challenges/challenges 访问受限版本——他们只能查看和管理与其指定分类中主题相关的挑战。这需要启用 daily_challenge_category_mod_access_enabled,并且用户必须通过 管理员 → 分类 → [分类] → moderation 被指定为分类版主。


自动帖子

每周排行榜

插件运行每小时后台任务,检查是否到了发布每周排行榜的时间。当到达配置的日期和时间(使用挑战时区)时,它会在挑战主题中创建格式化的帖子,显示当前排名。

最终结果帖子

挑战结束后的第二天,会自动发布最终结果帖子。该帖子列出所有参与者、他们的总签到数、完成状态,并祝贺获得徽章的人。


数据库表

  • daily_challenges — 挑战定义
  • daily_check_ins — 每个用户每个挑战每个周期的单独签到记录

兼容性

  • Discourse 3.x+
  • 已在自托管安装中测试
  • Ruby 3.x, Rails 7.x+

路线图 (v1.4.0)

  • @ChallengeBot 提及命令 — 成员可以在挑战主题中提及机器人账号 @ChallengeBot,通过私信获取个人统计信息(statusleaderboardstreakhelp
  • 签到确认私信 — 每次成功签到后自动发送包含当前连续签到信息的私信
  • 提醒私信 — 可选的每挑战设置;每日挑战在错过 2 天以上后发送提醒,每周挑战在当周最后一天如果尚未签到则发送提醒

变更日志

v1.3.0

  • 分类版主权限 — 分类版主可以通过 /challenges/dashboard/challenges/challenges 创建、编辑、删除和管理其指定分类中的挑战签到——无需管理员权限
  • 徽章名称自动填充 — 输入主题 ID 时,徽章名称字段会自动从主题标题填充
  • 徽章名称验证 — 启用“颁发完成徽章”时,徽章名称为必填项;返回正确的错误而不是 500 错误
  • 独立的版主/分类版主开关daily_challenge_mod_access_enableddaily_challenge_category_mod_access_enabled 可以独立切换

v1.2.0

  • 可配置的签到间隔 — 运行每日或每周挑战
  • 周起始日设置 — 对于每周挑战,可选择一周从星期日、星期一还是星期六开始
  • 感知间隔的连续签到 — 连续签到标签和计算现在反映挑战间隔
  • 版主权限 — 全站版主可以通过专用侧边栏部分管理挑战
  • 在管理员 UI 中重命名为“Discourse 挑战”

v1.1.1

  • 显示所有活跃挑战的多挑战仪表板
  • 显示历史结果的已归档挑战手风琴视图

v1.1.0

  • 每个挑战的时区支持
  • 每周发布时间表现在按挑战配置(日期、小时、启用/禁用开关)

v1.0.0

  • 初始发布

反馈与贡献

本插件是为解决我们社区的实际需求而开发的,如果它也能帮助您的社区,我很乐意分享。欢迎在 GitHub 上提交问题、PR 和功能请求。

如果您运营着一个重视责任感和一致性的社区——如健身、语言学习、写作、创意习惯等——每日挑战可以成为一种真正强大的参与工具。我希望这个插件能让您的 Discourse 社区更容易实现这一功能。

:link: GitHub - R23DPrinting/discourse-daily-challenge · GitHub

15 个赞

这太棒了,感谢分享。就功能而言,如果可以将挑战间隔指定为除每日以外的任何时间,即在挑战期间选择每周一次的挑战签到,那它对我们来说会很有用。如果版主/分类版主也可以创建挑战,那就更好了,这样就不必完全由管理员来设置了。

1 个赞

让我确认一下我的理解是否正确:

所以,您的意思是将挑战设置例如 1 个月(4 月 1 日至 30 日),但指定每周只计算 1 次条目,而不是每天计算 1 次?您希望是以周日到周六为一个周期,还是每 7 天为一个周期?

这是个好主意。说实话,我的社区里没有任何版主,所以我不知道管理员面板对他们来说是什么样子的。我会用我的开发环境创建一个版主账户,然后研究一下。

1 个赞

是的,完全正确。我们的社区有一个“52 周 52 项挑战”,其中一年内有一个每周挑战,因此非每日签到可以为不同类型的挑战提供更大的灵活性。

Discourse Calendar (and Event) 允许管理员将周从星期六、星期日或星期一开始设置,因此如果您想为与该插件的兼容性(例如生成签到或开始/结束日期的日历事件?)做准备,我可能会遵循日历如何设置一周?对于我的社区来说,这似乎并不重要。

类别版主以及常规版主将非常有用——我们有一些兴趣小组有自己的子类别,很高兴能允许这些类别版主运行单独的挑战,而无需将他们提升为完全版主。

1 个赞

这些都是绝妙的主意。感谢分享!我会看看我能做些什么来实现它们。

1 个赞

这个插件是否需要启用 gamification 插件,还是您选择该标签是因为您的插件是关于游戏化的一般用途?

2 个赞

它不需要插件。我只是选择了标签。抱歉。

2 个赞

v1.2.0 现已发布!

本次更新的新功能:

  • 可配置的签到间隔 — 运行每日或每周挑战
  • 周起始设置 — 对于每周挑战,可选择周从周日、周一还是周六开始
  • 感知间隔的连续签到 — 连续签到标签和计算现在会反映挑战间隔(“周连续”与“日连续”)
  • 版主访问权限 — 全站版主现在可以通过专用的侧边栏部分创建、编辑和管理挑战。此功能可在“管理 → 设置 → 插件”中启用或禁用(daily_challenge_mod_access_enabled),因此希望将挑战管理权限保留给管理员的社区可以这样做。
  • 在管理界面中重命名为“Discourse Challenges”
截图

挑战表单 — 签到间隔

仪表盘 — 周起始日

版主侧边栏访问权限

注意: 此版本需要运行 ./launcher rebuild app 以应用数据库迁移 — 面板内的“更新”按钮对此版本无效。


关于分类版主访问权限 — 这是我正在积极开发的内容。挑战在于 Discourse 不授予分类版主员工身份,这意味着无论插件如何设置,他们都无法访问 /admin 路由。我正在探索一个专门针对分类版主的独立非管理员路由,这将使他们能够访问其所在分类中的挑战。预计将在 v1.3.0 中推出。

1 个赞

我无法创建挑战,任何操作都返回错误 500。

这很奇怪。我在两个不同的论坛上运行都没有问题,而且我也做了大量的测试。你使用的 Discourse 版本是多少?你使用的是最新的插件版本(1.2.0)吗?

你能否通过 SSH 连接到你的服务器,运行几条命令,并发布结果?

首先,检查插件版本号:

./launcher enter app
cat /var/www/discourse/plugins/discourse-daily-challenge/plugin.rb | grep version

接下来,尝试创建一个挑战。当你遇到 500 错误时,立即在 SSH 会话中运行以下命令:

./launcher enter app
tail -50 /var/www/discourse/log/production.log

请告知结果,我们可以尝试找出问题所在。

Discourse 2026.3.0-latest.1 - https://github.com/discourse/discourse version f2781e1455274ea2c011fd1d161ac2ad9fde72bd

# version: 1.2.0
# required_version: 2.7.0
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:05 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "2", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não vou me estressar\",\"action\":\"reply                                                                                        \",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\                                                                                        "composerTime\":25298,\"typingTime\":3900,\"locale\":\"\"}", "owner" => "fee91ce                                                                                        037e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 11ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.1ms)
Started GET "/t/os-melhores-blogs-de-games-no-brasil/15399?__cf_chl_rt_tk=5M6_rd                                                                                        _fq46BL9PZ6VvZRAVdIsJ84aL9XIfa1Re_paQ-1773238724-1.0.1.1-g20DH5dkOVXOIHhKYVpNwYR                                                                                        CQNnUboE4.po6L1TFk1E" for 164.52.120.12 at 2026-03-26 20:49:06 +0000
Processing by TopicsController#show as */*
  Parameters: {"__cf_chl_rt_tk" => "5M6_rd_fq46BL9PZ6VvZRAVdIsJ84aL9XIfa1Re_paQ-                                                                                        1773238724-1.0.1.1-g20DH5dkOVXOIHhKYVpNwYRCQNnUboE4.po6L1TFk1E", "slug" => "os-m                                                                                        elhores-blogs-de-games-no-brasil", "topic_id" => "15399"}
  Rendered layout layouts/application.html.erb (Duration: 107.3ms | GC: 1.4ms)
Completed 200 OK in 192ms (Views: 109.0ms | ActiveRecord: 0.0ms (0 queries, 0 ca                                                                                        ched) | GC: 2.4ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:11 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "3", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não vou me estressar com o comportamento                                                                                         \",\"action\":\"reply\",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\                                                                                        ",\"metaData\":null,\"composerTime\":31344,\"typingTime\":4600,\"locale\":\"\"}"                                                                                        , "owner" => "fee91ce037e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 11ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started GET "/leaderboard" for 2804:1e68:c201:632a:1161:4504:ea4d:9983 at 2026-0                                                                                        3-26 20:49:15 +0000
Processing by DiscourseGamification::GamificationLeaderboardController#respond a                                                                                        s JSON
Completed 200 OK in 30ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:16 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "4", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não vou me estressar com o comportamento                                                                                         das outras pessoas \",\"action\":\"reply\",\"categoryId\":14,\"tags\":[],\"arche                                                                                        typeId\":\"regular\",\"metaData\":null,\"composerTime\":36481,\"typingTime\":510                                                                                        0,\"locale\":\"\"}", "owner" => "fee91ce037e64ec297a500af13091165", "force_save"                                                                                         => "false"}
Completed 200 OK in 11ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started GET "/leaderboard/6?period=daily" for 2804:1e68:c201:632a:1161:4504:ea4d                                                                                        :9983 at 2026-03-26 20:49:19 +0000
Processing by DiscourseGamification::GamificationLeaderboardController#respond a                                                                                        s JSON
  Parameters: {"period" => "daily", "id" => "6"}
Completed 200 OK in 20ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Processing by UserAvatarsController#show as PNG
  Parameters: {"hostname" => "segredin.com", "username" => "mary", "size" => "28                                                                                        8", "version" => "8962_2"}
Completed 418  in 8ms (Views: 0.2ms | ActiveRecord: 0.0ms (0 queries, 0 cached)                                                                                         | GC: 0.0ms)
Sent file /var/www/discourse/public/uploads/default/optimized/2X/8/86ba5981dfff7                                                                                        6df7de0f2141a1a87ad805e2c5c_2_288x288.jpeg (0.2ms)
Started GET "/latest.json" for 2804:79d4:f008:68f:fd8c:855b:db16:bbee at 2026-03                                                                                        -26 20:49:23 +0000
Processing by ListController#latest as JSON
Completed 200 OK in 143ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cach                                                                                        ed) | GC: 6.8ms)
Started GET "/t/17746/1.json?track_visit=true&forceLoad=true" for 2804:79d4:f008                                                                                        :68f:fd8c:855b:db16:bbee at 2026-03-26 20:49:31 +0000
Processing by TopicsController#show as JSON
  Parameters: {"track_visit" => "true", "forceLoad" => "true", "topic_id" => "17                                                                                        746", "post_number" => "1"}
Completed 200 OK in 114ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cach                                                                                        ed) | GC: 1.8ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:32 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "5", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não consigo k\",\"action\":\"reply\",\"ca                                                                                        tegoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\"compos                                                                                        erTime\":46059,\"typingTime\":8500,\"locale\":\"\"}", "owner" => "fee91ce037e64e                                                                                        c297a500af13091165", "force_save" => "false"}
Completed 200 OK in 13ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:36 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "6", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não consigo julgar \",\"action\":\"reply\                                                                                        ",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\"                                                                                        composerTime\":50400,\"typingTime\":9100,\"locale\":\"\"}", "owner" => "fee91ce0                                                                                        37e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 10ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:37 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "7", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não consigo julgar \",\"action\":\"reply\                                                                                        ",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\"                                                                                        composerTime\":51596,\"typingTime\":9100,\"locale\":\"\"}", "owner" => "fee91ce0                                                                                        37e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 10ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Processing by TopicsController#timings as */*
  Parameters: {"timings" => {"1" => "59036"}, "topic_time" => "59036", "topic_id                                                                                        " => "17746"}
Completed 418  in 8ms (Views: 0.2ms | ActiveRecord: 0.0ms (0 queries, 0 cached)                                                                                         | GC: 0.0ms)

嗯,该日志并未显示 500 错误。错误究竟是在何时发生的?您是在屏幕上看到任何具体错误信息,还是只看到通用的“发生了一些问题”提示?您是否确保填写了所有字段?特别是主题 ID?

能否请您再次尝试创建一个挑战,并在看到 500 错误后立即运行以下命令?我们将稍微增加输出量:

./launcher enter app
tail -100 /var/www/discourse/log/production.log | grep -A 30 "500\|Error\|exception"

(帖子已被作者删除)

稍等,我将再次启用。

啊,我想我明白问题所在了。徽章名称本应从主题标题中提取,但我觉得在上次更新时搞错了。你能试着填写徽章名称字段,看看能否通过吗?我一到家也会推送一个修复更新。

2 个赞

有新东西吗?我在这里更新了,但还没有任何变化。

嘿,抱歉。我昨晚忘了更新这篇帖子。我已经推送了一个更新,其中包含对徽章的修复(以及一些其他改进)。如果您前往“管理 > 更新 Discourse”,应该会看到有可用更新。

我稍后会发布一篇包含所有变更的完整帖子。

2 个赞

我确信我这样做的方式是错误的。

第一个疑问:主题 ID 可以已存在,还是需要创建一个新的?
我理解标签是不带 # 的。

开始和结束时间可以是几天或几周,或者我可以设置为例如直到十二月吗?

无论如何,我会等待您的解释,因为我喜欢这个插件,我在这里是为了提供反馈。

它必须是您的账户可访问的现有主题 ID(我假设您是管理员,因此这不应成问题)。例如,如果主题的 URL 为:

https://community.com/t/test-formatting/722

您输入的主题 ID 应为:722

输入数字后,主题名称应出现在下方,以确认您输入了正确的 ID。

您说得对。请输入不带 # 的标签。如果您想使用 #testtag,只需在框中输入 testtag 即可。

开始和结束日期可以随意设置。

activerecord (8.0.5) lib/active_record/inheritance.rb:76:in 'ActiveRecord::Inheritance::ClassMethods#new'
plugins/discourse-daily-challenge/app/controllers/discourse_daily_challenge/admin_daily_challenges_controller.rb:26:in 'DiscourseDailyChallenge::AdminDailyChallengesController#create'

我的初始配置

以及我最近的日志