浏览代码

交接临时提交

boweniac 4 月之前
父节点
当前提交
df2fda7bd4
共有 13 个文件被更改,包括 750 次插入88 次删除
  1. 11 0
      config-template.json
  2. 10 10
      config.json
  3. 1 1
      config.py
  4. 26 0
      db/sql_lite.py
  5. 二进制
      local.db
  6. 117 0
      logic/logic_batch_task_create.py
  7. 49 0
      logic/logic_batch_task_detail.py
  8. 140 45
      main.py
  9. 58 0
      service/batch_task.py
  10. 90 15
      service/robot.py
  11. 76 17
      service/ui.py
  12. 103 0
      service/ui_batch_task_create.py
  13. 69 0
      service/ui_batch_task_detail.py

+ 11 - 0
config-template.json

@@ -0,0 +1,11 @@
+{
+    "debug": true,
+    "api_base": "https://fastgpt.gkscrm.com/api/v1",
+    "api_key": "fastgpt-j5EzfAPJ9YXjEb30BbeMnzgaTWCTK5m9OcMbjtX6E6HGx4NiR2l8KnTS5",
+    "token": "bowen-test",
+    "contacts_white_list": [
+        "ALL",
+        "ALL_GROUPS",
+        "ALL_CONTACTS"
+    ]
+}

+ 10 - 10
config.json

@@ -1,11 +1,11 @@
-{
-    "debug": true,
-    "api_base": "https://fastgpt.gkscrm.com/api/v1",
-    "api_key": "fastgpt-j5EzfAPJ9YXjEb30BbeMnzgaTWCTK5m9OcMbjtX6E6HGx4NiR2l8KnTS5",
-    "token": "请填入获得的token",
-    "contacts_white_list": [
-        "ALL",
-        "ALL_GROUPS",
-        "ALL_CONTACTS"
-    ]
+{
+    "debug": true,
+    "api_base": "https://fastgpt.gkscrm.com/api/v1",
+    "api_key": "fastgpt-j5EzfAPJ9YXjEb30BbeMnzgaTWCTK5m9OcMbjtX6E6HGx4NiR2l8KnTS5",
+    "token": "bowen-test2",
+    "contacts_white_list": [
+        "ALL",
+        "ALL_GROUPS",
+        "ALL_CONTACTS"
+    ]
 }

+ 1 - 1
config.py

@@ -13,7 +13,7 @@ available_setting = {
     "debug":False,
     "api_base": "http://fastgpt.ascrm.cn/api/v1",
     "api_key": "fastgpt-sKABkv3PTHxlFZYPn9Mo35HHsZSdzdFNBH4XeWIRn5CwdkG7aXqEDmXwDwK",
-    "token":"",
+    "token":"bowen-test",
     "open_ai_model": "gpt-4o",
     "open_ai_temperature": 0.7,
     "open_ai_max_tokens": 1024,

+ 26 - 0
db/sql_lite.py

@@ -0,0 +1,26 @@
+import sqlite3
+from typing import Optional
+
+connection: Optional[sqlite3.Connection] = None
+
+
+def init_global_db_connection(db_path='./local.db'):
+    global connection
+    connection = sqlite3.connect(db_path)
+    print("SQLite connection is established.")
+
+
+def get_global_db_connection():
+    return connection
+
+
+def close_global_db_connection():
+    global connection
+    if connection is not None:
+        connection.close()
+        connection = None
+        print("SQLite connection is closed.")
+
+
+def init_new_db_connection(db_path='./local.db'):
+    return sqlite3.connect(db_path)

二进制
local.db


+ 117 - 0
logic/logic_batch_task_create.py

@@ -0,0 +1,117 @@
+from datetime import datetime
+from tkinter import messagebox
+
+from db.sql_lite import get_global_db_connection, init_new_db_connection
+from service.robot import get_robot
+from service.ui_batch_task_create import WinGUIBatchTaskCreate
+
+
+class WinBatchTaskCreate(WinGUIBatchTaskCreate):
+    def __init__(self):
+        super().__init__()
+        self.__event_bind()
+        self.label_list = ["全部"]
+        self.label_map = {
+            "全部": 0
+        }
+        self.contacts = []
+
+    def __event_bind(self):
+        self.tk_button_send.bind('<Button-1>', self.send_event)
+        self.tk_label_frame_contact.tk_select_box_type.bind('<<ComboboxSelected>>', self.on_select_event)
+        self.tk_label_frame_contact.tk_select_box_label.bind('<<ComboboxSelected>>', self.on_select_event)
+        self.tk_label_frame_contact.tk_table_contact_list.bind('<Control-a>', self.select_all_event)
+
+    def on_select_event(self, event):
+        contact_type = self.tk_label_frame_contact.tk_select_box_type.get()
+        label = self.tk_label_frame_contact.tk_select_box_label.get()
+        if contact_type == "联系人":
+            self.tk_label_frame_contact.tk_select_box_label.config(state='normal')
+        else:
+            self.tk_label_frame_contact.tk_select_box_label.config(state='disabled')
+        self.search(contact_type, label)
+
+    def search(self, contact_type, label):
+        # 清空列表
+        self.tk_label_frame_contact.tk_table_contact_list.delete(*self.tk_label_frame_contact.tk_table_contact_list.get_children())
+
+        # 查询
+        robot = get_robot()
+        contacts = robot.getFriendOrChatRoomList(contact_type, self.label_map.get(label, 0))
+
+        for contact in contacts:
+            self.contacts.append((contact['wxid'], contact_type, contact['name']))
+
+        for values in self.contacts:
+            self.tk_label_frame_contact.tk_table_contact_list.insert('', "end", values=values)
+
+    def send_event(self, event):
+        content = self.tk_label_frame_content.tk_text_content.get(1.0, "end").strip()
+        if content == "":
+            messagebox.showerror('错误', '发送内容不能为空')
+            return
+        selected_items = self.tk_label_frame_contact.tk_table_contact_list.selection()
+        if len(selected_items) == 0:
+            if len(self.contacts) == 0:
+                messagebox.showerror('错误', '请选择联系人或群组')
+                return
+            selected_items = self.contacts
+
+        connection = init_new_db_connection()
+        cursor = connection.cursor()
+
+        now = datetime.now()
+        created_at = int(now.timestamp())
+        robot = get_robot()
+        wx_wxid = robot.wcf.get_self_wxid()
+        insert_query = "INSERT INTO batch_task (created_at, wx_wxid, content, total) VALUES (?, ?, ?, ?)"
+        data_to_insert = (created_at, wx_wxid, content, len(selected_items))
+        cursor.execute(insert_query, data_to_insert)
+        batch_task_id = cursor.lastrowid
+
+        msg_to_insert = []
+        for item in selected_items:
+            item_values = self.tk_label_frame_contact.tk_table_contact_list.item(item, "values")
+            print(item_values)
+            if item_values[1] == "联系人":
+                msg_type = 1
+            else:
+                msg_type = 2
+            msg_to_insert.append((batch_task_id, created_at, msg_type, wx_wxid, item_values[0], item_values[2]))
+
+        # 使用executemany来插入多行数据
+        cursor.executemany('INSERT INTO batch_msg (batch_task_id, created_at, type, wx_wxid, wxid, nickname) VALUES (?,?,?,?,?,?)', msg_to_insert)
+
+        # 提交事务
+        connection.commit()
+        connection.close()
+
+        # 关闭当前窗口
+        self.destroy()
+
+    def select_all_event(self, event):
+        for item in self.tk_label_frame_contact.tk_table_contact_list.get_children():
+            self.tk_label_frame_contact.tk_table_contact_list.selection_add(item)
+
+def open_batch_task_create_win():
+    # 创建窗口对象
+    win_batch_task_create = WinBatchTaskCreate()
+
+    # 获取全部联系人和标签列表
+    robot = get_robot()
+    label_list = robot.getContactLabelList()
+
+    for label in label_list:
+        win_batch_task_create.label_map[label['LabelName']] = label['LabelId']
+        win_batch_task_create.label_list.append(label['LabelName'])
+    win_batch_task_create.tk_label_frame_contact.tk_select_box_label['values'] = win_batch_task_create.label_list
+    win_batch_task_create.tk_label_frame_contact.tk_select_box_label.set(win_batch_task_create.label_list[0])
+
+    win_batch_task_create.search(win_batch_task_create.tk_label_frame_contact.tk_select_box_type.get(), win_batch_task_create.tk_label_frame_contact.tk_select_box_label.get())
+
+    # 设置默认
+    # win_batch_task_create.tk_label_frame_contact.tk_select_box_type.set('联系人')
+    # for values in users_to_insert:
+    #     win_batch_task_create.tk_label_frame_contact.tk_table_contact_list.insert('', "end", values=values)
+
+    win_batch_task_create.mainloop()

+ 49 - 0
logic/logic_batch_task_detail.py

@@ -0,0 +1,49 @@
+from datetime import datetime
+from tkinter import messagebox
+
+from db.sql_lite import get_global_db_connection, init_new_db_connection
+from service.robot import get_robot
+from service.ui_batch_task_create import WinGUIBatchTaskCreate
+from service.ui_batch_task_detail import WinGUIBatchTaskDetail
+
+
+class WinBatchTaskDetail(WinGUIBatchTaskDetail):
+    def __init__(self):
+        super().__init__()
+        self.label_list = ["全部"]
+        self.label_map = {
+            "全部": 0
+        }
+        self.contacts = []
+
+
+def open_batch_task_detail_win(task_id: int, task_info: tuple):
+    # 创建窗口对象
+    win_batch_task_detail = WinBatchTaskDetail()
+
+    win_batch_task_detail.tk_label_frame_content.tk_text_content.insert(1.0, task_info[4])
+    win_batch_task_detail.tk_label_frame_content.tk_text_content.config(state='disabled')
+
+    connection = get_global_db_connection()
+    cursor = connection.cursor()
+
+    query = "SELECT * FROM batch_msg WHERE batch_task_id = ?"
+    # 执行查询
+    cursor.execute(query, (task_id,))
+
+    # 获取查询结果
+    results = cursor.fetchall()
+
+    for values in results:
+        if values[4] == 1:
+            status = "待发送"
+        else:
+            status = "未知"
+
+        if values[3] is None:
+            updated_at = ""
+        else:
+            updated_at = datetime.fromtimestamp(values[3]).strftime("%Y-%m-%d %H:%M:%S")
+        win_batch_task_detail.tk_label_frame_contact.tk_table_contact_list.insert('', "end", values=(values[7], values[5], values[8], updated_at, status))
+
+    win_batch_task_detail.mainloop()

+ 140 - 45
main.py

@@ -1,20 +1,22 @@
 import hashlib
 import json
-import signal
+import threading
 import time
 import tkinter
 import uuid
-from time import sleep
+from datetime import datetime
 from tkinter import messagebox
 
 import requests
 
+from db.sql_lite import init_global_db_connection, close_global_db_connection, get_global_db_connection
+from logic.logic_batch_task_create import open_batch_task_create_win
+from logic.logic_batch_task_detail import open_batch_task_detail_win
+from service.batch_task import BatchTaskService
 from service.ui import WinGUI
-from wcferry import Wcf
 
 from config import load_config, conf
-from service.robot import Robot
-
+from service.robot import init_robot, get_robot
 
 
 class Win(WinGUI):
@@ -22,13 +24,18 @@ class Win(WinGUI):
         super().__init__()
         self.__event_bind()
         self.__style_config()
-        self.robot = None
+        self.selection_batch_task_id = None
+        self._batch_task_service = BatchTaskService()
 
     def __event_bind(self):
-        self.tk_button_save.bind('<Button-1>', self.save_event)
-        self.tk_button_start.bind('<Button-1>', self.start_event)
-        self.tk_button_pause.bind('<Button-1>', self.stop_event)
-        self.tk_button_version.bind('<Button-1>', self.version_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_save.bind('<Button-1>', self.save_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_start.bind('<Button-1>', self.start_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_pause.bind('<Button-1>', self.stop_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_version.bind('<Button-1>', self.version_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_button_create.bind('<Button-1>', self.batch_task_create_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.bind('<Double-1>', self.batch_task_detail_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.bind('<Button-1>',self.just_click_event)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.bind('<Button-3>',self.batch_task_action_event)
         pass
 
     def __style_config(self):
@@ -38,14 +45,15 @@ class Win(WinGUI):
         messagebox.showinfo('版本信息', '当前版本:v1.0.0')
 
     def stop_event(self, event):
-
-        if self.tk_button_pause.cget('state') == tkinter.DISABLED:
+        self._batch_task_service.stop_task()
+        if self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_pause.cget('state') == tkinter.DISABLED:
             return
-
-        self.robot.wcf.cleanup()
-        self.robot = None
-        self.tk_button_start.config(state=tkinter.NORMAL)
-        self.tk_button_pause.config(state=tkinter.DISABLED)
+        robot = get_robot()
+        if robot is not None:
+            robot.wcf.cleanup()
+            robot = None
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_start.config(state=tkinter.NORMAL)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_pause.config(state=tkinter.DISABLED)
         messagebox.showinfo('提示', '助手已停止运行!')
 
     @staticmethod
@@ -61,14 +69,12 @@ class Win(WinGUI):
             "mac": str(mac)
         })
 
-
         headers = {
             'Content-Type': 'application/json'
         }
 
         response = requests.request("POST", url, headers=headers, data=payload)
 
-
         if response.status_code == 200:
             resp = json.loads(response.text)
 
@@ -92,16 +98,14 @@ class Win(WinGUI):
                 md5_hash.update(str(mac).encode('utf-8'))
                 md5_mac = md5_hash.hexdigest()
 
-
                 md5_hash = hashlib.md5()
                 md5_hash.update(str(timestamp).encode('utf-8'))
                 md5_timestamp = md5_hash.hexdigest()
 
                 md5_hash = hashlib.md5()
-                md5_hash.update((md5_token+md5_mac+md5_timestamp).encode('utf-8'))
+                md5_hash.update((md5_token + md5_mac + md5_timestamp).encode('utf-8'))
                 md5_sign = md5_hash.hexdigest()
 
-
                 if md5_sign != sign:
                     messagebox.showerror('错误', 'token验证失败!')
                     return False
@@ -109,7 +113,8 @@ class Win(WinGUI):
                 return True
 
             else:
-                messagebox.showerror('错误', 'token已失效 或 与当前设备未绑定,每个token只能绑定一台设备!请填入和当前设备绑定的token,或者获取新的token!')
+                messagebox.showerror('错误',
+                                     'token已失效 或 与当前设备未绑定,每个token只能绑定一台设备!请填入和当前设备绑定的token,或者获取新的token!')
                 return False
         else:
             messagebox.showerror('错误', '您的网络状态异常!请稍候重试')
@@ -117,36 +122,68 @@ class Win(WinGUI):
 
     def start_event(self, event):
 
-        if self.tk_button_start.cget('state') == tkinter.DISABLED:
-            return
-
-        if not self.check_token():
+        if self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_start.cget('state') == tkinter.DISABLED:
             return
 
-        wcf = Wcf(debug=conf().get("debug", False))
-        def handler(sig, frame):
-            wcf.cleanup()  # 退出前清理环境
-            exit(0)
-        signal.signal(signal.SIGINT, handler)
-
-        self.robot = Robot(conf(), wcf)
+        # if not self.check_token():
+        #     return
 
         # 接收消息
         # robot.enableRecvMsg()     # 可能会丢消息?
-        self.robot.enableReceivingMsg()  # 加队列
+        robot = get_robot()
+        robot.enableReceivingMsg()  # 加队列
 
-        if self.robot.wcf.is_login():
-            self.tk_button_start.config(state='disabled')
-            self.tk_button_pause.config(state='normal')
+        if robot.wcf.is_login():
+            self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_start.config(state='disabled')
+            self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_pause.config(state='normal')
             messagebox.showinfo('提示', '助手开始运行!')
+            self._batch_task_service.start_task()
+
+            wx_wxid = robot.wcf.get_self_wxid()
+            connection = get_global_db_connection()
+            cursor = connection.cursor()
+
+            def refresh_list():
+                query = "SELECT * FROM batch_task WHERE wx_wxid = ? ORDER BY created_at DESC"
+                # 执行查询
+                cursor.execute(query, (wx_wxid,))
+
+                # 获取查询结果
+                results = cursor.fetchall()
+                batch_tasks = []
+                for result in results:
+                    if result[2] == 1:
+                        status = "等待中"
+                    elif result[2] == 2:
+                        status = "已开始"
+                    elif result[2] == 3:
+                        status = "已停止"
+                    else:
+                        status = "未知"
+                    created_at = datetime.fromtimestamp(result[1]).strftime("%Y-%m-%d %H:%M:%S")
+                    batch_tasks.append((result[0], created_at, status, f"{result[6] + result[7]}/{result[5]}", result[7], result[4]))
+                    # 清空列表
+                self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.delete(*self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.get_children())
+
+                for values in batch_tasks:
+                    self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.insert('', "end", iid=values[0], values=values[1:])
+                    if values[0] == self.selection_batch_task_id:
+                        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.selection_set(values[0])
+
+
+                # 每 3秒调用一次这个函数
+                print("refresh_list")
+                self.tk_tabs_main_tabs.after(3000, refresh_list)
+
+            self.tk_tabs_main_tabs.after(0, refresh_list)
 
         # robot.keepRunningAndBlockProcess()
 
     def save_event(self, event):
         conf().update({
-            "api_base":self.tk_input_api_base.get(),
-            "api_key":self.tk_input_api_key.get(),
-            "token":self.tk_input_token.get()
+            "api_base": self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_input_api_base.get(),
+            "api_key": self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_input_api_key.get(),
+            "token": self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_input_token.get()
         })
         # 将字典写入 JSON 文件
         with open('config.json', 'w') as json_file:
@@ -154,13 +191,72 @@ class Win(WinGUI):
 
         messagebox.showinfo('提示', '保存文件成功!')
 
+    def batch_task_create_event(self, event):
+        # win_batch_task_create = WinGUIBatchTaskCreate()
+        # win_batch_task_create.mainloop()
+        open_batch_task_create_win()
+
+    def batch_task_detail_event(self, event):
+        # 获取双击项的标识符
+        item_id = self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.identify_row(event.y)
+        # 获取该项的值
+        item_values = self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.item(item_id, "values")
+        # print("Double-clicked item:", item_values)
+        if len(item_values) > 0:
+            self.selection_batch_task_id = int(item_id)
+            open_batch_task_detail_win(int(item_id), item_values)
+        else:
+            self.selection_batch_task_id = None
+
+    def batch_task_action_event(self, event):
+        # 获取右键项的标识符
+        item_id = self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.identify_row(event.y)
+        print(f"Right-clicked item: {item_id}")
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.selection_set(item_id)
+        # 获取该项的值
+        item_values = self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.item(item_id, "values")
+        if len(item_values) > 0:
+            self.selection_batch_task_id = int(item_id)
+
+            # print("Right-clicked item:", item_values)
+
+            # 创建菜单
+            menu = tkinter.Menu(self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list, tearoff=0)
+            menu.add_command(label="查看详情", command=lambda: self.batch_task_detail_event(event))
+            if item_values[1] == "等待中" or item_values[1] == "已开始":
+                menu.add_command(label="停止", command=lambda: self.batch_task_detail_event(event))
+            elif item_values[1] == "已停止":
+                menu.add_command(label="开始", command=lambda: self.batch_task_detail_event(event))
+            menu.post(event.x_root, event.y_root)
+        else:
+            self.selection_batch_task_id = None
+
+    def just_click_event(self, event):
+        item_id = self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.identify_row(event.y)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.selection_set(item_id)
+        item_values = self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.item(item_id, "values")
+        if len(item_values) > 0:
+            self.selection_batch_task_id = int(item_id)
+        else:
+            self.selection_batch_task_id = None
+
+
+
+
 def main():
     load_config()
 
-    win = Win()
-    win.mainloop()
+    # 初始化sql_lite
+    init_global_db_connection()
 
+    # 初始化 wcf
+    init_robot()
 
+    try:
+        win = Win()
+        win.mainloop()
+    finally:
+        close_global_db_connection()
 
     # wcf = Wcf(debug=conf().get("debug", False))
     #
@@ -180,7 +276,6 @@ def main():
     # robot.keepRunningAndBlockProcess()
 
 
-
 # Press the green button in the gutter to run the script.
 if __name__ == '__main__':
-    main()
+    main()

+ 58 - 0
service/batch_task.py

@@ -0,0 +1,58 @@
+import threading
+import time
+
+from db.sql_lite import init_new_db_connection
+from service.robot import get_robot
+
+
+class BatchTaskService:
+    def __init__(self):
+        self._event = threading.Event()
+        self._connection = init_new_db_connection()
+        self._robot = get_robot()
+        self._wx_wxid = self._robot.wcf.get_self_wxid()
+        self._execute_task_info = None
+
+    def execute_task(self):
+        # while self._event.is_set():
+        #     cursor = self._connection.cursor()
+        #     task_select_query = "SELECT * FROM batch_task WHERE wx_wxid = ? AND (status = 1 OR status = 2)  ORDER BY created_at ASC LIMIT 1;"
+        #     cursor.execute(task_select_query, (self._wx_wxid,))
+        #     result = cursor.fetchone()
+        #     if result is not None:
+        #         if result[2] == 1:
+        #             update_query = "UPDATE batch_task SET status = ? WHERE id = ?"
+        #             data_to_update = (result[0], )
+        #             cursor.execute(update_query, data_to_update)
+        #         task_select_query = "SELECT * FROM batch_msg WHERE batch_task_id = ? AND (status = 1 OR status = 3)  ORDER BY created_at ASC LIMIT 1;"
+        #         cursor.execute(task_select_query, (self._wx_wxid,))
+        #         result = cursor.fetchone()
+        #         self._connection.commit()
+            time.sleep(3)
+
+    def send_batch_msg(self, batch_task_id, msg):
+        pass
+        # cursor = self._connection.cursor()
+        # query = "SELECT * FROM batch_msg WHERE wx_wxid = ? AND  ORDER BY created_at ASC LIMIT 1;"
+        # # 执行查询
+        # cursor.execute(query, (self._wx_wxid,))
+        #
+        # # 获取查询结果
+        # results = cursor.fetchall()
+
+    # def get_batch_task(self):
+    #     print("get_batch_task")
+    #     time.sleep(5)
+    #     print("get_batch_task")
+    #     time.sleep(5)
+
+
+    def start_task(self):
+        print("Start task.")
+        # 设置事件为 True
+        self._event.set()
+        threading.Thread(target=self.execute_task).start()
+
+    def stop_task(self):
+        # 清除事件
+        self._event.clear()

+ 90 - 15
service/robot.py

@@ -1,14 +1,17 @@
 import json
 import logging
+import signal
 from queue import Empty
 from threading import Thread
-
+from typing import Optional
+import requests
 import openai
+from google.protobuf import json_format
 from openai import OpenAI, api_key, base_url, AuthenticationError, APIConnectionError, APIError
-from wcferry import Wcf, WxMsg
+from wcferry import Wcf, WxMsg, wcf_pb2
 
 from common.log import logger
-from config import Config
+from config import Config, conf
 
 
 class Robot():
@@ -21,7 +24,6 @@ class Robot():
         self.allContacts = self.getAllContacts()
         self.aiClient = OpenAI(api_key=self.config.get("api_key"), base_url=self.config.get("api_base"))
 
-
         self.LOG.info(f"{self.user} 登录成功")
 
     def enableRecvMsg(self) -> None:
@@ -36,6 +38,7 @@ class Robot():
         打开消息通知,使用消息队列的方式获取
         :return:
         """
+
         def innerProcessMsg(wcf: Wcf):
             while wcf.is_receiving_msg():
                 try:
@@ -61,10 +64,8 @@ class Robot():
         if "gkscrm.com" not in self.config.get("api_base"):
             return 0
 
-
-
         try:
-            self.LOG.info(f"Received message: {msg}") # 打印信息
+            self.LOG.info(f"Received message: {msg}")  # 打印信息
             self.processMsg(msg)
         except Exception as e:
             self.LOG.error(e)
@@ -84,13 +85,19 @@ class Robot():
             return
         elif msg.is_text():
             if msg.from_group():
-                if "ALL" not in self.config.get("contacts_white_list") and "ALL_GROUPS" not in self.config.get("contacts_white_list") and msg.roomid not in self.config.get("contacts_white_list") and self.allContacts.get(msg.roomid) not in self.config.get("contacts_white_list"):
+                if "ALL" not in self.config.get("contacts_white_list") and "ALL_GROUPS" not in self.config.get(
+                        "contacts_white_list") and msg.roomid not in self.config.get(
+                    "contacts_white_list") and self.allContacts.get(msg.roomid) not in self.config.get(
+                    "contacts_white_list"):
                     return
-                if msg.is_at(self.wxid) is False and "@"+self.user.get("name") not in msg.content:
+                if msg.is_at(self.wxid) is False and "@" + self.user.get("name") not in msg.content:
                     return
                 rsp = self.get_answer(msg)  # 闲聊
             else:
-                if "ALL" not in self.config.get("contacts_white_list") and "ALL_CONTACTS" not in self.config.get("contacts_white_list") and msg.sender not in self.config.get("contacts_white_list") and self.allContacts.get(msg.sender) not in self.config.get("contacts_white_list"):
+                if "ALL" not in self.config.get("contacts_white_list") and "ALL_CONTACTS" not in self.config.get(
+                        "contacts_white_list") and msg.sender not in self.config.get(
+                    "contacts_white_list") and self.allContacts.get(msg.sender) not in self.config.get(
+                    "contacts_white_list"):
                     return
                 rsp = self.get_answer(msg)  # 闲聊
 
@@ -131,16 +138,16 @@ class Robot():
             # 在fastgpt的时候增加chatId字段
             if "fastgpt" in self.config.get("api_base"):
                 extra_body = {
-                    "chatId": "chatId-"+msg.sender
+                    "chatId": "chatId-" + msg.sender
                 }
             else:
                 extra_body = {}
 
             ret = self.aiClient.chat.completions.create(
                 model=self.config.get("open_ai_model", "gpt-3.5-turbo"),
-                max_tokens=self.config.get("open_ai_max_tokens",8192),
-                temperature=self.config.get("open_ai_temperature",0.7),
-                top_p=self.config.get("open_ai_top_p",1),
+                max_tokens=self.config.get("open_ai_max_tokens", 8192),
+                temperature=self.config.get("open_ai_temperature", 0.7),
+                top_p=self.config.get("open_ai_top_p", 1),
                 extra_body=extra_body,
                 messages=[
                     {"role": "user", "content": msg.content}
@@ -197,4 +204,72 @@ class Robot():
         """
         保持机器人运行,不让进程退出
         """
-        self.wcf.keep_running()
+        self.wcf.keep_running()
+
+    def getFriendOrChatRoomList(self, type: str, label_id: int) -> list:
+        """
+        获取好友列表或者群列表
+        :param type: 1: 联系人  2: 群组
+        :param label_id:
+        :return: 好友列表或者群列表
+        """
+        contacts_list = []
+        contacts = self.wcf.query_sql("MicroMsg.db", "select UserName, NickName, LabelIDList, Type from Contact")
+
+        not_contacts = {
+            "fmessage": "朋友推荐消息",
+            "medianote": "语音记事本",
+            "floatbottle": "漂流瓶",
+            "filehelper": "文件传输助手",
+            "newsapp": "新闻",
+            "mphelper": "公众平台安全助手",
+        }
+        for cnt in contacts:
+            if ((type == "联系人" and (cnt["Type"] != 3 or (label_id != 0 and not is_int_in_string(label_id, cnt["LabelIDList"])))) or  # 群组
+                    (type == "群组" and (cnt["Type"] != 2 or not cnt["UserName"].endswith("@chatroom"))) or  # 联系人
+                    cnt["UserName"].startswith("gh_") or  # 公众号
+                    cnt["UserName"] in not_contacts.keys()  # 其他杂号
+            ):
+                continue
+            contact = {
+                "wxid": cnt.get("UserName", ""),
+                "name": cnt.get("NickName", "")}
+            contacts_list.append(contact)
+
+        return contacts_list
+
+    def getContactLabelList(self) -> list:
+        """
+        查询数据库
+        :param sql: 查询语句
+        :param params: 参数
+        :return: 查询结果
+        """
+        return self.wcf.query_sql("MicroMsg.db", "select * from ContactLabel")
+
+
+robot: Optional[Robot] = None
+
+
+def init_robot():
+    global robot
+    wcf = Wcf(debug=conf().get("debug", False))
+
+    def handler(sig, frame):
+        wcf.cleanup()  # 退出前清理环境
+        exit(0)
+
+    signal.signal(signal.SIGINT, handler)
+    robot = Robot(conf(), wcf)
+
+
+def get_robot():
+    return robot
+
+def is_int_in_string(int_value, str_values):
+    # 拆分字符串并去除多余空格
+    split_values = map(str.strip, str_values.split(','))
+    # 过滤掉空字符串,转换为整数列表
+    values_list = list(map(int, filter(lambda x: x != '', split_values)))
+    # 检查整数是否在列表中
+    return int_value in values_list

+ 76 - 17
service/ui.py

@@ -1,4 +1,3 @@
-import random
 import tkinter
 from tkinter import *
 from tkinter.ttk import *
@@ -10,27 +9,17 @@ class WinGUI(Tk):
     def __init__(self):
         super().__init__()
         self.__win()
-        self.tk_label_lab_api_base = self.__tk_label_lab_api_base(self)
-        self.tk_label_lab_api_key = self.__tk_label_lab_api_key(self)
-        self.tk_input_api_base = self.__tk_input_api_base(self)
-        self.tk_input_api_key = self.__tk_input_api_key(self)
-        self.tk_input_token = self.__tk_input_token(self)
-        self.tk_label_lab_token = self.__tk_label_lab_token(self)
-        self.tk_button_save = self.__tk_button_save(self)
-        self.tk_button_start = self.__tk_button_start(self)
-        self.tk_button_pause = self.__tk_button_pause(self)
-        self.tk_button_version = self.__tk_button_version(self)
+        self.tk_tabs_main_tabs = Frame_main_tabs(self)
 
     def __win(self):
         self.title("轻马私域微信机器人助手")
         # 设置窗口大小、居中
-        width = 670
-        height = 250
+        width = 675
+        height = 510
         screenwidth = self.winfo_screenwidth()
         screenheight = self.winfo_screenheight()
         geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
         self.geometry(geometry)
-
         self.resizable(width=False, height=False)
 
     def scrollbar_autohide(self, vbar, hbar, widget):
@@ -72,6 +61,40 @@ class WinGUI(Tk):
             self.h_scrollbar(hbar, widget, x, y, w, h, pw, ph)
         self.scrollbar_autohide(vbar, hbar, widget)
 
+
+class Frame_main_tabs(Notebook):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+
+    def __frame(self):
+        self.tk_tabs_main_tabs_0 = Frame_main_tabs_0(self)
+        self.add(self.tk_tabs_main_tabs_0, text="启动服务")
+
+        self.tk_tabs_main_tabs_1 = Frame_main_tabs_1(self)
+        self.add(self.tk_tabs_main_tabs_1, text="群发管理")
+
+        self.place(x=0, y=0, width=675, height=510)
+
+
+class Frame_main_tabs_0(Frame):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+        self.tk_label_lab_api_base = self.__tk_label_lab_api_base(self)
+        self.tk_input_api_base = self.__tk_input_api_base(self)
+        self.tk_label_lab_api_key = self.__tk_label_lab_api_key(self)
+        self.tk_input_api_key = self.__tk_input_api_key(self)
+        self.tk_label_lab_token = self.__tk_label_lab_token(self)
+        self.tk_input_token = self.__tk_input_token(self)
+        self.tk_button_save = self.__tk_button_save(self)
+        self.tk_button_start = self.__tk_button_start(self)
+        self.tk_button_pause = self.__tk_button_pause(self)
+        self.tk_button_version = self.__tk_button_version(self)
+
+    def __frame(self):
+        self.place(x=0, y=0, width=675, height=510)
+
     def __tk_label_lab_api_base(self, parent):
         label = Label(parent, text="API_BASE", anchor="center", )
         label.place(x=30, y=30, width=60, height=30)
@@ -116,13 +139,49 @@ class WinGUI(Tk):
         return btn
 
     def __tk_button_pause(self, parent):
-        btn = Button(parent, text="暂停", takefocus=False, state=tkinter.DISABLED )
+        btn = Button(parent, text="暂停", takefocus=False, state=tkinter.DISABLED)
         btn.place(x=230, y=190, width=50, height=30)
         return btn
 
     def __tk_button_version(self, parent):
-        btn = Button(parent, text="版本", takefocus=False )
-        btn.place(x=530,y=190, width=50, height=30)
+        btn = Button(parent, text="版本", takefocus=False)
+        btn.place(x=530, y=190, width=50, height=30)
         return btn
 
 
+class Frame_main_tabs_1(Frame):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+        self.tk_table_batch_task_list = self.__tk_table_batch_task_list(self)
+        self.tk_button_create = self.__tk_button_create(self)
+
+    def __frame(self):
+        self.place(x=0, y=0, width=675, height=510)
+
+    def __tk_table_batch_task_list(self, parent):
+        # 表头字段 表头宽度
+        columns = {"创建时间": 121, "状态": 121, "进度": 121, "失败": 121, "内容": 121}
+        # 初始化表格 表格是基于Treeview,tkinter本身没有表格。show="headings" 为隐藏首列。
+        tk_table = Treeview(parent, show="headings", columns=list(columns))
+        for text, width in columns.items():  # 批量设置列属性
+            tk_table.heading(text, text=text, anchor='center')
+            tk_table.column(text, anchor='center', width=width, stretch=False)  # stretch 不自动拉伸
+
+        # 插入数据示例
+        # data = [
+        #     [1, "github", "https://github.com/iamxcd/tkinter-helper"],
+        #     [2, "演示地址", "https://www.pytk.net/tkinter-helper"]
+        # ]
+        #
+        # # 导入初始数据
+        # for values in data:
+        #     tk_table.insert('', END, values=values)
+
+        tk_table.place(x=30, y=50, width=608, height=420)
+        return tk_table
+
+    def __tk_button_create(self, parent):
+        btn = Button(parent, text="新建任务")
+        btn.place(x=560, y=10, width=80, height=30)
+        return btn

+ 103 - 0
service/ui_batch_task_create.py

@@ -0,0 +1,103 @@
+from tkinter import *
+from tkinter.ttk import *
+
+
+class WinGUIBatchTaskCreate(Tk):
+    def __init__(self):
+        super().__init__()
+        self.__win()
+        self.tk_label_frame_content = Frame_content(self)
+        self.tk_label_frame_contact = Frame_contact(self)
+        self.tk_button_send = self.__tk_button_send()
+
+    def __win(self):
+        self.title("新建群发任务")
+        # 设置窗口大小、居中
+        width = 600
+        height = 600
+        screenwidth = self.winfo_screenwidth()
+        screenheight = self.winfo_screenheight()
+        geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
+        self.geometry(geometry)
+        self.resizable(width=False, height=False)
+
+    def __tk_button_send(self):
+        btn = Button(self, text="发送")
+        btn.place(x=500, y=560, width=80, height=30)
+        return btn
+
+
+class Frame_content(LabelFrame):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+        self.tk_text_content = self.__tk_text_content()
+
+    def __frame(self):
+        self.configure(text="发送内容")
+        self.place(x=20, y=10, width=560, height=130)
+
+    def __tk_text_content(self):
+        text = Text(self)
+        text.place(x=5, y=5, width=550, height=100)
+        return text
+
+
+class Frame_contact(LabelFrame):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+        self.tk_label_lab_type = self.__tk_label_lab_type()
+        self.tk_select_box_type = self.__tk_select_box_type()
+        self.tk_label_lab_label = self.__tk_label_lab_label()
+        self.tk_select_box_label = self.__tk_select_box_label()
+        self.tk_table_contact_list = self.__tk_table_contact_list()
+
+    def __frame(self):
+        self.configure(text="选择联系人或群组(全选:control + A | 多选:control + shift⬆️)")
+        self.place(x=20, y=150, width=560, height=400)
+
+    def __tk_label_lab_type(self):
+        label = Label(self, text="类型", anchor="center")
+        label.place(x=5, y=5, width=50, height=25)
+        return label
+
+    def __tk_select_box_type(self):
+        cb = Combobox(self, state="readonly")
+        cb['values'] = ("联系人", "群组")
+        cb.place(x=70, y=5, width=150, height=25)
+        cb.set("联系人")
+        return cb
+
+    def __tk_label_lab_label(self):
+        label = Label(self, text="标签", anchor="center")
+        label.place(x=240, y=5, width=50, height=25)
+        return label
+
+    def __tk_select_box_label(self):
+        cb = Combobox(self, state="readonly")
+        cb['values'] = []
+        cb.place(x=300, y=5, width=150, height=25)
+        return cb
+
+    def __tk_table_contact_list(self):
+        # 表头字段 表头宽度
+        columns = {"微信 ID": 110, "类型": 165, "昵称": 275}
+        # 初始化表格 表格是基于Treeview,tkinter本身没有表格。show="headings" 为隐藏首列。
+        tk_table = Treeview(self, show="headings", columns=list(columns))
+        for text, width in columns.items():  # 批量设置列属性
+            tk_table.heading(text, text=text, anchor='center')
+            tk_table.column(text, anchor='center', width=width, stretch=False)  # stretch 不自动拉伸
+
+        # 插入数据示例
+        # data = [
+        #     [1, "github", "https://github.com/iamxcd/tkinter-helper"],
+        #     [2, "演示地址", "https://www.pytk.net/tkinter-helper"]
+        # ]
+        #
+        # # 导入初始数据
+        # for values in data:
+        #     tk_table.insert('', END, values=values)
+
+        tk_table.place(x=5, y=40, width=550, height=335)
+        return tk_table

+ 69 - 0
service/ui_batch_task_detail.py

@@ -0,0 +1,69 @@
+from tkinter import *
+from tkinter.ttk import *
+
+
+class WinGUIBatchTaskDetail(Tk):
+    def __init__(self):
+        super().__init__()
+        self.__win()
+        self.tk_label_frame_content = Frame_content(self)
+        self.tk_label_frame_contact = Frame_contact(self)
+
+    def __win(self):
+        self.title("新建群发任务")
+        # 设置窗口大小、居中
+        width = 600
+        height = 600
+        screenwidth = self.winfo_screenwidth()
+        screenheight = self.winfo_screenheight()
+        geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
+        self.geometry(geometry)
+        self.resizable(width=False, height=False)
+
+class Frame_content(LabelFrame):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+        self.tk_text_content = self.__tk_text_content()
+
+    def __frame(self):
+        self.configure(text="发送内容")
+        self.place(x=20, y=10, width=560, height=130)
+
+    def __tk_text_content(self):
+        text = Text(self)
+        text.place(x=5, y=5, width=550, height=100)
+        return text
+
+
+class Frame_contact(LabelFrame):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.__frame()
+        self.tk_table_contact_list = self.__tk_table_contact_list()
+
+    def __frame(self):
+        self.configure(text="接收对象")
+        self.place(x=20, y=150, width=560, height=400)
+
+    def __tk_table_contact_list(self):
+        # 表头字段 表头宽度
+        columns = {"微信 ID": 112, "类型": 112, "昵称": 112, "发送时间": 112, "发送状态": 112}
+        # 初始化表格 表格是基于Treeview,tkinter本身没有表格。show="headings" 为隐藏首列。
+        tk_table = Treeview(self, show="headings", columns=list(columns))
+        for text, width in columns.items():  # 批量设置列属性
+            tk_table.heading(text, text=text, anchor='center')
+            tk_table.column(text, anchor='center', width=width, stretch=False)  # stretch 不自动拉伸
+
+        # 插入数据示例
+        # data = [
+        #     [1, "github", "https://github.com/iamxcd/tkinter-helper"],
+        #     [2, "演示地址", "https://www.pytk.net/tkinter-helper"]
+        # ]
+        #
+        # # 导入初始数据
+        # for values in data:
+        #     tk_table.insert('', END, values=values)
+
+        tk_table.place(x=5, y=5, width=550, height=370)
+        return tk_table