前言
這幾天我的一個(gè)小伙伴問我能不能給 Ta 做一個(gè)配置靈活的微信群聊天機(jī)器人,之前了解過 itchat
庫的使用聚霜,我就爽快的答應(yīng)了,花了一個(gè)晚上珠叔,終于做出了雛形蝎宇。
電腦上運(yùn)行程序如下:
手機(jī)上的信息如下:
其實(shí)基于 itchat
的微信機(jī)器人早已經(jīng)爛大街了,但大多數(shù)過于簡(jiǎn)單祷安,相比較而言姥芥,我的這個(gè)程序有下面幾大鮮明的特色:
1、支持打開/關(guān)閉指定群聊的自動(dòng)回復(fù)汇鞭,只需用記事本打開 group.csv
文件凉唐,填寫想要打開自動(dòng)回復(fù)群聊名稱即可,每次輸完一個(gè)群聊名稱霍骄,必須換行台囱。
2、支持自定義設(shè)置關(guān)鍵詞回復(fù)读整,只需用記事本打開 keyword.csv
文件簿训,按照{關(guān)鍵詞,回復(fù)}的格式添加即可,而不需要在代碼中做任何修改。輸完一個(gè)鍵值對(duì)强品,同樣也需要換行膘侮,注意輸入的是英文逗號(hào)。
3的榛、支持定時(shí)群發(fā)消息琼了,而且時(shí)間、消息可以在程序運(yùn)行中動(dòng)態(tài)修改困曙。
4表伦、有較為良好的 GUI
界面,其中色彩搭配參考了微信的簡(jiǎn)約設(shè)計(jì)慷丽。
DIY 玩法
1、面向商戶
作為商戶鳄哭,維護(hù)群的時(shí)候可能有這樣的需求要糊,定時(shí)發(fā)送消息。比如
回復(fù) xxx 可獲得 yyy
,同時(shí)在keyword.csv
文件中事先寫好妆丘。這樣锄俄,可以讓群成員各取所需,你又不需要打字勺拣、復(fù)制粘貼奶赠,還可以同時(shí)處理多個(gè)群,省心還高效药有。
2毅戈、面向普通個(gè)人
定時(shí)向男女朋友,父母親人發(fā)送晚安祝福消息等愤惰。
3苇经、部署至阿里云服務(wù)器
有個(gè)缺陷就是如果想一直自動(dòng)群發(fā)消息的話,你的電腦就必須一直開著宦言,但是部署至云服務(wù)器可以解決這個(gè)問題扇单。部署流程可以參考我之前的文章, 自己動(dòng)手打造mini型QQ(二):從局域網(wǎng)到互聯(lián)網(wǎng)的miniQQ 同時(shí)給出阿里云服務(wù)器優(yōu)惠購買傳送門。點(diǎn)擊傳送
代碼的詳細(xì)設(shè)計(jì)
1奠旺、代碼的架構(gòu)
由于引入了 GUI
蜘澜,GUI
代碼塊和負(fù)責(zé)群發(fā)消息的代碼塊一樣,都是阻塞型的响疚,為此鄙信,程序就必須引入多線程機(jī)制,其中 GUI
界面是主線程稽寒,負(fù)責(zé)群發(fā)消息的代碼塊運(yùn)行在子線程扮碧,線程間的通信我用的是 wxPython
內(nèi)置的 wx.lib.pubsub
模塊,一旦子線程執(zhí)行了相應(yīng)的動(dòng)作,就通過 wx.CallAfter(pub.sendMessage)
接口發(fā)送消息給通知 GUI
線程慎王,從而保證 GUI
能夠及時(shí)刷新并不至于卡頓蚓土。
2、代碼的流程
首先是加載相應(yīng)的配置文件赖淤,確定要開啟哪些群聊的自動(dòng)回復(fù)蜀漆,以及關(guān)鍵詞回復(fù)信息。也正因如此咱旱,在程序執(zhí)行過程中确丢,這些信息是不能被動(dòng)態(tài)改變的。
其中加載 keyword
的代碼如下:
def load_keyword(self):
global keywords
with open('keyword.csv', 'r', encoding='utf-8', newline='') as f:
reader = csv.reader(f)
for i, line in enumerate(reader):
if i == 0:
continue
keywords[line[0]] = line[1]
把 keywords
設(shè)置為全局變量方便后面使用吐限,避免傳參調(diào)用鲜侥,判斷 i == 0
是為了去掉 csv
文件的第一行頭部信息。
負(fù)責(zé)群發(fā)的主要代碼塊如下诸典,代碼注釋較為清晰描函,不再贅述
@itchat.msg_register(TEXT, isGroupChat=True)
def group_text(msg):
global keywords
groups = itchat.get_chatrooms(update=True)
for group in groups:
# 群的 NickName 是群名稱,UserName 是群id(以兩個(gè)@開始)
# Python/Java 學(xué)習(xí)交流群
if group['NickName'] in group_names: # 從群中找到指定的群聊
group_id = msg['FromUserName']
# 防止其他群消息的的干擾
if not group_id == group['UserName']:
break
# 準(zhǔn)備回復(fù)的消息
keys = keywords.keys()
key = ''
for i in keys:
if i in msg['Text']:
key = i
break
if key == '':
return
message = keywords.get(key)
# 在消息中找到 發(fā)送人的id
sender_id = msg['ActualUserName']
# 有時(shí) group['MemberList'] 為空狐粱,改變思路由群 id 獲取群聊成員
# group_info = itchat.update_chatroom(msg['ToUserName'], detailedMember=True)
# if len(group_info) == 0:
# toUserName 是自己在群聊發(fā)消息時(shí)舀寓,群 id 在消息里的 key
# FromUserName 是別人在群里發(fā)時(shí),群 id 在消息里的 key
group_info = itchat.update_chatroom(group_id, detailedMember=True)
memberlist = group_info['MemberList']
for member in memberlist:
# 找到消息的發(fā)送者
if member['UserName'] == sender_id:
# 如果有備注名肌蜻,群聊顯示的是備注名
to_user = member['RemarkName']
if len(to_user) == 0:
# 否則顯示成員自己修改的在群里的昵稱
to_user = member['DisplayName']
if len(to_user) == 0:
# 否則顯示他微信號(hào)的昵稱
to_user = member['NickName']
itchat.send_msg('@{}\n{}'.format(to_user, message), group['UserName'])
wx.CallAfter(pub.sendMessage, "update", msg="回復(fù)群聊[{}]成員[{}]成功:[{}]".format(group['NickName'],to_user,message))
負(fù)責(zé)定時(shí)群發(fā)的代碼和上面的代碼比較獨(dú)立互墓,在子線程開始的同時(shí),開始執(zhí)行定時(shí)群發(fā)的邏輯
def run(self):
global t
t = threading.Timer(minutes * 60, self.auto_timer)
t.start()
self.load_keyword()
self.load_group()
itchat.auto_login(hotReload=True)
itchat.run()
其中主要的函數(shù)是 threading.Timer(minutes * 60, self.auto_timer)
,它的意思是在負(fù)責(zé)執(zhí)行群發(fā)的線程里蒋搜,再開一個(gè)線程篡撵,這個(gè)線程間隔minutes * 60
秒后去執(zhí)行回調(diào)函數(shù) self.auto_timer
,但是這樣只能觸發(fā)一次,沒辦法一直輪詢齿诞,解決辦法是在回調(diào)函數(shù)里面再去執(zhí)行 threading.Timer(minutes * 60, self.auto_timer)
,有點(diǎn)兒類似于遞歸調(diào)用酸休,和遞歸不同的是,調(diào)用是沒有終止條件的祷杈,但并不會(huì)產(chǎn)生內(nèi)存溢出斑司,因?yàn)槎〞r(shí)器的存在,時(shí)間一到一觸發(fā)回調(diào)函數(shù)但汞,這個(gè)線程的生命就到此為止了宿刮,因此在整個(gè)程序運(yùn)行期間,活躍線程的數(shù)目?jī)H僅只是個(gè)位數(shù):
def auto_timer(self):
global auto_message
groups = itchat.get_chatrooms(update=True)
for group in groups:
if group['NickName'] in group_names:
itchat.send_msg('{}'.format(auto_message), group['UserName'])
wx.CallAfter(pub.sendMessage, "update",
msg="群聊[{}]定時(shí)消息:[{}]發(fā)送成功".format(group['NickName'], auto_message))
global t # 把 t 設(shè)置為全局變量
t = threading.Timer(minutes * 60, self.auto_timer)
t.start()
GUI
部分的代碼由于篇幅限制,就不貼出來了私蕾。
如何體驗(yàn)
關(guān)注公眾號(hào)月小水長(zhǎng),后臺(tái)回復(fù) 微信群機(jī)器人 即可獲得,不用寫一行代碼僵缺,做任何配置,雙擊運(yùn)行即可體驗(yàn)群聊自動(dòng)回復(fù)踩叭。