@@ -166,6 +166,44 @@ TRIVIA_HISTORY_KEY = "trivia_quiz_history"
TRIVIA_ANSWER_WINDOW_MINUTES = 5
TRIVIA_QUIZ_PROBABILITY_PERCENT = 75
TRIVIA_RECENT_BASE_LIMIT = 24
TRIVIA_TOPIC_NAMES = (
" 反恐精英 " ,
" 顶点英雄 " ,
" 守望先锋 " ,
" 英雄联盟 " ,
" 少女乐团派对 " ,
" 韩流 " ,
" 原神 " ,
)
TRIVIA_TOPIC_ALIASES = {
" 反恐精英 " : " 反恐精英 " ,
" 反恐 " : " 反恐精英 " ,
" cs " : " 反恐精英 " ,
" cs2 " : " 反恐精英 " ,
" csgo " : " 反恐精英 " ,
" 顶点英雄 " : " 顶点英雄 " ,
" 顶点 " : " 顶点英雄 " ,
" apex " : " 顶点英雄 " ,
" 守望先锋 " : " 守望先锋 " ,
" 守望 " : " 守望先锋 " ,
" ow " : " 守望先锋 " ,
" overwatch " : " 守望先锋 " ,
" 英雄联盟 " : " 英雄联盟 " ,
" 联盟 " : " 英雄联盟 " ,
" lol " : " 英雄联盟 " ,
" leagueoflegends " : " 英雄联盟 " ,
" 少女乐团派对 " : " 少女乐团派对 " ,
" 邦邦 " : " 少女乐团派对 " ,
" gbp " : " 少女乐团派对 " ,
" girlsbandparty " : " 少女乐团派对 " ,
" bangdream " : " 少女乐团派对 " ,
" 韩流 " : " 韩流 " ,
" 韩国流行音乐 " : " 韩流 " ,
" kpop " : " 韩流 " ,
" 原神 " : " 原神 " ,
" genshin " : " 原神 " ,
" genshinimpact " : " 原神 " ,
}
BEIJING_TIMEZONE = ZoneInfo ( " Asia/Shanghai " )
ACKNOWLEDGEMENT = " 已收到,正在处理中喵 "
STEAM_ACKNOWLEDGEMENT = " Steam 查询 [■□□] 正在读取公开数据 "
@@ -360,6 +398,21 @@ TRIVIA_FACTS: tuple[tuple[str, str, str, str, tuple[str, str, str], str, int], .
( " lol-zac " , " 英雄联盟 " , " 扎克名字常被解释为什么缩写? " , " 祖安无定形战斗体 " , ( " 祖安奥术核心 " , " 时间装置自动体 " , " 祖安酸液生物 " ) , " 扎克的名字常被当作祖安无定形战斗体的缩写梗。 " , 4 ) ,
( " lol-kindred " , " 英雄联盟 " , " 永猎双子由哪两个化身组成? " , " 羊灵和狼灵 " , ( " 乌鸦和雄鹿 " , " 狐狸和猫头鹰 " , " 太阳和月亮 " ) , " 永猎双子是羊灵与狼灵两个死亡化身。 " , 2 ) ,
( " lol-ryze " , " 英雄联盟 " , " 瑞兹长期收集并封存的危险物是什么? " , " 世界符文 " , ( " 暗裔武器 " , " 海克斯核心 " , " 虚空碎片 " ) , " 瑞兹的主线目标是阻止世界符文失控。 " , 3 ) ,
( " gbp-random-star " , " 少女乐团派对 " , " 户山香澄使用的星形吉他昵称是什么? " , " 随机星 " , ( " 流星号 " , " 闪耀心 " , " 星光鸟 " ) , " 户山香澄的标志性星形吉他昵称是随机星。 " , 4 ) ,
( " gbp-hikawa-sisters " , " 少女乐团派对 " , " 冰川双子里姐姐是谁? " , " 冰川纱夜 " , ( " 冰川日菜 " , " 丸山彩 " , " 今井莉莎 " ) , " 冰川纱夜是姐姐,冰川日菜是妹妹。 " , 3 ) ,
( " gbp-aya-idol " , " 少女乐团派对 " , " 丸山彩所在乐队最核心的组合定位是? " , " 偶像乐队 " , ( " 幼驯染摇滚 " , " 哥特交响 " , " 欢乐马戏 " ) , " 丸山彩所在组合以偶像乐队定位为核心。 " , 3 ) ,
( " gbp-yukina-father " , " 少女乐团派对 " , " 凑友希那坚持音乐的家庭原因主要和谁有关? " , " 父亲 " , ( " 姐姐 " , " 祖母 " , " 老师 " ) , " 凑友希那对音乐的执着,很大一部分和父亲的经历有关。 " , 4 ) ,
( " gbp-tsugumi-cafe " , " 少女乐团派对 " , " 羽泽鸫家里经营的店更接近什么? " , " 咖啡店 " , ( " 唱片店 " , " 花店 " , " 乐器店 " ) , " 羽泽鸫家经营咖啡店,她也经常帮忙。 " , 3 ) ,
( " kpop-snsd-debut " , " 韩流 " , " 少女时代的出道曲是哪首? " , " 再次重逢的世界 " , ( " 说出你的愿望 " , " 哦 " , " 狮心 " ) , " 少女时代的出道曲是再次重逢的世界。 " , 4 ) ,
( " kpop-redvelvet-leader " , " 韩流 " , " 红贝贝的队长是谁? " , " 裴柱现 " , ( " 姜涩琪 " , " 孙胜完 " , " 朴秀荣 " ) , " 红贝贝的队长是裴柱现。 " , 3 ) ,
( " kpop-newjeans-fandom " , " 韩流 " , " 新牛仔的官方粉丝名中文常译为什么? " , " 兔子 " , ( " 星星 " , " 花园 " , " 钥匙 " ) , " 新牛仔的官方粉丝名常译作兔子。 " , 4 ) ,
( " kpop-first-win " , " 韩流 " , " 歌谣节目里常说的“一位”通常指什么? " , " 当期第一名 " , ( " 出道舞台 " , " 安可失败 " , " 投票资格 " ) , " 一位通常指某个歌谣节目当期排名第一。 " , 2 ) ,
( " kpop-lightstick " , " 韩流 " , " 韩流演唱会里粉丝常用来应援的发光道具叫什么? " , " 应援棒 " , ( " 舞台牌 " , " 投票券 " , " 签售卡 " ) , " 应援棒是韩流现场应援里很常见的发光道具。 " , 2 ) ,
( " genshin-barbatos " , " 原神 " , " 蒙德风神的真实魔神名是? " , " 巴巴托斯 " , ( " 摩拉克斯 " , " 巴尔泽布 " , " 布耶尔 " ) , " 蒙德风神温迪对应的魔神名是巴巴托斯。 " , 3 ) ,
( " genshin-morax " , " 原神 " , " 璃月岩王帝君的魔神名是? " , " 摩拉克斯 " , ( " 巴巴托斯 " , " 厄歌莉娅 " , " 布耶尔 " ) , " 岩王帝君钟离对应的魔神名是摩拉克斯。 " , 3 ) ,
( " genshin-ei " , " 原神 " , " 稻妻一心净土里的真正主人是谁? " , " 雷电影 " , ( " 雷电真 " , " 八重神子 " , " 九条裟罗 " ) , " 一心净土里的真正主人是雷电影。 " , 4 ) ,
( " genshin-nahida-title " , " 原神 " , " 纳西妲作为神明对应的称号是? " , " 小吉祥草王 " , ( " 大慈树王 " , " 尘世闲游 " , " 正义之神 " ) , " 纳西妲在须弥被称为小吉祥草王。 " , 3 ) ,
( " genshin-neuvillette " , " 原神 " , " 枫丹最高审判官是谁? " , " 那维莱特 " , ( " 莱欧斯利 " , " 芙宁娜 " , " 克洛琳德 " ) , " 那维莱特是枫丹的最高审判官。 " , 3 ) ,
)
TRIVIA_TEMPLATES = (
" 【 {domain} 冷知识】 {prompt} " ,
@@ -378,12 +431,32 @@ def trivia_reward(rarity: int) -> int:
return 20 + max ( 1 , min ( 5 , int ( rarity ) ) ) * 30
def trivia_topic_key ( value : object ) - > str :
return re . sub (
r " [ \ s ' ’ `·・_\ -:: .。!! ?? ]+ " ,
" " ,
str ( value or " " ) . casefold ( ) ,
)
def canonical_trivia_topic ( value : object ) - > str | None :
topic = trivia_topic_key ( value )
if not topic :
return None
return TRIVIA_TOPIC_ALIASES . get ( topic )
def supported_trivia_topics_message ( ) - > str :
return " 支持主题: " + " 、 " . join ( TRIVIA_TOPIC_NAMES )
@lru_cache ( maxsize = 1 )
def trivia_question_bank ( ) - > tuple [ dict [ str , object ] , . . . ] :
questions : list [ dict [ str , object ] ] = [
{
* * question ,
" base_id " : str ( question [ " id " ] ) ,
" topic " : canonical_trivia_topic ( question . get ( " domain " ) ) or str ( question [ " domain " ] ) ,
" rarity " : int ( question . get ( " rarity " , 4 ) ) ,
" reward " : trivia_reward ( int ( question . get ( " rarity " , 4 ) ) ) ,
}
@@ -399,6 +472,7 @@ def trivia_question_bank() -> tuple[dict[str, object], ...]:
" id " : f " { base_id } - { variant + 1 } " ,
" base_id " : base_id ,
" domain " : domain ,
" topic " : canonical_trivia_topic ( domain ) or domain ,
" question " : template . format ( domain = domain , prompt = prompt ) ,
" options " : rotated ,
" answer " : answer_label ,
@@ -6764,10 +6838,20 @@ class HourlyBroadcastEngine:
* ,
group : str ,
local_now : datetime ,
topic : str | None = None ,
) - > dict [ str , object ] :
bank = trivia_question_bank ( )
bank = tuple (
question
for question in trivia_question_bank ( )
if topic is None or str ( question . get ( " topic " ) ) == topic
)
if not bank :
raise LocalCommandError ( " 这个主题还没有题喵 \n " + supported_trivia_topics_message ( ) )
recent = set ( self . trivia_recent_base_ids ( group ) )
seed = f " { group } | { local_now . date ( ) . isoformat ( ) } | { local_now . hour : 02d } |trivia "
seed = (
f " { group } | { local_now . date ( ) . isoformat ( ) } | "
f " { local_now . hour : 02d } |trivia| { topic or ' all ' } "
)
start = deterministic_int ( seed , 0 , len ( bank ) - 1 )
for offset in range ( len ( bank ) ) :
candidate = bank [ ( start + offset ) % len ( bank ) ]
@@ -6783,8 +6867,15 @@ class HourlyBroadcastEngine:
raise LocalCommandError ( " 冷门问答题库选项无效 " )
rarity = max ( 1 , min ( 5 , int ( question . get ( " rarity " , 3 ) ) ) )
reward = max ( 1 , min ( 500 , int ( question . get ( " reward " , trivia_reward ( rarity ) ) ) ) )
topic = normalize_text ( question . get ( " topic " ) or question . get ( " domain " ) )
heading = f " 冷门知识问答(冷门度 { rarity } /5,限时5分钟,答对 + { reward } 摩拉) "
if topic :
heading = (
f " 冷门知识问答(主题: { topic } ,冷门度 { rarity } /5, "
f " 限时5分钟,答对 + { reward } 摩拉) "
)
lines = [
f " 冷门知识问答(冷门度 { rarity } /5,限时5分钟,答对 + { reward } 摩拉) " ,
heading ,
str ( question . get ( " question " , " " ) ) ,
]
lines . extend (
@@ -6799,8 +6890,13 @@ class HourlyBroadcastEngine:
* ,
group : str ,
local_now : datetime ,
topic : str | None = None ,
) - > str :
question = self . select_trivia_question ( group = group , local_now = local_now )
question = self . select_trivia_question (
group = group ,
local_now = local_now ,
topic = topic ,
)
message = self . render_trivia_message ( question )
expires_at = (
local_now + timedelta ( minutes = TRIVIA_ANSWER_WINDOW_MINUTES )
@@ -6811,6 +6907,7 @@ class HourlyBroadcastEngine:
{
" question_id " : str ( question [ " id " ] ) ,
" base_id " : str ( question . get ( " base_id " ) or question [ " id " ] ) ,
" topic " : str ( question . get ( " topic " ) or question . get ( " domain " ) or " " ) ,
" question " : str ( question [ " question " ] ) ,
" options " : list ( question [ " options " ] ) ,
" answer " : str ( question [ " answer " ] ) ,
@@ -6828,9 +6925,13 @@ class HourlyBroadcastEngine:
self ,
* ,
group : str ,
topic_name : str = " " ,
now : datetime | None = None ,
) - > str :
local_now = self . in_beijing_timezone ( now )
topic = canonical_trivia_topic ( topic_name )
if normalize_text ( topic_name ) and topic is None :
return supported_trivia_topics_message ( )
active = self . ledger . local_game ( group , TRIVIA_GAME_KEY )
if active :
try :
@@ -6839,7 +6940,11 @@ class HourlyBroadcastEngine:
except ( TypeError , ValueError ) :
return " 冷门问答状态坏了喵 "
self . ledger . clear_local_game ( group , TRIVIA_GAME_KEY )
return self . start_trivia_question ( group = group , local_now = local_now )
return self . start_trivia_question (
group = group ,
local_now = local_now ,
topic = topic ,
)
def due_trivia_broadcast (
self ,
@@ -8107,10 +8212,11 @@ class EdgeApplication:
}
if decision . execution_path is ExecutionPath . LOCAL_COMMAND :
try :
command_kind = local_command_kind ( str ( row [ " question " ] ) )
command_kind , command_arguments = local_command_parts ( str ( row [ " question " ] ) )
reply = (
self . clock . force_trivia_message (
group = str ( row [ " group_fingerprint " ] )
group = str ( row [ " group_fingerprint " ] ) ,
topic_name = command_arguments ,
)
if command_kind == " trivia_start "
else