فهرست منبع

增加群发功能

boweniac 4 ماه پیش
والد
کامیت
bc1c71f43e
15فایلهای تغییر یافته به همراه564 افزوده شده و 363 حذف شده
  1. 0 0
      common/sql_lite.py
  2. 1 1
      config.json
  3. 79 0
      db/batch_msg.py
  4. 86 0
      db/batch_task.py
  5. BIN
      local.db
  6. 33 28
      logic/logic_batch_task_create.py
  7. 13 24
      logic/logic_batch_task_detail.py
  8. 283 0
      logic/logic_ui.py
  9. 6 260
      main.py
  10. 1 1
      main.spec
  11. 56 45
      service/batch_task.py
  12. 5 3
      service/robot.py
  13. 1 1
      ui/ui.py
  14. 0 0
      ui/ui_batch_task_create.py
  15. 0 0
      ui/ui_batch_task_detail.py

+ 0 - 0
db/sql_lite.py → common/sql_lite.py


+ 1 - 1
config.json

@@ -2,7 +2,7 @@
     "debug": true,
     "api_base": "https://fastgpt.gkscrm.com/api/v1",
     "api_key": "fastgpt-j5EzfAPJ9YXjEb30BbeMnzgaTWCTK5m9OcMbjtX6E6HGx4NiR2l8KnTS5",
-    "token": "bowen-test2",
+    "token": "bowen-test3",
     "contacts_white_list": [
         "ALL",
         "ALL_GROUPS",

+ 79 - 0
db/batch_msg.py

@@ -0,0 +1,79 @@
+from datetime import datetime
+from sqlite3 import Cursor
+from typing import TypedDict, Optional
+
+batch_msg_status = {
+    1: "待发送",
+    2: "成功",
+    3: "失败",
+}
+
+batch_msg_type = {
+    1: "联系人",
+    2: "群组",
+}
+
+
+class BatchMsgModel(TypedDict):
+    id: int
+    batch_task_id: int
+    created_at: int
+    updated_at: int
+    status: int  # 1 待发送 2 成功 3 失败
+    type: int  # 1 联系人 2 群组
+    wx_wxid: str
+    wxid: str
+    nickname: str
+
+
+def batch_msg_get_list(cur: Cursor, task_id: int) -> list[BatchMsgModel]:
+    query = "SELECT * FROM batch_msg WHERE batch_task_id = ?"
+    cur.execute(query, (task_id,))
+    results = cur.fetchall()
+    msgs: list[BatchMsgModel] = []
+    for result in results:
+        msg: BatchMsgModel = {
+            "id": result[0],
+            "batch_task_id": result[1],
+            "created_at": result[2],
+            "updated_at": result[3],
+            "status": result[4],
+            "type": result[5],
+            "wx_wxid": result[6],
+            "wxid": result[7],
+            "nickname": result[8]
+        }
+        msgs.append(msg)
+    return msgs
+
+
+def batch_msg_get_earliest_msg_to_execute(cur: Cursor, task_id: int) -> Optional[BatchMsgModel]:
+    query = "SELECT * FROM batch_msg WHERE batch_task_id = ? AND (status = 1 OR status = 3)  ORDER BY created_at ASC LIMIT 1"
+    cur.execute(query, (task_id,))
+    result = cur.fetchone()
+    if result is None:
+        return None
+    task: BatchMsgModel = {
+        "id": result[0],
+        "batch_task_id": result[1],
+        "created_at": result[2],
+        "updated_at": result[3],
+        "status": result[4],
+        "type": result[5],
+        "wx_wxid": result[6],
+        "wxid": result[7],
+        "nickname": result[8]
+    }
+    return task
+
+
+def batch_msg_create_many(cur: Cursor, msg_list: list[tuple]):
+    cur.executemany(
+        'INSERT INTO batch_msg (batch_task_id, created_at, type, wx_wxid, wxid, nickname) VALUES (?,?,?,?,?,?)',
+        msg_list)
+
+
+def batch_msg_update_status(cur: Cursor, msg_id: int, status: int):
+    update_query = "UPDATE batch_msg SET status = ? WHERE id = ?"
+    data_to_update = (status, msg_id)
+    cur.execute(update_query, data_to_update)

+ 86 - 0
db/batch_task.py

@@ -0,0 +1,86 @@
+from datetime import datetime
+from sqlite3 import Cursor
+from typing import Optional, TypedDict
+
+batch_task_status = {
+    1: "等待中",
+    2: "已开始",
+    3: "已停止",
+    4: "已完成"
+}
+
+
+class BatchTaskModel(TypedDict):
+    id: int
+    created_at: int
+    status: int  # 1 等待中 2 已开始 3 已停止 4 已完成
+    wx_wxid: str
+    content: str
+    total: int
+    success: int
+    fail: int
+
+
+def batch_task_get_list(cur: Cursor, wx_wxid: str) -> list[BatchTaskModel]:
+    query = "SELECT * FROM batch_task WHERE wx_wxid = ? ORDER BY created_at DESC"
+    cur.execute(query, (wx_wxid,))
+    results = cur.fetchall()
+    tasks: list[BatchTaskModel] = []
+    for result in results:
+        task: BatchTaskModel = {
+            "id": result[0],
+            "created_at": result[1],
+            "status": result[2],
+            "wx_wxid": result[3],
+            "content": result[4],
+            "total": result[5],
+            "success": result[6],
+            "fail": result[7]
+        }
+        tasks.append(task)
+    return tasks
+
+
+def batch_task_get_earliest_task_to_execute(cur: Cursor, wx_wxid: str) -> Optional[BatchTaskModel]:
+    query = "SELECT * FROM batch_task WHERE wx_wxid = ? AND (status = 1 OR status = 2)  ORDER BY created_at ASC LIMIT 1"
+    cur.execute(query, (wx_wxid,))
+    result = cur.fetchone()
+    if result is None:
+        return None
+    task: BatchTaskModel = {
+        "id": result[0],
+        "created_at": result[1],
+        "status": result[2],
+        "wx_wxid": result[3],
+        "content": result[4],
+        "total": result[5],
+        "success": result[6],
+        "fail": result[7]
+    }
+    return task
+
+
+def batch_task_create(cur: Cursor, wx_wxid: str, content: str, total: int) -> int:
+    created_at = int(datetime.now().timestamp())
+    insert_query = "INSERT INTO batch_task (created_at, wx_wxid, content, total) VALUES (?, ?, ?, ?)"
+    data_to_insert = (created_at, wx_wxid, content, total)
+    cur.execute(insert_query, data_to_insert)
+    return cur.lastrowid
+
+
+def batch_task_update_status(cur: Cursor, task_id: int, status: int):
+    update_query = "UPDATE batch_task SET status = ? WHERE id = ?"
+    data_to_update = (status, task_id)
+    cur.execute(update_query, data_to_update)
+
+
+def batch_task_update_success(cur: Cursor, task_id: int, success: int):
+    update_query = "UPDATE batch_task SET success = ? WHERE id = ?"
+    data_to_update = (success, task_id)
+    cur.execute(update_query, data_to_update)
+
+
+def batch_task_update_fail(cur: Cursor, task_id: int, fail: int):
+    update_query = "UPDATE batch_task SET fail = ? WHERE id = ?"
+    data_to_update = (fail, task_id)
+    cur.execute(update_query, data_to_update)

BIN
local.db


+ 33 - 28
logic/logic_batch_task_create.py

@@ -1,9 +1,11 @@
 from datetime import datetime
 from tkinter import messagebox
 
-from db.sql_lite import get_global_db_connection, init_new_db_connection
+from common.sql_lite import init_new_db_connection
+from db.batch_msg import batch_msg_create_many
+from db.batch_task import batch_task_create
 from service.robot import get_robot
-from service.ui_batch_task_create import WinGUIBatchTaskCreate
+from ui.ui_batch_task_create import WinGUIBatchTaskCreate
 
 
 class WinBatchTaskCreate(WinGUIBatchTaskCreate):
@@ -59,32 +61,35 @@ class WinBatchTaskCreate(WinGUIBatchTaskCreate):
 
         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()
+        try:
+            now = datetime.now()
+            created_at = int(now.timestamp())
+            robot = get_robot()
+            wx_wxid = robot.wcf.get_self_wxid()
+            batch_task_id = batch_task_create(cursor, wx_wxid, content, len(selected_items))
+
+            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来插入多行数据
+            batch_msg_create_many(cursor, msg_to_insert)
+            # 提交事务
+            connection.commit()
+        except Exception as e:
+            # 回滚事务
+            connection.rollback()
+            print(f"发生错误: {e}")
+        finally:
+            # 确保资源被正确释放
+            cursor.close()
+            connection.close()
 
         # 关闭当前窗口
         self.destroy()

+ 13 - 24
logic/logic_batch_task_detail.py

@@ -1,10 +1,8 @@
 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
+from common.sql_lite import get_global_db_connection
+from db.batch_msg import batch_msg_get_list, batch_msg_status, batch_msg_type
+from ui.ui_batch_task_detail import WinGUIBatchTaskDetail
 
 
 class WinBatchTaskDetail(WinGUIBatchTaskDetail):
@@ -26,24 +24,15 @@ def open_batch_task_detail_win(task_id: int, task_info: tuple):
 
     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))
+    try:
+        results = batch_msg_get_list(cursor, task_id)
+    finally:
+        cursor.close()
+
+    for result in results:
+        updated_at = datetime.fromtimestamp(result["updated_at"]).strftime("%Y-%m-%d %H:%M:%S") if result["updated_at"] is not None else ""
+        status = batch_msg_status[result["status"]]
+        type = batch_msg_type[result["type"]]
+        win_batch_task_detail.tk_label_frame_contact.tk_table_contact_list.insert('', "end", values=(result["wxid"], type, result["nickname"], updated_at, status))
 
     win_batch_task_detail.mainloop()

+ 283 - 0
logic/logic_ui.py

@@ -0,0 +1,283 @@
+import hashlib
+import json
+import time
+import tkinter
+import uuid
+from datetime import datetime
+from tkinter import messagebox
+
+import requests
+
+from common.sql_lite import get_global_db_connection
+from config import conf
+from db.batch_task import batch_task_get_list, batch_task_status, batch_task_update_status
+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 stop_batch_task, start_batch_task
+from service.robot import get_robot
+from ui.ui import WinGUI
+
+
+class Win(WinGUI):
+    def __init__(self):
+        super().__init__()
+        self.__event_bind()
+        self.__style_config()
+        self.__is_started = False
+        self.selection_batch_task_id = None
+
+    def __event_bind(self):
+        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):
+        pass
+
+    def version_event(self, event):
+        messagebox.showinfo('版本信息', '当前版本:v1.0.0')
+
+    def stop_event(self, event):
+        stop_batch_task()
+        if self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_pause.cget('state') == tkinter.DISABLED:
+            return
+        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)
+        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_button_create.config(state=tkinter.DISABLED)
+        self.__is_started = False
+        messagebox.showinfo('提示', '助手已停止运行!')
+
+    @staticmethod
+    def check_token():
+        # 获取当前网卡mac地址
+        mac = uuid.getnode()
+        url = "https://wxadminapi.gkscrm.com/wechat-api/token/check"
+
+        token = str(conf().get("token"))
+
+        payload = json.dumps({
+            "token": token,
+            "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)
+
+            if resp['valid'] == True:
+                sign = resp['sign']
+                timestamp = resp['timestamp']
+                # 获取当前unix时间戳
+                current_timestamp = int(time.time())
+
+                if abs(current_timestamp - int(timestamp)) > 180:
+                    messagebox.showerror('错误', '验证失败!请重试')
+                    return False
+                # 创建一个MD5哈希对象
+                md5_hash = hashlib.md5()
+                # 更新哈希对象
+                md5_hash.update(token.encode('utf-8'))
+                # 获取十六进制格式的MD5哈希值
+                md5_token = md5_hash.hexdigest()
+
+                md5_hash = hashlib.md5()
+                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_sign = md5_hash.hexdigest()
+
+                if md5_sign != sign:
+                    messagebox.showerror('错误', 'token验证失败!')
+                    return False
+
+                return True
+
+            else:
+                messagebox.showerror('错误',
+                                     'token已失效 或 与当前设备未绑定,每个token只能绑定一台设备!请填入和当前设备绑定的token,或者获取新的token!')
+                return False
+        else:
+            messagebox.showerror('错误', '您的网络状态异常!请稍候重试')
+            return False
+
+    def start_event(self, event):
+
+        if self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_start.cget('state') == tkinter.DISABLED:
+            return
+
+        if not self.check_token():
+            return
+
+        # 接收消息
+        # robot.enableRecvMsg()     # 可能会丢消息?
+        robot = get_robot()
+        robot.enableReceivingMsg()  # 加队列
+
+        if robot.wcf.is_login():
+            self.__is_started = True
+            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')
+            self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_button_create.config(state='normal')
+            messagebox.showinfo('提示', '助手开始运行!')
+            start_batch_task()
+
+            wx_wxid = robot.wcf.get_self_wxid()
+            connection = get_global_db_connection()
+
+            def refresh_list():
+                # 清空列表
+                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())
+
+                # 查询数据
+                cursor = connection.cursor()
+                try:
+                    results = batch_task_get_list(cursor, wx_wxid)
+                finally:
+                    cursor.close()
+                for result in results:
+                    created_at = datetime.fromtimestamp(result["created_at"]).strftime("%Y-%m-%d %H:%M:%S")
+                    status = batch_task_status[result["status"]]
+                    values = (
+                    created_at, status, f"{result['success'] + result['fail']}/{result['total']}", result['fail'],
+                    result['content'])
+                    self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.insert('', "end",
+                                                                                               iid=result['id'],
+                                                                                               values=values)
+                    # 维持选中状态
+                    if result['id'] == self.selection_batch_task_id:
+                        self.tk_tabs_main_tabs.tk_tabs_main_tabs_1.tk_table_batch_task_list.selection_set(result['id'])
+
+                # 每 3秒调用一次这个函数
+                self.tk_tabs_main_tabs.after(3000, refresh_list)
+
+            self.tk_tabs_main_tabs.after(0, refresh_list)
+
+    def save_event(self, event):
+        conf().update({
+            "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:
+            json.dump(conf(), json_file, indent=4)
+
+        messagebox.showinfo('提示', '保存文件成功!')
+
+    def batch_task_create_event(self, event):
+        if self.__is_started:
+            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_start_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)
+            connection = get_global_db_connection()
+            cursor = connection.cursor()
+            try:
+                batch_task_update_status(cursor, int(item_id), 1)
+                connection.commit()
+            except Exception as e:
+                # 回滚事务
+                connection.rollback()
+                print(f"发生错误: {e}")
+            finally:
+                # 确保资源被正确释放
+                cursor.close()
+        else:
+            self.selection_batch_task_id = None
+
+    def batch_task_stop_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)
+            connection = get_global_db_connection()
+            cursor = connection.cursor()
+            try:
+                batch_task_update_status(cursor, int(item_id), 3)
+                connection.commit()
+            except Exception as e:
+                # 回滚事务
+                connection.rollback()
+                print(f"发生错误: {e}")
+            finally:
+                # 确保资源被正确释放
+                cursor.close()
+        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_stop_event(event))
+            elif item_values[1] == "已停止":
+                menu.add_command(label="开始", command=lambda: self.batch_task_start_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

+ 6 - 260
main.py

@@ -1,246 +1,8 @@
-import hashlib
-import json
-import threading
-import time
-import tkinter
-import uuid
-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 config import load_config, conf
-from service.robot import init_robot, get_robot
-
-
-class Win(WinGUI):
-    def __init__(self):
-        super().__init__()
-        self.__event_bind()
-        self.__style_config()
-        self.selection_batch_task_id = None
-        self._batch_task_service = BatchTaskService()
-
-    def __event_bind(self):
-        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):
-        pass
-
-    def version_event(self, event):
-        messagebox.showinfo('版本信息', '当前版本:v1.0.0')
-
-    def stop_event(self, event):
-        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
-        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
-    def check_token():
-        # 获取当前网卡mac地址
-        mac = uuid.getnode()
-        url = "https://wxadminapi.gkscrm.com/wechat-api/token/check"
-
-        token = str(conf().get("token"))
-
-        payload = json.dumps({
-            "token": token,
-            "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)
-
-            if resp['valid'] == True:
-                sign = resp['sign']
-                timestamp = resp['timestamp']
-                # 获取当前unix时间戳
-                current_timestamp = int(time.time())
-
-                if abs(current_timestamp - int(timestamp)) > 180:
-                    messagebox.showerror('错误', '验证失败!请重试')
-                    return False
-                # 创建一个MD5哈希对象
-                md5_hash = hashlib.md5()
-                # 更新哈希对象
-                md5_hash.update(token.encode('utf-8'))
-                # 获取十六进制格式的MD5哈希值
-                md5_token = md5_hash.hexdigest()
-
-                md5_hash = hashlib.md5()
-                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_sign = md5_hash.hexdigest()
-
-                if md5_sign != sign:
-                    messagebox.showerror('错误', 'token验证失败!')
-                    return False
-
-                return True
-
-            else:
-                messagebox.showerror('错误',
-                                     'token已失效 或 与当前设备未绑定,每个token只能绑定一台设备!请填入和当前设备绑定的token,或者获取新的token!')
-                return False
-        else:
-            messagebox.showerror('错误', '您的网络状态异常!请稍候重试')
-            return False
-
-    def start_event(self, event):
-
-        if self.tk_tabs_main_tabs.tk_tabs_main_tabs_0.tk_button_start.cget('state') == tkinter.DISABLED:
-            return
-
-        # if not self.check_token():
-        #     return
-
-        # 接收消息
-        # robot.enableRecvMsg()     # 可能会丢消息?
-        robot = get_robot()
-        robot.enableReceivingMsg()  # 加队列
-
-        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_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:
-            json.dump(conf(), json_file, indent=4)
-
-        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
-
-
+from common.sql_lite import init_global_db_connection, close_global_db_connection
+from logic.logic_ui import Win
+from service.batch_task import stop_batch_task
+from config import load_config
+from service.robot import init_robot
 
 
 def main():
@@ -256,25 +18,9 @@ def main():
         win = Win()
         win.mainloop()
     finally:
+        stop_batch_task()
         close_global_db_connection()
 
-    # wcf = Wcf(debug=conf().get("debug", False))
-    #
-    # def handler(sig, frame):
-    #     wcf.cleanup()  # 退出前清理环境
-    #     exit(0)
-    #
-    # signal.signal(signal.SIGINT, handler)
-    #
-    # robot = Robot(conf(), wcf)
-    #
-    # # 接收消息
-    # # robot.enableRecvMsg()     # 可能会丢消息?
-    # robot.enableReceivingMsg()  # 加队列
-    #
-    #
-    # robot.keepRunningAndBlockProcess()
-
 
 # Press the green button in the gutter to run the script.
 if __name__ == '__main__':

+ 1 - 1
main.spec

@@ -5,7 +5,7 @@ a = Analysis(
     ['main.py'],
     pathex=[],
     binaries=[('config.json','config.json')],
-    datas=[('wcferry', 'wcferry')],
+    datas=[('wcferry', 'wcferry'),('local.db', '.')],
     hiddenimports=['_cffi_backend'],
     hookspath=[],
     hooksconfig={},

+ 56 - 45
service/batch_task.py

@@ -1,58 +1,69 @@
 import threading
 import time
 
-from db.sql_lite import init_new_db_connection
+from common.sql_lite import init_new_db_connection
+from db.batch_msg import batch_msg_get_earliest_msg_to_execute, batch_msg_update_status
+from db.batch_task import batch_task_get_earliest_task_to_execute, batch_task_update_status, batch_task_update_success, \
+    batch_task_update_fail
 from service.robot import get_robot
 
+batch_task_event = threading.Event()
+
 
 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()
+        connection = init_new_db_connection()
+        try:
+            while batch_task_event.is_set():
+                # 查询最早的一条待执行或正在执行的任务
+                cursor = connection.cursor()
+                try:
+                    task_result = batch_task_get_earliest_task_to_execute(cursor, self._wx_wxid)
+                    if task_result is not None:
+                        if task_result["status"] == 1:
+                            # 更新任务状态为正在执行
+                            batch_task_update_status(cursor, task_result["id"], 2)
+                        # 获取一条待发送消息
+                        msg_result = batch_msg_get_earliest_msg_to_execute(cursor, task_result["id"])
+                        if msg_result is None:
+                            # 如果已没有待发送消息,则将任务状态改为已完成
+                            batch_task_update_status(cursor, task_result["id"], 4)
+                        else:
+                            # 如果有待发送消息,则发送
+                            send_result = self._robot.sendTextMsg(task_result["content"], msg_result["wxid"])
+                            if send_result == 0:
+                                batch_task_update_success(cursor, task_result["id"], task_result["success"]+1)
+                                batch_msg_update_status(cursor, msg_result["id"], 2)
+                            else:
+                                batch_task_update_fail(cursor, task_result["id"], task_result["fail"]+1)
+                                batch_msg_update_status(cursor, msg_result["id"], 3)
+                        connection.commit()
+                except Exception as e:
+                    # 回滚事务
+                    connection.rollback()
+                    print(f"发生错误: {e}")
+                finally:
+                    # 确保资源被正确释放
+                    cursor.close()
+                time.sleep(3)
+        finally:
+            connection.close()
+
+
+def start_batch_task():
+    global batch_task_event
+    print("Start task.")
+    # 设置事件为 True
+    batch_task_event.set()
+    threading.Thread(target=BatchTaskService().execute_task).start()
+
+
+def stop_batch_task():
+    global batch_task_event
+    # 清除事件
+    batch_task_event.clear()

+ 5 - 3
service/robot.py

@@ -167,7 +167,7 @@ class Robot():
             self.LOG.error(f"发生未知错误:{str(e0)}")
         return rsp
 
-    def sendTextMsg(self, msg: str, receiver: str, at_list: str = "") -> None:
+    def sendTextMsg(self, msg: str, receiver: str, at_list: str = "") -> int:
         """ 发送消息
         :param msg: 消息字符串
         :param receiver: 接收人wxid或者群id
@@ -187,10 +187,12 @@ class Robot():
         # {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
         if ats == "":
             self.LOG.info(f"To {receiver}: {msg}")
-            self.wcf.send_text(f"{msg}", receiver, at_list)
+            result = self.wcf.send_text(f"{msg}", receiver, at_list)
+            return result
         else:
             self.LOG.info(f"To {receiver}: {ats}\r{msg}")
-            self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
+            result = self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
+            return result
 
     def getAllContacts(self) -> dict:
         """

+ 1 - 1
service/ui.py → ui/ui.py

@@ -182,6 +182,6 @@ class Frame_main_tabs_1(Frame):
         return tk_table
 
     def __tk_button_create(self, parent):
-        btn = Button(parent, text="新建任务")
+        btn = Button(parent, text="新建任务", takefocus=False, state=tkinter.DISABLED)
         btn.place(x=560, y=10, width=80, height=30)
         return btn

+ 0 - 0
service/ui_batch_task_create.py → ui/ui_batch_task_create.py


+ 0 - 0
service/ui_batch_task_detail.py → ui/ui_batch_task_detail.py