坏坏 AI 怎么把 diff 文件交上去了

This commit is contained in:
2025-11-19 00:47:24 +08:00
parent f6fadb7226
commit 988965451b
2 changed files with 1 additions and 849 deletions

2
.gitignore vendored
View File

@ -2,4 +2,4 @@
/data
__pycache__
/*.diff

View File

@ -1,848 +0,0 @@
commit f21da657dbc79c2d139265a69696e5ad213f5c53
Author: MixBadGun <1059129006@qq.com>
Date: Tue Nov 18 19:36:05 2025 +0800
database 接入
diff --git a/.env.example b/.env.example
index 7fde1d8..488632c 100644
--- a/.env.example
+++ b/.env.example
@@ -1,4 +1,4 @@
ENVIRONMENT=dev
PORT=21333
-
+DATABASE_PATH="./data/database.db"
ENABLE_CONSOLE=true
diff --git a/.gitignore b/.gitignore
index 9f2daec..8337d30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
/.env
/data
-__pycache__
\ No newline at end of file
+__pycache__
+
+*.db
\ No newline at end of file
diff --git a/bot.py b/bot.py
index 782c870..e4c56ca 100644
--- a/bot.py
+++ b/bot.py
@@ -10,6 +10,7 @@ from nonebot.adapters.onebot.v11 import Adapter as OnebotAdapter
from konabot.common.log import init_logger
from konabot.common.nb.exc import BotExceptionMessage
from konabot.common.path import LOG_PATH
+from konabot.core.preinit import preinit
dotenv.load_dotenv()
env = os.environ.get("ENVIRONMENT", "prod")
@@ -48,6 +49,9 @@ def main():
nonebot.load_plugins("konabot/plugins")
nonebot.load_plugin("nonebot_plugin_analysis_bilibili")
+ # 预加载
+ preinit("konabot/plugins")
+
nonebot.run()
if __name__ == "__main__":
diff --git a/konabot/common/database/__init__.py b/konabot/common/database/__init__.py
new file mode 100644
index 0000000..2a44469
--- /dev/null
+++ b/konabot/common/database/__init__.py
@@ -0,0 +1,64 @@
+import os
+import sqlite3
+from typing import List, Dict, Any, Optional
+
+class DatabaseManager:
+ """超级无敌神奇的数据库!"""
+
+ @classmethod
+ def query(cls, query: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
+ """执行查询语句并返回结果"""
+ conn = sqlite3.connect(os.environ.get('DATABASE_PATH', './data/database.db'))
+ cursor = conn.cursor()
+ cursor.execute(query, params or ())
+ columns = [description[0] for description in cursor.description]
+ results = [dict(zip(columns, row)) for row in cursor.fetchall()]
+ cursor.close()
+ conn.close()
+ return results
+
+ @classmethod
+ def query_by_sql_file(cls, file_path: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
+ """从 SQL 文件中读取查询语句并执行"""
+ with open(file_path, 'r', encoding='utf-8') as f:
+ query = f.read()
+ return cls.query(query, params)
+
+ @classmethod
+ def execute(cls, command: str, params: Optional[tuple] = None) -> None:
+ """执行非查询语句"""
+ conn = sqlite3.connect(os.environ.get('DATABASE_PATH', './data/database.db'))
+ cursor = conn.cursor()
+ cursor.execute(command, params or ())
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+ @classmethod
+ def execute_by_sql_file(cls, file_path: str, params: Optional[tuple] = None) -> None:
+ """从 SQL 文件中读取非查询语句并执行"""
+ with open(file_path, 'r', encoding='utf-8') as f:
+ command = f.read()
+ # 按照需要执行多条语句
+ commands = command.split(';')
+ for cmd in commands:
+ cmd = cmd.strip()
+ if cmd:
+ cls.execute(cmd, params)
+
+ @classmethod
+ def execute_many(cls, command: str, seq_of_params: List[tuple]) -> None:
+ """执行多条非查询语句"""
+ conn = sqlite3.connect(os.environ.get('DATABASE_PATH', './data/database.db'))
+ cursor = conn.cursor()
+ cursor.executemany(command, seq_of_params)
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+ @classmethod
+ def execute_many_values_by_sql_file(cls, file_path: str, seq_of_params: List[tuple]) -> None:
+ """从 SQL 文件中读取一条语句,但是被不同值同时执行"""
+ with open(file_path, 'r', encoding='utf-8') as f:
+ command = f.read()
+ cls.execute_many(command, seq_of_params)
\ No newline at end of file
diff --git a/konabot/core/preinit.py b/konabot/core/preinit.py
new file mode 100644
index 0000000..ccfd3f7
--- /dev/null
+++ b/konabot/core/preinit.py
@@ -0,0 +1,15 @@
+from pathlib import Path
+
+from nonebot import logger
+
+def preinit(path: str):
+ # 执行预初始化,递归找到位于对应路径内文件名为 __preinit__.py 的所有文件都会被执行
+ dir_path = Path(path)
+ for item in dir_path.iterdir():
+ if item.is_dir():
+ preinit(item)
+ elif item.is_file() and item.name == "__preinit__.py":
+ # 动态导入该文件以执行预初始化代码
+ module_path = str(item.with_suffix("")).replace("/", ".").replace("\\", ".")
+ __import__(module_path)
+ logger.info(f"Preinitialized module: {module_path}")
\ No newline at end of file
diff --git a/konabot/plugins/air_conditioner/__init__.py b/konabot/plugins/air_conditioner/__init__.py
index 4f921fe..e148954 100644
--- a/konabot/plugins/air_conditioner/__init__.py
+++ b/konabot/plugins/air_conditioner/__init__.py
@@ -7,16 +7,19 @@ from nonebot.adapters.discord.event import MessageEvent as DiscordMessageEvent
from nonebot_plugin_alconna import Alconna, AlconnaMatcher, Args, UniMessage, on_alconna
from PIL import Image
import numpy as np
+from konabot.common.database import DatabaseManager
from konabot.common.longtask import DepLongTaskTarget
from konabot.common.path import ASSETS_PATH
from konabot.common.web_render import WebRenderer
from konabot.plugins.air_conditioner.ac import AirConditioner, CrashType, generate_ac_image, wiggle_transform
-
+from pathlib import Path
import random
import math
+ROOT_PATH = Path(__file__).resolve().parent
+
def get_ac(id: str) -> AirConditioner:
- ac = AirConditioner.air_conditioners.get(id)
+ ac = AirConditioner.get_ac(id)
if ac is None:
ac = AirConditioner(id)
return ac
@@ -61,7 +64,7 @@ evt = on_alconna(Alconna(
async def _(event: BaseEvent, target: DepLongTaskTarget):
id = target.channel_id
ac = get_ac(id)
- ac.on = True
+ ac.update_ac(state=True)
await send_ac_image(evt, ac)
evt = on_alconna(Alconna(
@@ -72,7 +75,7 @@ evt = on_alconna(Alconna(
async def _(event: BaseEvent, target: DepLongTaskTarget):
id = target.channel_id
ac = get_ac(id)
- ac.on = False
+ ac.update_ac(state=False)
await send_ac_image(evt, ac)
evt = on_alconna(Alconna(
@@ -82,6 +85,8 @@ evt = on_alconna(Alconna(
@evt.handle()
async def _(event: BaseEvent, target: DepLongTaskTarget, temp: Optional[Union[int, float]] = 1):
+ if temp is None:
+ temp = 1
if temp <= 0:
return
id = target.channel_id
@@ -89,7 +94,7 @@ async def _(event: BaseEvent, target: DepLongTaskTarget, temp: Optional[Union[in
if not ac.on or ac.burnt == True or ac.frozen == True:
await send_ac_image(evt, ac)
return
- ac.temperature += temp
+ ac.update_ac(temperature_delta=temp)
if ac.temperature > 40:
# 根据温度随机出是否爆炸40度开始呈指数增长
possibility = -math.e ** ((40-ac.temperature) / 50) + 1
@@ -115,6 +120,8 @@ evt = on_alconna(Alconna(
@evt.handle()
async def _(event: BaseEvent, target: DepLongTaskTarget, temp: Optional[Union[int, float]] = 1):
+ if temp is None:
+ temp = 1
if temp <= 0:
return
id = target.channel_id
@@ -122,7 +129,7 @@ async def _(event: BaseEvent, target: DepLongTaskTarget, temp: Optional[Union[in
if not ac.on or ac.burnt == True or ac.frozen == True:
await send_ac_image(evt, ac)
return
- ac.temperature -= temp
+ ac.update_ac(temperature_delta=-temp)
if ac.temperature < 0:
# 根据温度随机出是否冻结0度开始呈指数增长
possibility = -math.e ** (ac.temperature / 50) + 1
@@ -141,6 +148,16 @@ async def _(event: BaseEvent, target: DepLongTaskTarget):
ac.change_ac()
await send_ac_image(evt, ac)
+def query_number_ranking(id: str) -> tuple[int, int]:
+ result = DatabaseManager.query_by_sql_file(
+ ROOT_PATH / "sql" / "query_crash_and_rank.sql",
+ (id,id)
+ )
+ if len(result) == 0:
+ return 0, 0
+ else:
+ return result[0].values()
+
evt = on_alconna(Alconna(
"空调炸炸排行榜",
), use_cmd_start=True, use_cmd_sep=False, skip_for_unmatch=True)
@@ -148,8 +165,9 @@ evt = on_alconna(Alconna(
@evt.handle()
async def _(event: BaseEvent, target: DepLongTaskTarget):
id = target.channel_id
- ac = get_ac(id)
- number, ranking = ac.get_crashes_and_ranking()
+ # ac = get_ac(id)
+ # number, ranking = ac.get_crashes_and_ranking()
+ number, ranking = query_number_ranking(id)
params = {
"number": number,
"ranking": ranking
diff --git a/konabot/plugins/air_conditioner/__preinit__.py b/konabot/plugins/air_conditioner/__preinit__.py
new file mode 100644
index 0000000..67054a0
--- /dev/null
+++ b/konabot/plugins/air_conditioner/__preinit__.py
@@ -0,0 +1,9 @@
+# 预初始化,只要是导入本插件包就会执行这里的代码
+from pathlib import Path
+
+from konabot.common.database import DatabaseManager
+
+# 初始化数据库表
+DatabaseManager.execute_by_sql_file(
+ Path(__file__).resolve().parent / "sql" / "create_table.sql"
+)
diff --git a/konabot/plugins/air_conditioner/ac.py b/konabot/plugins/air_conditioner/ac.py
index 6614784..9be7619 100644
--- a/konabot/plugins/air_conditioner/ac.py
+++ b/konabot/plugins/air_conditioner/ac.py
@@ -1,66 +1,112 @@
from enum import Enum
from io import BytesIO
+from pathlib import Path
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
+from konabot.common.database import DatabaseManager
from konabot.common.path import ASSETS_PATH, FONTS_PATH
from konabot.common.path import DATA_PATH
import json
+ROOT_PATH = Path(__file__).resolve().parent
+
class CrashType(Enum):
BURNT = 0
FROZEN = 1
class AirConditioner:
- air_conditioners: dict[str, "AirConditioner"] = {}
+ @classmethod
+ def get_ac(cls, id: str) -> 'AirConditioner':
+ result = DatabaseManager.query_by_sql_file(ROOT_PATH / "sql" / "query_ac.sql", (id,))
+ if len(result) == 0:
+ ac = cls.create_ac(id)
+ return ac
+ ac_data = result[0]
+ ac = AirConditioner(id)
+ ac.on = bool(ac_data["on"])
+ ac.temperature = float(ac_data["temperature"])
+ ac.burnt = bool(ac_data["burnt"])
+ ac.frozen = bool(ac_data["frozen"])
+ return ac
- def __init__(self, id: str) -> None:
- self.id = id
+ @classmethod
+ def create_ac(cls, id: str) -> 'AirConditioner':
+ ac = AirConditioner(id)
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "insert_ac.sql",
+ (id, ac.on, ac.temperature, ac.burnt, ac.frozen)
+ )
+ return ac
+
+ def update_ac(self, state: bool = None, temperature_delta: float = None, burnt: bool = None, frozen: bool = None) -> 'AirConditioner':
+ if state is not None:
+ self.on = state
+ if temperature_delta is not None:
+ self.temperature += temperature_delta
+ if burnt is not None:
+ self.burnt = burnt
+ if frozen is not None:
+ self.frozen = frozen
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "update_ac.sql",
+ (self.on, self.temperature, self.burnt, self.frozen, self.id)
+ )
+ return self
+
+ def change_ac(self) -> 'AirConditioner':
self.on = False
- self.temperature = 24 # 默认温度
+ self.temperature = 24
self.burnt = False
self.frozen = False
- AirConditioner.air_conditioners[id] = self
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "update_ac.sql",
+ (self.on, self.temperature, self.burnt, self.frozen, self.id)
+ )
+ return self
- def change_ac(self):
+ def __init__(self, id: str) -> None:
+ self.id = id
+ self.on = False
+ self.temperature = 24 # 默认温度
self.burnt = False
self.frozen = False
- self.on = False
- self.temperature = 24 # 重置为默认温度
def broke_ac(self, crash_type: CrashType):
'''
- 让空调坏掉,并保存数据
-
+ 让空调坏掉
:param crash_type: CrashType 枚举,表示空调坏掉的类型
'''
match crash_type:
case CrashType.BURNT:
- self.burnt = True
+ self.update_ac(burnt=True)
case CrashType.FROZEN:
- self.frozen = True
- self.save_crash_data(crash_type)
+ self.update_ac(frozen=True)
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "insert_crash.sql",
+ (self.id, crash_type.value)
+ )
- def save_crash_data(self, crash_type: CrashType):
- '''
- 如果空调爆炸了,就往本地的 ac_crash_data.json 里该 id 的记录加一
- '''
- data_file = DATA_PATH / "ac_crash_data.json"
- crash_data = {}
- if data_file.exists():
- with open(data_file, "r", encoding="utf-8") as f:
- crash_data = json.load(f)
- if self.id not in crash_data:
- crash_data[self.id] = {"burnt": 0, "frozen": 0}
- match crash_type:
- case CrashType.BURNT:
- crash_data[self.id]["burnt"] += 1
- case CrashType.FROZEN:
- crash_data[self.id]["frozen"] += 1
- with open(data_file, "w", encoding="utf-8") as f:
- json.dump(crash_data, f, ensure_ascii=False, indent=4)
+ # def save_crash_data(self, crash_type: CrashType):
+ # '''
+ # 如果空调爆炸了,就往本地的 ac_crash_data.json 里该 id 的记录加一
+ # '''
+ # data_file = DATA_PATH / "ac_crash_data.json"
+ # crash_data = {}
+ # if data_file.exists():
+ # with open(data_file, "r", encoding="utf-8") as f:
+ # crash_data = json.load(f)
+ # if self.id not in crash_data:
+ # crash_data[self.id] = {"burnt": 0, "frozen": 0}
+ # match crash_type:
+ # case CrashType.BURNT:
+ # crash_data[self.id]["burnt"] += 1
+ # case CrashType.FROZEN:
+ # crash_data[self.id]["frozen"] += 1
+ # with open(data_file, "w", encoding="utf-8") as f:
+ # json.dump(crash_data, f, ensure_ascii=False, indent=4)
def get_crashes_and_ranking(self) -> tuple[int, int]:
'''
diff --git a/konabot/plugins/air_conditioner/sql/create_table.sql b/konabot/plugins/air_conditioner/sql/create_table.sql
new file mode 100644
index 0000000..5203e23
--- /dev/null
+++ b/konabot/plugins/air_conditioner/sql/create_table.sql
@@ -0,0 +1,15 @@
+-- 创建所有表
+CREATE TABLE IF NOT EXISTS air_conditioner (
+ id VARCHAR(128) PRIMARY KEY,
+ 'on' BOOLEAN NOT NULL,
+ temperature REAL NOT NULL,
+ burnt BOOLEAN NOT NULL,
+ frozen BOOLEAN NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS air_conditioner_crash_log (
+ id VARCHAR(128) NOT NULL,
+ crash_type INT NOT NULL,
+ timestamp DATETIME NOT NULL,
+ FOREIGN KEY (id) REFERENCES air_conditioner(id)
+);
\ No newline at end of file
diff --git a/konabot/plugins/air_conditioner/sql/insert_ac.sql b/konabot/plugins/air_conditioner/sql/insert_ac.sql
new file mode 100644
index 0000000..3fb1c76
--- /dev/null
+++ b/konabot/plugins/air_conditioner/sql/insert_ac.sql
@@ -0,0 +1,3 @@
+-- 插入一台新空调
+INSERT INTO air_conditioner (id, 'on', temperature, burnt, frozen)
+VALUES (?, ?, ?, ?, ?);
\ No newline at end of file
diff --git a/konabot/plugins/air_conditioner/sql/insert_crash.sql b/konabot/plugins/air_conditioner/sql/insert_crash.sql
new file mode 100644
index 0000000..aae3898
--- /dev/null
+++ b/konabot/plugins/air_conditioner/sql/insert_crash.sql
@@ -0,0 +1,3 @@
+-- 插入一条空调爆炸记录
+INSERT INTO air_conditioner_crash_log (id, crash_type, timestamp)
+VALUES (?, ?, CURRENT_TIMESTAMP);
\ No newline at end of file
diff --git a/konabot/plugins/air_conditioner/sql/query_ac.sql b/konabot/plugins/air_conditioner/sql/query_ac.sql
new file mode 100644
index 0000000..db957d3
--- /dev/null
+++ b/konabot/plugins/air_conditioner/sql/query_ac.sql
@@ -0,0 +1,4 @@
+-- 查询空调状态,如果没有就插入一条新的记录
+SELECT *
+FROM air_conditioner
+WHERE id = ?;
\ No newline at end of file
diff --git a/konabot/plugins/air_conditioner/sql/query_crash_and_rank.sql b/konabot/plugins/air_conditioner/sql/query_crash_and_rank.sql
new file mode 100644
index 0000000..c180638
--- /dev/null
+++ b/konabot/plugins/air_conditioner/sql/query_crash_and_rank.sql
@@ -0,0 +1,23 @@
+-- 从 air_conditioner_crash_log 表中获取指定 id 损坏的次数以及损坏次数的排名
+SELECT crash_count, crash_rank
+FROM (
+ SELECT id,
+ COUNT(*) AS crash_count,
+ RANK() OVER (ORDER BY COUNT(*) DESC) AS crash_rank
+ FROM air_conditioner_crash_log
+ GROUP BY id
+) AS ranked_data
+WHERE id = ?
+-- 如果该 id 没有损坏记录,则返回 0 次损坏和对应的最后一名
+UNION
+SELECT 0 AS crash_count,
+ (SELECT COUNT(DISTINCT id) + 1 FROM air_conditioner_crash_log) AS crash_rank
+FROM (
+ SELECT DISTINCT id
+ FROM air_conditioner_crash_log
+) AS ranked_data
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM air_conditioner_crash_log
+ WHERE id = ?
+);
\ No newline at end of file
diff --git a/konabot/plugins/air_conditioner/sql/update_ac.sql b/konabot/plugins/air_conditioner/sql/update_ac.sql
new file mode 100644
index 0000000..df9145e
--- /dev/null
+++ b/konabot/plugins/air_conditioner/sql/update_ac.sql
@@ -0,0 +1,4 @@
+-- 更新空调状态
+UPDATE air_conditioner
+SET 'on' = ?, temperature = ?, burnt = ?, frozen = ?
+WHERE id = ?;
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/__init__.py b/konabot/plugins/idiomgame/__init__.py
index ee4e26c..36710aa 100644
--- a/konabot/plugins/idiomgame/__init__.py
+++ b/konabot/plugins/idiomgame/__init__.py
@@ -18,11 +18,14 @@ from nonebot_plugin_alconna import (
on_alconna,
)
+from konabot.common.database import DatabaseManager
from konabot.common.longtask import DepLongTaskTarget
from konabot.common.path import ASSETS_PATH
from konabot.common.llm import get_llm
+ROOT_PATH = Path(__file__).resolve().parent
+
DATA_DIR = Path(__file__).parent.parent.parent.parent / "data"
DATA_FILE_PATH = (
@@ -94,18 +97,19 @@ class IdiomGameLLM:
@classmethod
async def storage_idiom(cls, idiom: str):
- # 将 idiom 存入本地文件以备后续分析
- with open(DATA_DIR / "idiom_llm_storage.txt", "a", encoding="utf-8") as f:
- f.write(idiom + "\n")
- IdiomGame.append_into_word_list(idiom)
+ # 将 idiom 存入数据库
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "insert_custom_word.sql",
+ (idiom,)
+ )
class IdiomGame:
- ALL_WORDS = [] # 所有四字词语
- ALL_IDIOMS = [] # 所有成语
+ # ALL_WORDS = [] # 所有四字词语
+ # ALL_IDIOMS = [] # 所有成语
INSTANCE_LIST: dict[str, "IdiomGame"] = {} # 群号对应的游戏实例
- IDIOM_FIRST_CHAR = {} # 所有成语包括词语的首字字典
- AVALIABLE_IDIOM_FIRST_CHAR = {} # 真正有效的成语首字字典
+ # IDIOM_FIRST_CHAR = {} # 所有成语包括词语的首字字典
+ # AVALIABLE_IDIOM_FIRST_CHAR = {} # 真正有效的成语首字字典
__inited = False
@@ -130,11 +134,10 @@ class IdiomGame:
'''
将一个新词加入到词语列表中
'''
- if word not in cls.ALL_WORDS:
- cls.ALL_WORDS.append(word)
- if word[0] not in cls.IDIOM_FIRST_CHAR:
- cls.IDIOM_FIRST_CHAR[word[0]] = []
- cls.IDIOM_FIRST_CHAR[word[0]].append(word)
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "insert_custom_word.sql",
+ (word,)
+ )
def be_able_to_play(self) -> bool:
if self.last_play_date != datetime.date.today():
@@ -145,11 +148,17 @@ class IdiomGame:
return True
return False
+ @staticmethod
+ def random_idiom() -> str:
+ return DatabaseManager.query_by_sql_file(
+ ROOT_PATH / "sql" / "random_choose_idiom.sql"
+ )[0]["idiom"]
+
def choose_start_idiom(self) -> str:
"""
随机选择一个成语作为起始成语
"""
- self.last_idiom = secrets.choice(IdiomGame.ALL_IDIOMS)
+ self.last_idiom = IdiomGame.random_idiom()
self.last_char = self.last_idiom[-1]
if not self.is_nextable(self.last_char):
self.choose_start_idiom()
@@ -208,7 +217,7 @@ class IdiomGame:
return self.last_idiom
def _skip_idiom_async(self) -> str:
- self.last_idiom = secrets.choice(IdiomGame.ALL_IDIOMS)
+ self.last_idiom = IdiomGame.random_idiom()
self.last_char = self.last_idiom[-1]
if not self.is_nextable(self.last_char):
self._skip_idiom_async()
@@ -228,8 +237,11 @@ class IdiomGame:
"""
判断是否有成语可以接
"""
- return last_char in IdiomGame.AVALIABLE_IDIOM_FIRST_CHAR
-
+ return DatabaseManager.query_by_sql_file(
+ ROOT_PATH / "sql" / "is_nextable.sql",
+ (last_char,)
+ )[0]["DEED"] == 1
+
def add_already_idiom(self, idiom: str):
if idiom in self.already_idioms:
self.already_idioms[idiom] += 1
@@ -259,7 +271,12 @@ class IdiomGame:
if idiom[0] != self.last_char:
state.append(TryVerifyState.WRONG_FIRST_CHAR)
return state
- if idiom not in IdiomGame.ALL_IDIOMS and idiom not in IdiomGame.ALL_WORDS:
+ # 成语是否存在
+ result = DatabaseManager.query_by_sql_file(
+ ROOT_PATH / "sql" / "query_idiom.sql",
+ (idiom, idiom, idiom)
+ )[0]["status"]
+ if result == -1:
logger.info(f"用户 {user_id} 发送了未知词语 {idiom},正在使用 LLM 进行验证")
try:
if not await IdiomGameLLM.verify_idiom_with_llm(idiom):
@@ -281,7 +298,7 @@ class IdiomGame:
self.last_idiom = idiom
self.last_char = idiom[-1]
self.add_score(user_id, 1 * score_k) # 先加 1 分
- if idiom in IdiomGame.ALL_IDIOMS:
+ if result == 1:
state.append(TryVerifyState.VERIFIED_AND_REAL)
self.add_score(user_id, 4 * score_k) # 再加 4 分
self.remain_rounds -= 1
@@ -319,14 +336,21 @@ class IdiomGame:
@classmethod
def random_idiom_starting_with(cls, first_char: str) -> Optional[str]:
cls.init_lexicon()
- if first_char not in cls.AVALIABLE_IDIOM_FIRST_CHAR:
+ result = DatabaseManager.query_by_sql_file(
+ ROOT_PATH / "sql" / "query_idiom_start_with.sql",
+ (first_char,)
+ )
+ if len(result) == 0:
return None
- return secrets.choice(cls.AVALIABLE_IDIOM_FIRST_CHAR[first_char])
+ return result[0]["idiom"]
@classmethod
def init_lexicon(cls):
if cls.__inited:
return
+ DatabaseManager.execute_by_sql_file(
+ ROOT_PATH / "sql" / "create_table.sql"
+ ) # 确保数据库初始化
cls.__inited = True
# 成语大表
@@ -334,11 +358,12 @@ class IdiomGame:
ALL_IDIOMS_INFOS = json.load(f)
# 词语大表
+ ALL_WORDS = []
with open(ASSETS_PATH / "lexicon" / "ci.json", "r", encoding="utf-8") as f:
jsonData = json.load(f)
- cls.ALL_WORDS = [item["ci"] for item in jsonData]
- logger.debug(f"Loaded {len(cls.ALL_WORDS)} words from ci.json")
- logger.debug(f"Sample words: {cls.ALL_WORDS[:5]}")
+ ALL_WORDS = [item["ci"] for item in jsonData]
+ logger.debug(f"Loaded {len(ALL_WORDS)} words from ci.json")
+ logger.debug(f"Sample words: {ALL_WORDS[:5]}")
COMMON_WORDS = []
# 读取 COMMON 词语大表
@@ -389,29 +414,44 @@ class IdiomGame:
logger.debug(f"Loaded additional {len(LOCAL_LLM_WORDS)} words from idiom_llm_storage.txt")
# 只有成语的大表
- cls.ALL_IDIOMS = [idiom["word"] for idiom in ALL_IDIOMS_INFOS] + THUOCL_IDIOMS
- cls.ALL_IDIOMS = list(set(cls.ALL_IDIOMS)) # 去重
+ ALL_IDIOMS = [idiom["word"] for idiom in ALL_IDIOMS_INFOS] + THUOCL_IDIOMS
+ ALL_IDIOMS = list(set(ALL_IDIOMS)) # 去重
+ # 批量插入数据库
+ DatabaseManager.execute_many_values_by_sql_file(
+ ROOT_PATH / "sql" / "insert_idiom.sql",
+ [(idiom,) for idiom in ALL_IDIOMS]
+ )
+
# 其他四字词语表,仅表示可以有这个词
- cls.ALL_WORDS = (
- [word for word in cls.ALL_WORDS if len(word) == 4]
+ ALL_WORDS = (
+ [word for word in ALL_WORDS if len(word) == 4]
+ THUOCL_WORDS
+ COMMON_WORDS
- + LOCAL_LLM_WORDS
)
- cls.ALL_WORDS = list(set(cls.ALL_WORDS)) # 去重
+ # 插入数据库
+ DatabaseManager.execute_many_values_by_sql_file(
+ ROOT_PATH / "sql" / "insert_word.sql",
+ [(word,) for word in ALL_WORDS]
+ )
+
+ # 自定义词语 LOCAL_LLM_WORDS 插入数据库,兼容用
+ DatabaseManager.execute_many_values_by_sql_file(
+ ROOT_PATH / "sql" / "insert_custom_word.sql",
+ [(word,) for word in LOCAL_LLM_WORDS]
+ )
- # 根据成语大表,划分出成语首字字典
- for idiom in cls.ALL_IDIOMS + cls.ALL_WORDS:
- if idiom[0] not in cls.IDIOM_FIRST_CHAR:
- cls.IDIOM_FIRST_CHAR[idiom[0]] = []
- cls.IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
+ # # 根据成语大表,划分出成语首字字典
+ # for idiom in cls.ALL_IDIOMS + cls.ALL_WORDS:
+ # if idiom[0] not in cls.IDIOM_FIRST_CHAR:
+ # cls.IDIOM_FIRST_CHAR[idiom[0]] = []
+ # cls.IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
- # 根据真正的成语大表,划分出有效成语首字字典
- for idiom in cls.ALL_IDIOMS:
- if idiom[0] not in cls.AVALIABLE_IDIOM_FIRST_CHAR:
- cls.AVALIABLE_IDIOM_FIRST_CHAR[idiom[0]] = []
- cls.AVALIABLE_IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
+ # # 根据真正的成语大表,划分出有效成语首字字典
+ # for idiom in cls.ALL_IDIOMS:
+ # if idiom[0] not in cls.AVALIABLE_IDIOM_FIRST_CHAR:
+ # cls.AVALIABLE_IDIOM_FIRST_CHAR[idiom[0]] = []
+ # cls.AVALIABLE_IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
evt = on_alconna(
@@ -514,7 +554,9 @@ async def end_game(event: BaseEvent, group_id: str):
for line in history_lines:
result_text += line + "\n"
await evt.send(await result_text.export())
- instance.clear_score_board()
+ # instance.clear_score_board()
+ # 将实例删除
+ del IdiomGame.INSTANCE_LIST[group_id]
evt = on_alconna(
diff --git a/konabot/plugins/idiomgame/sql/create_table.sql b/konabot/plugins/idiomgame/sql/create_table.sql
new file mode 100644
index 0000000..5d38580
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/create_table.sql
@@ -0,0 +1,15 @@
+-- 创建成语大表
+CREATE TABLE IF NOT EXISTS all_idioms (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ idiom VARCHAR(128) NOT NULL UNIQUE
+);
+
+CREATE TABLE IF NOT EXISTS all_words (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ word VARCHAR(128) NOT NULL UNIQUE
+);
+
+CREATE TABLE IF NOT EXISTS custom_words (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ word VARCHAR(128) NOT NULL UNIQUE
+);
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/sql/insert_custom_word.sql b/konabot/plugins/idiomgame/sql/insert_custom_word.sql
new file mode 100644
index 0000000..212c8a2
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/insert_custom_word.sql
@@ -0,0 +1,3 @@
+-- 插入自定义词
+INSERT OR IGNORE INTO custom_words (word)
+VALUES (?);
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/sql/insert_idiom.sql b/konabot/plugins/idiomgame/sql/insert_idiom.sql
new file mode 100644
index 0000000..eaedae8
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/insert_idiom.sql
@@ -0,0 +1,3 @@
+-- 插入成语大表,避免重复插入
+INSERT OR IGNORE INTO all_idioms (idiom)
+VALUES (?);
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/sql/insert_word.sql b/konabot/plugins/idiomgame/sql/insert_word.sql
new file mode 100644
index 0000000..b085aab
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/insert_word.sql
@@ -0,0 +1,3 @@
+-- 插入词
+INSERT OR IGNORE INTO all_words (word)
+VALUES (?);
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/sql/is_nextable.sql b/konabot/plugins/idiomgame/sql/is_nextable.sql
new file mode 100644
index 0000000..a7bbeb1
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/is_nextable.sql
@@ -0,0 +1,5 @@
+-- 查询是否有以 xx 开头的成语,有则返回真,否则假
+SELECT EXISTS(
+ SELECT 1 FROM all_idioms
+ WHERE idiom LIKE ? || '%'
+) AS DEED;
diff --git a/konabot/plugins/idiomgame/sql/query_idiom.sql b/konabot/plugins/idiomgame/sql/query_idiom.sql
new file mode 100644
index 0000000..fa3bf93
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/query_idiom.sql
@@ -0,0 +1,7 @@
+-- 查询成语是否在 all_idioms 中,如果存在则返回 1否则再判断是否在 custom_words 或 all_words 中,存在则返回 0否则返回 -1
+SELECT
+ CASE
+ WHEN EXISTS (SELECT 1 FROM all_idioms WHERE idiom = ?) THEN 1
+ WHEN EXISTS (SELECT 1 FROM custom_words WHERE word = ?) OR EXISTS (SELECT 1 FROM all_words WHERE word = ?) THEN 0
+ ELSE -1
+ END AS status;
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/sql/query_idiom_start_with.sql b/konabot/plugins/idiomgame/sql/query_idiom_start_with.sql
new file mode 100644
index 0000000..a6e8fc6
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/query_idiom_start_with.sql
@@ -0,0 +1,4 @@
+-- 查询以 xx 开头的成语,随机打乱后只取第一个
+SELECT idiom FROM all_idioms
+WHERE idiom LIKE ? || '%'
+ORDER BY RANDOM() LIMIT 1;
\ No newline at end of file
diff --git a/konabot/plugins/idiomgame/sql/random_choose_idiom.sql b/konabot/plugins/idiomgame/sql/random_choose_idiom.sql
new file mode 100644
index 0000000..f706092
--- /dev/null
+++ b/konabot/plugins/idiomgame/sql/random_choose_idiom.sql
@@ -0,0 +1,2 @@
+-- 随机从 all_idioms 表中选择一个成语
+SELECT idiom FROM all_idioms ORDER BY RANDOM() LIMIT 1;
\ No newline at end of file