diff --git a/config.gen.py b/config.gen.py
index eaee1e3..732d534 100644
--- a/config.gen.py
+++ b/config.gen.py
@@ -25,5 +25,6 @@ OWNER = 0
CHANNEL = 0
# 帮助信息
-HELP = 'Group Word Cloud Bot\n\n/start - 查看此帮助信息\n/ping - 我还活着吗?\n/rank - 手动生成词云(绒布球)\n\n' \
+HELP = 'Group Word Cloud Bot\n\n/start - 查看此帮助信息\n/ping - 我还活着吗?\n/rank - 手动生成词云(绒布球)\n' \
+ '/stat - 生成单用户词云\n\n' \
'此项目开源于:https://git.io/JnrvH'
diff --git a/func.py b/func.py
index 371d306..ad5baed 100644
--- a/func.py
+++ b/func.py
@@ -5,7 +5,7 @@ import telegram
from telegram.ext import CommandHandler, MessageHandler, Filters
from config import TOKEN, LIMIT_COUNT, EXCLUSIVE_MODE, RANK_COMMAND_MODE, OWNER, EXCLUSIVE_LIST, CHANNEL, HELP
import schedule
-from task import add_task
+from task import add_task, add_user_task
bot = telegram.Bot(token=TOKEN)
@@ -102,6 +102,53 @@ def rank(update, context):
print(e)
+def stat(update, context):
+ try:
+ r = connector.get_connection()
+ chat_type = update.effective_chat.type
+ user_id = update.effective_user.id
+ chat_id = update.effective_message.chat_id
+ try:
+ username = update.effective_user.username
+ except Exception as e:
+ username = update.effective_user.id
+ # 限制为群组
+ if chat_type != "supergroup":
+ update.message.reply_text("此命令只有在群组中有效!")
+ return
+ if r.exists("{}_{}_frequency_limit".format(chat_id, user_id)):
+ r.setrange("{}_{}_frequency_limit".format(chat_id, user_id), 0,
+ int(r.get("{}_{}_frequency_limit".format(chat_id, user_id))) + 1)
+ else:
+ struct_time = time.localtime(time.time())
+ # 数据过期时间为当前小时的 59 分
+ ex_time = datetime.datetime(
+ struct_time.tm_year,
+ struct_time.tm_mon,
+ struct_time.tm_mday,
+ struct_time.tm_hour,
+ 59
+ )
+ r.set("{}_{}_frequency_limit".format(chat_id, user_id), 1)
+ r.expireat("{}_{}_frequency_limit".format(chat_id, user_id), ex_time)
+ count = int(r.get("{}_{}_frequency_limit".format(chat_id, user_id)))
+ if count > LIMIT_COUNT:
+ update.message.reply_text(f"[您](tg://user?id={user_id})在这个小时内的生成配额已经用完,请稍后再试~")
+ return
+ add_user_task(chat_id, user_id)
+ print("群组: {},用户: {} | {} 发起了主动触发请求".format(chat_id, username, user_id, ))
+ if not CHANNEL == 0:
+ ctext = f'#WORDCLOUD #APPLY #id{user_id} \n' \
+ f'群组 ID:`{chat_id}`\n' \
+ f'用户 ID:`{user_id}`\n' \
+ f'执行操作:`主动生成用户词云`'
+ bot.send_message(chat_id=CHANNEL, text=ctext, parse_mode="Markdown")
+ update.message.reply_text("统计数据将在分析完毕后发送到当前群组,请稍等~")
+ except Exception as e:
+ print("主动触发任务失败,请检查")
+ print(e)
+
+
def chat_content_exec(update, context):
try:
r = connector.get_connection()
@@ -133,9 +180,12 @@ def chat_content_exec(update, context):
else:
if text[-1] not in [",", "。", "!", ":", "?", "!", "?", ",", ":", "."]:
r.append("{}_chat_content".format(chat_id), text + "。")
+ r.append("{}_{}_user_content".format(chat_id, user_id), text + "。")
else:
r.append("{}_chat_content".format(chat_id), text)
+ r.append("{}_{}_user_content".format(chat_id, user_id), text)
r.incrby("{}_total_message_amount".format(chat_id))
+ r.incrby("{}_{}_user_message_amount".format(chat_id, user_id))
r.hincrby("{}_user_message_amount".format(chat_id), name)
print("---------------------------")
except Exception as e:
@@ -152,4 +202,5 @@ def check_schedule():
start_handler = CommandHandler('start', start)
ping_handler = CommandHandler('ping', ping)
rank_handler = CommandHandler('rank', rank)
+stat_handler = CommandHandler('stat', stat)
chat_content_handler = MessageHandler(Filters.text, chat_content_exec)
diff --git a/main.py b/main.py
index 90b30c5..1b18046 100644
--- a/main.py
+++ b/main.py
@@ -1,8 +1,8 @@
from telegram.ext import Updater
from config import TOKEN
-from func import start_handler, ping_handler, chat_content_handler, check_schedule, rank_handler
+from func import start_handler, ping_handler, chat_content_handler, check_schedule, rank_handler, stat_handler
import schedule
-from task import schedule_task, flush_redis, do_task
+from task import schedule_task, flush_redis, do_task, do_user_task
import threading
# 开始定时任务 - 群组分析
@@ -14,8 +14,9 @@ schedule.every().day.at('23:59').do(flush_redis)
# 测试代码,每分钟推送数据,非测试目的不要取消注释下一行
# schedule.every(1).minutes.do(schedule_task)
-# 开启分析线程,当队列中由任务时,会取出任务分析生成数据
+# 开启分析线程,当队列中有任务时,会取出任务分析生成数据
threading.Thread(target=do_task).start()
+threading.Thread(target=do_user_task).start()
threading.Thread(target=check_schedule).start()
@@ -25,6 +26,7 @@ dispatcher = updater.dispatcher
dispatcher.add_handler(start_handler)
dispatcher.add_handler(ping_handler)
dispatcher.add_handler(rank_handler)
+dispatcher.add_handler(stat_handler)
dispatcher.add_handler(chat_content_handler)
updater.start_polling()
diff --git a/task.py b/task.py
index 13fd1bf..101e448 100644
--- a/task.py
+++ b/task.py
@@ -13,6 +13,7 @@ from config import TOKEN, FRONT, CHANNEL
bot = telegram.Bot(token=TOKEN)
task_queue = queue.Queue()
+user_task_queue = queue.Queue()
def schedule_task():
@@ -66,10 +67,46 @@ def do_task():
time.sleep(1)
+def do_user_task():
+ while True:
+ temp = user_task_queue.get()
+ group = temp.split('|')[0]
+ uid = temp.split('|')[1]
+ try:
+ print("---------------------------")
+ print("群组: {} | 用户: {} | 分析处理中... | 剩余任务数量 {}".format(group, uid, task_queue.qsize()))
+ start_time = float(time.time())
+ generate_user(group, uid)
+ stop_time = float(time.time())
+ print("当前群组处理耗时:" + str(stop_time - start_time))
+ print("---------------------------")
+ ctext = f'#WORDCLOUD #id{uid} \n' \
+ f'群组 ID:`{group}`\n' \
+ f'用户 ID:`{uid}`\n' \
+ f'执行操作:`生成用户词云`\n' \
+ f'结果:`成功`\n' \
+ f'处理耗时:`{str(stop_time - start_time)[:5]}`'
+ except Exception as e:
+ print("群组: {} | 用户: {} | 处理失败,可能是机器人已经被移出群组,请检查报错!".format(group, uid))
+ print(e)
+ ctext = f'#WORDCLOUD #SCHEDULE #id{uid} \n' \
+ f'群组 ID:`{group}`\n' \
+ f'用户 ID:`{uid}`\n' \
+ f'执行操作:`生成用户词云`\n' \
+ f'结果:`失败`\n'
+ if not CHANNEL == 0:
+ bot.send_message(chat_id=CHANNEL, text=ctext, parse_mode="Markdown")
+ time.sleep(1)
+
+
def add_task(group):
task_queue.put(group)
+def add_user_task(group, uid):
+ user_task_queue.put(f'{group}|{uid}')
+
+
# 核心函数,分词统计
def generate(group):
mk = imageio.imread("circle.png")
@@ -137,7 +174,7 @@ def generate(group):
hot_word_string += "\t\t\t\t\t\t\t\t" + "👥 `" + str(word_amount[i][0]) + "`" + ":" + str(
word_amount[i][1]) + "\n"
# print(hot_word_string)
- text += f"🗣️ 本群 {user_amount} 位成员共产生 {total_message_amount} 条发言\n" \
+ text += f"🗣️ 本群 {user_amount} 位成员共产生 {total_message_amount} 条纯文本消息\n" \
f"🤹 大家今天讨论最多的是:\n\n{hot_word_string}\n"
else:
text += '无法分析出当前群组的热词列表,可能是数据量过小,嗨起来吧~\n'
@@ -193,6 +230,107 @@ def generate(group):
print("删除图片失败")
+# 核心函数,用户分词统计
+def generate_user(group, uid):
+ mk = imageio.imread("circle.png")
+ # 构建并配置词云对象w,注意要加scale参数,提高清晰度
+ w = wordcloud.WordCloud(width=800,
+ height=800,
+ background_color='white',
+ font_path=FRONT,
+ mask=mk,
+ scale=5)
+ r = connector.get_connection()
+ print(f"当前处理的群组:{group} | {uid}")
+ # 生成词云图片
+ jieba.enable_paddle() # 启动paddle模式。 0.40版之后开始支持,早期版本不支持
+ chat_content = r.get("{}_{}_user_content".format(group, uid))
+
+ if chat_content is None:
+ print("数据库中不存在此用户 {} | {} 数据".format(group, uid))
+ return
+ word_list = []
+ words = pseg.cut(chat_content, use_paddle=True) # paddle模式
+ for word, flag in words:
+ # print(word + "\t" + flag)
+ if flag in ["n", "nr", "nz", "PER", "f", "ns", "LOC", "s", "nt", "ORG", "nw"]:
+ # 判断该词是否有效,不为空格
+ if re.match(r"^\s+?$", word) is None:
+ word_list.append(word)
+ # print(word_list)
+
+ # 获取消息总数
+ total_message_amount = r.get("{}_{}_user_message_amount".format(group, uid))
+
+ # 截至时间
+ date = time.strftime("%Y年%m月%d日", time.localtime()) + ' ⏱ ' + time.strftime("%H:%M", time.localtime())
+ text = f'📅 截至 {date}\n'
+ # 分析高频词
+ if len(word_list) > 0:
+ word_amount = {}
+ # print(word_amount)
+ for word in word_list:
+ if re.search(
+ r"[。|,|、|?|!|,|.|!|?|\\|/|+|\-|`|~|·|@|#|¥|$|%|^|&|*|(|)|;|;|‘|’|“|”|'|_|=|•|·|…|\"]",
+ word) is not None:
+ continue
+ # 判断该词是否之前已经出现
+ if word_amount.get(word) is not None:
+ word_amount[word] = word_amount.get(word) + 1
+ else:
+ word_amount[word] = 1
+ # print(word_amount)
+ word_amount = sorted(word_amount.items(), key=lambda kv: (int(kv[1])), reverse=True)
+ if len(word_amount) > 0:
+ # print("排序后的热词:" + str(word_amount))
+ hot_word_string = ""
+ # 默认展示前5位,少于5个则全部展示
+ for i in range(min(5, len(word_amount))):
+ hot_word_string += "\t\t\t\t\t\t\t\t" + "👥 `" + str(word_amount[i][0]) + "`" + ":" + str(
+ word_amount[i][1]) + "\n"
+ # print(hot_word_string)
+ text += f"🗣️ [此成员](tg://user?id={uid})共产生 {total_message_amount} 条纯文本消息\n" \
+ f"🤹 Ta 今天讨论最多的是:\n\n{hot_word_string}\n"
+ else:
+ text += '无法分析出 Ta 的热词列表,可能是数据量过小,嗨起来吧~\n'
+ else:
+ text += '无法分析出 Ta 的热词列表,可能是数据量过小,嗨起来吧~\n'
+
+ # 开始创建词云
+ img_path = 'images/default.png'
+ try:
+ string = " ".join(word_list)
+ # 将string变量传入w的generate()方法,给词云输入文字
+ w.generate(string)
+ # 将词云图片导出到 images 文件夹
+ w.to_file('images/{}_chat_word_cloud.png'.format(group))
+ img_path = 'images/{}_chat_word_cloud.png'.format(group)
+ except Exception as e:
+ print(e)
+ print("词云图片生成失败")
+
+ # 发送结果
+ try:
+ bot.send_photo(
+ chat_id=group,
+ photo=open(img_path, "rb"),
+ caption=text,
+ parse_mode='Markdown',
+ disable_notification=True
+ )
+ except Exception as e:
+ print(e)
+ r.delete('{}_{}_user_content'.format(group, uid))
+ print("发送结果失败")
+
+ # 删除图片
+ try:
+ os.remove("images/{}_chat_word_cloud.png".format(group))
+ except Exception as e:
+ print(e)
+ print("删除图片失败")
+
+
def flush_redis():
r = connector.get_connection()
r.flushall()