From 06fca041872deeeb8a41b5f42839e58d771d98a9 Mon Sep 17 00:00:00 2001
From: shuaikangzhou <863909694@qq.com>
Date: Wed, 11 Jan 2023 13:04:19 +0800
Subject: [PATCH] Initial commit
---
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 4 +
.idea/vcs.xml | 6 +
TEST.py | 94 +
app/DB/__init__.py | 9 +
app/DB/data.py | 404 +
app/DataBase/__init__.py | 9 +
app/DataBase/config.txt | 7 +
app/DataBase/data.py | 197 +
app/DataBase/to_docx.py | 564 ++
app/Ui/__init__.py | 15 +
app/Ui/chat/__init__.py | 9 +
app/Ui/chat/addContact/__init__.py | 9 +
app/Ui/chat/addContact/addContact.py | 89 +
app/Ui/chat/addContact/addContactUi.py | 98 +
app/Ui/chat/addContact/addContactUi.ui | 207 +
app/Ui/chat/backup/chatUi - 副本.py | 143 +
app/Ui/chat/backup/chatUi - 副本.ui | 845 ++
app/Ui/chat/backup/chatUi.py | 184 +
app/Ui/chat/backup/chatUi.ui | 422 +
app/Ui/chat/chat - 副本.py | 428 +
app/Ui/chat/chat.py | 459 ++
app/Ui/chat/chatUi.py | 130 +
app/Ui/chat/chatUi.ui | 315 +
app/Ui/chat/group/CreateGroup.py | 107 +
app/Ui/chat/group/Group.py | 442 +
app/Ui/chat/group/GroupUi.py | 100 +
app/Ui/chat/group/GroupUi.ui | 217 +
app/Ui/chat/group/__init__.py | 9 +
app/Ui/chat/group/addGroup.py | 101 +
app/Ui/chat/group/addGroup.ui | 226 +
app/Ui/chat/group/create_groupUi.py | 113 +
app/Ui/chat/group/create_groupUi.ui | 217 +
app/Ui/chat/myinfo/__init__.py | 9 +
app/Ui/chat/myinfo/myinfo.py | 67 +
app/Ui/chat/myinfo/myinfoUi.py | 128 +
app/Ui/chat/myinfo/myinfoUi.ui | 211 +
app/Ui/chat/userinfo/__init__.py | 9 +
app/Ui/chat/userinfo/userinfoUi.py | 100 +
app/Ui/chat/userinfo/userinfoUi.ui | 208 +
app/Ui/decrypt/decrypt.py | 153 +
app/Ui/decrypt/decryptUi.py | 72 +
app/Ui/decrypt/decryptUi.ui | 119 +
app/Ui/mainview.py | 104 +
app/Ui/mainviewUi.py | 101 +
app/Ui/mainviewUi.ui | 216 +
app/__init__.py | 9 +
app/main/__init__.py | 9 +
auth_info_key_prefs.xml | 9 +
main.py | 51 +
sqlcipher-3.0.1/bin/adb.txt | 5 +
sqlcipher-3.0.1/bin/bat使用说明.txt | 3 +
sqlcipher-3.0.1/bin/sqlcipher - 副本.txt | 3 +
.../bin/sqlcipher-shell32-debug.exe | Bin 0 -> 2108416 bytes
.../bin/sqlcipher-shell32-debug.pdb | Bin 0 -> 86016 bytes
sqlcipher-3.0.1/bin/sqlcipher-shell32.exe | Bin 0 -> 1145856 bytes
.../bin/sqlcipher-shell64-debug.exe | Bin 0 -> 3052032 bytes
.../bin/sqlcipher-shell64-debug.pdb | Bin 0 -> 77824 bytes
sqlcipher-3.0.1/bin/sqlcipher-shell64.exe | Bin 0 -> 1567744 bytes
sqlcipher-3.0.1/bin/sqlcipher.bat | 3 +
sqlcipher-3.0.1/lib/sqlcipher32-debug.lib | Bin 0 -> 1925538 bytes
sqlcipher-3.0.1/lib/sqlcipher32-debug.pdb | Bin 0 -> 225280 bytes
sqlcipher-3.0.1/lib/sqlcipher32.lib | Bin 0 -> 2704968 bytes
sqlcipher-3.0.1/lib/sqlcipher32.pdb | Bin 0 -> 217088 bytes
sqlcipher-3.0.1/lib/sqlcipher64-debug.lib | Bin 0 -> 2266408 bytes
sqlcipher-3.0.1/lib/sqlcipher64-debug.pdb | Bin 0 -> 217088 bytes
sqlcipher-3.0.1/lib/sqlcipher64.lib | Bin 0 -> 2730162 bytes
sqlcipher-3.0.1/lib/sqlcipher64.pdb | Bin 0 -> 217088 bytes
sqlcipher-3.0.1/sqlcipher/sqlite3.h | 7304 +++++++++++++++++
test.html | 84 +
wechat.html | 168 +
71 files changed, 15330 insertions(+)
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/vcs.xml
create mode 100644 TEST.py
create mode 100644 app/DB/__init__.py
create mode 100644 app/DB/data.py
create mode 100644 app/DataBase/__init__.py
create mode 100644 app/DataBase/config.txt
create mode 100644 app/DataBase/data.py
create mode 100644 app/DataBase/to_docx.py
create mode 100644 app/Ui/__init__.py
create mode 100644 app/Ui/chat/__init__.py
create mode 100644 app/Ui/chat/addContact/__init__.py
create mode 100644 app/Ui/chat/addContact/addContact.py
create mode 100644 app/Ui/chat/addContact/addContactUi.py
create mode 100644 app/Ui/chat/addContact/addContactUi.ui
create mode 100644 app/Ui/chat/backup/chatUi - 副本.py
create mode 100644 app/Ui/chat/backup/chatUi - 副本.ui
create mode 100644 app/Ui/chat/backup/chatUi.py
create mode 100644 app/Ui/chat/backup/chatUi.ui
create mode 100644 app/Ui/chat/chat - 副本.py
create mode 100644 app/Ui/chat/chat.py
create mode 100644 app/Ui/chat/chatUi.py
create mode 100644 app/Ui/chat/chatUi.ui
create mode 100644 app/Ui/chat/group/CreateGroup.py
create mode 100644 app/Ui/chat/group/Group.py
create mode 100644 app/Ui/chat/group/GroupUi.py
create mode 100644 app/Ui/chat/group/GroupUi.ui
create mode 100644 app/Ui/chat/group/__init__.py
create mode 100644 app/Ui/chat/group/addGroup.py
create mode 100644 app/Ui/chat/group/addGroup.ui
create mode 100644 app/Ui/chat/group/create_groupUi.py
create mode 100644 app/Ui/chat/group/create_groupUi.ui
create mode 100644 app/Ui/chat/myinfo/__init__.py
create mode 100644 app/Ui/chat/myinfo/myinfo.py
create mode 100644 app/Ui/chat/myinfo/myinfoUi.py
create mode 100644 app/Ui/chat/myinfo/myinfoUi.ui
create mode 100644 app/Ui/chat/userinfo/__init__.py
create mode 100644 app/Ui/chat/userinfo/userinfoUi.py
create mode 100644 app/Ui/chat/userinfo/userinfoUi.ui
create mode 100644 app/Ui/decrypt/decrypt.py
create mode 100644 app/Ui/decrypt/decryptUi.py
create mode 100644 app/Ui/decrypt/decryptUi.ui
create mode 100644 app/Ui/mainview.py
create mode 100644 app/Ui/mainviewUi.py
create mode 100644 app/Ui/mainviewUi.ui
create mode 100644 app/__init__.py
create mode 100644 app/main/__init__.py
create mode 100644 auth_info_key_prefs.xml
create mode 100644 main.py
create mode 100644 sqlcipher-3.0.1/bin/adb.txt
create mode 100644 sqlcipher-3.0.1/bin/bat使用说明.txt
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher - 副本.txt
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher-shell32-debug.exe
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher-shell32-debug.pdb
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher-shell32.exe
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher-shell64-debug.exe
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher-shell64-debug.pdb
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher-shell64.exe
create mode 100644 sqlcipher-3.0.1/bin/sqlcipher.bat
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher32-debug.lib
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher32-debug.pdb
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher32.lib
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher32.pdb
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher64-debug.lib
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher64-debug.pdb
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher64.lib
create mode 100644 sqlcipher-3.0.1/lib/sqlcipher64.pdb
create mode 100644 sqlcipher-3.0.1/sqlcipher/sqlite3.h
create mode 100644 test.html
create mode 100644 wechat.html
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..dc9ea49
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TEST.py b/TEST.py
new file mode 100644
index 0000000..f31af6c
--- /dev/null
+++ b/TEST.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Sat May 9 17:14:37 2020
+
+@author: Giyn
+"""
+
+import sys
+from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QTextBrowser, QVBoxLayout, QHBoxLayout, QMessageBox
+from PyQt5.QtGui import QIcon
+
+
+class Simple_Window(QWidget):
+ def __init__(self):
+ super(Simple_Window, self).__init__() # 使用super函数可以实现子类使用父类的方法
+ self.setWindowTitle("记事本")
+ self.setWindowIcon(QIcon('NoteBook.png')) # 设置窗口图标
+ self.resize(412, 412)
+ self.text_browser = QTextBrowser(self) # 实例化一个QTextBrowser对象
+ # self.text_browser.setText("
Hello World!
") # 设置编辑框初始化时显示的文本
+ # self.text_browser.setReadOnly(False) # 调用setReadOnly方法并传入False参数即可编辑文本浏览框(编辑框也可以变成只读)
+
+ self.save_button = QPushButton("Save", self)
+ self.clear_button = QPushButton("Clear", self)
+ self.add_button = QPushButton("Add", self)
+
+ self.save_button.clicked.connect(lambda: self.button_slot(self.save_button))
+ self.clear_button.clicked.connect(lambda: self.button_slot(self.clear_button))
+ self.add_button.clicked.connect(self.add_text)
+
+ self.h_layout = QHBoxLayout()
+ self.v_layout = QVBoxLayout()
+
+ self.h_layout.addWidget(self.save_button)
+ self.h_layout.addWidget(self.clear_button)
+ self.h_layout.addWidget(self.add_button)
+ self.v_layout.addWidget(self.text_browser)
+ self.v_layout.addLayout(self.h_layout)
+
+ self.setLayout(self.v_layout)
+
+ def button_slot(self, button):
+ if button == self.save_button:
+ choice = QMessageBox.question(self, "Question", "Do you want to save it?", QMessageBox.Yes | QMessageBox.No)
+ if choice == QMessageBox.Yes:
+ with open('Second text.txt', 'w') as f:
+ f.write(self.text_browser.toPlainText())
+ self.close()
+ elif choice == QMessageBox.No:
+ self.close()
+ elif button == self.clear_button:
+ self.text_browser.clear()
+
+ def add_text(self):
+ # self.text_browser.append("Hello World!
") # 调用append方法可以向文本浏览框中添加文本
+ html = """
+
+
+ 我要抢楼
+
+
+

+
+ """
+ self.text_browser.insertHtml(html)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window = Simple_Window()
+ window.show()
+ sys.exit(app.exec())
diff --git a/app/DB/__init__.py b/app/DB/__init__.py
new file mode 100644
index 0000000..b32c754
--- /dev/null
+++ b/app/DB/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/25 19:53
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/DB/data.py b/app/DB/data.py
new file mode 100644
index 0000000..e8ec0fd
--- /dev/null
+++ b/app/DB/data.py
@@ -0,0 +1,404 @@
+# -*- coding: utf-8 -*-
+"""
+@File : data.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 20:59
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import json
+import pymysql
+import random
+import time
+import hashlib
+import datetime
+import os
+import shutil
+f = open('./app/data/config.json','r')
+config = json.loads(f.read())
+username = config['username']
+password = config['password']
+database = config['database']
+# 打开数据库连接t
+db = pymysql.connect(
+ host='localhost',
+ user=username,
+ password=password,
+ database=database,
+ autocommit=True
+)
+cursor = db.cursor()
+
+
+def register(username, password, nickname):
+ """
+ 注册账号
+ :param username: 用户名
+ :param password: 密码
+ :param nickname: 昵称
+ :return:
+ """
+ try:
+ create = 'insert into users (username,password,createTime) values (%s,%s,%s)'
+ timestamp = float(time.time())
+ dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ cursor.execute(create, [username, password, dt])
+ create = 'insert into userinfo (username,nickname) values (%s,%s)'
+ cursor.execute(create, [username, nickname])
+ db.commit()
+ try:
+ sql = f'''
+ create view msg_view_{username}
+ as
+ select msgId,type,IsSend,createTime,content,talkerId from message
+ where username = %s
+ order by msgId;
+ '''
+ cursor.execute(sql, [username])
+ sql = f'''
+ create view contact_view_{username}
+ as
+ select contactId,conRemark,type,addTime from contact
+ where username = %s;
+ '''
+ cursor.execute(sql, [username])
+ sql = f'''
+ create view group_view_{username}
+ as
+ select g_id,g_name,gu_nickname,gu_type,gu_time from group_users,`group`
+ where gu_uid=%s and group_users.gu_gid=`group`.g_id;
+ '''
+ cursor.execute(sql, [username])
+ db.commit()
+ except pymysql.err.OperationalError:
+ print('视图已经存在')
+ except pymysql.err.IntegrityError:
+ print('用户已存在')
+ return False
+ return True
+
+
+def login(username, password):
+ select = 'select * from users where username = %s and password = %s'
+ cursor.execute(select, [username, password])
+ result = cursor.fetchall()
+ if result:
+ return True
+ return False
+
+
+def del_user(username):
+ sql = 'delete from users where username = %s'
+ cursor.execute(sql, [username])
+ db.commit()
+ return True
+
+
+def searchUser(username):
+ select = 'select * from users where username = %s'
+ cursor.execute(select, [username])
+ result = cursor.fetchall()
+ if result:
+ return True
+ return False
+
+
+def add_contact(username, contactId, conRemark, _type=3):
+ send = 'insert into contact (username,conRemark,type,addTime,contactId) values(%s,%s,%s,%s,%s)'
+ dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ select = 'select * from userinfo where username = %s '
+ cursor.execute(select, [username])
+ result = cursor.fetchall()
+ if not result:
+ return False
+ if not conRemark:
+ conRemark = None
+ try:
+ if _type == 3:
+ cursor.execute(send, [username, conRemark, 3, dt, contactId])
+ db.commit()
+ return (contactId, conRemark, 3, dt)
+ except:
+ return False
+
+
+def get_userinfo(username):
+ sql = 'select * from userinfo where username = %s'
+ cursor.execute(sql, [username])
+ result = cursor.fetchone()
+ return result[0]
+
+
+def online(username, socket=None):
+ status = random.randint(32010, 52090)
+ sql = 'update userinfo set status = %s where username=%s'
+ cursor.execute(sql, [status, username])
+ db.commit()
+
+ return status
+
+
+def tell_online(username, socket):
+ if socket:
+ contacts = get_contacts(username)
+ for contact in contacts:
+ contactID = contact[0]
+ status = check_online(contactID)
+ if status != -1:
+ ta_addr = ('localhost', status)
+ send_data = {
+ 'type': 'T',
+ 'username': username,
+ 'content': '在线0000_1'
+ }
+ socket.sendto(json.dumps(send_data).encode('utf-8'), ta_addr)
+
+
+def offline(username):
+ status = -1
+ sql = 'update userinfo set status = %s where username=%s'
+ cursor.execute(sql, [status, username])
+ db.commit()
+ return status
+
+
+def check_online(username):
+ db.commit()
+ sql = 'select status from userinfo where username=%s'
+ cursor.execute(sql, [username])
+ db.commit()
+ result = cursor.fetchone()
+ # print(username, '端口号:', result)
+ if result:
+ return result[0]
+ else:
+ return -1
+
+
+def send_msg(IsSend, msg, ta, me, status=-1, _type=3):
+ if status == -1:
+ return False
+ send = 'insert into message (type,isSend,createTime,content,talkerId,username) values(%s,%s,%s,%s,%s,%s)'
+ dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ if _type == 3:
+ cursor.execute(send, [_type, IsSend, dt, msg, ta, me])
+ db.commit()
+ return 1, _type, IsSend, datetime.datetime.now(), msg, ta
+
+
+def send_group_msg(gid, msg, talker, IsSend=0, _type=3):
+ send = 'insert into group_message (g_id,gm_type,gm_content,gm_time,gm_talker,gm_isSend) values(%s,%s,%s,%s,%s,%s)'
+ dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ if _type == 3:
+ cursor.execute(send, [gid, _type, msg, dt, talker, IsSend])
+ db.commit()
+ return 1, gid, _type, msg, datetime.datetime.now(), talker, IsSend
+
+
+def get_group_message(g_id):
+ sql = f'select * from group_message where g_id = %s order by gm_id'
+ cursor.execute(sql, [g_id])
+ result = cursor.fetchall()
+ return result
+
+
+def avatar_md5(wxid):
+ m = hashlib.md5()
+ # 参数必须是byte类型,否则报Unicode-objects must be encoded before hashing错误
+ m.update(bytes(wxid.encode('utf-8')))
+ return m.hexdigest()
+
+
+def get_avator(wxid):
+ if wxid == None:
+ return
+ wxid = str(wxid)
+ avatar = avatar_md5(wxid)
+ avatar_path = r"./data/avatar/"
+ path = avatar_path + avatar[:2] + '/' + avatar[2:4]
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if avatar in file:
+ avatar = file
+ break
+ return path + '/' + avatar
+
+
+def get_contacts(username):
+ sql = f'select * from contact_view_{username} '
+ cursor.execute(sql)
+ result = cursor.fetchall()
+ return result
+
+
+def get_myinfo(username):
+ sql = 'select * from userinfo where username = %s'
+ cursor.execute(sql, [username])
+ result = cursor.fetchone()
+ return result
+
+
+def update_userinfo(userinfo):
+ sql = '''
+ update userinfo
+ set
+ nickname=%s,
+ gender=%s,
+ city=%s,
+ province=%s,
+ tel=%s,
+ email=%s,
+ signsture=%s
+ where username=%s
+ '''
+ cursor.execute(sql, userinfo)
+
+
+def get_nickname(username):
+ sql = 'select nickname from userinfo where username=%s'
+ cursor.execute(sql, [username])
+ result = cursor.fetchone()
+ return result[0]
+
+
+def update_conRemark(username, contactId, new_conRemark):
+ sql = 'update contact set conRemark=%s where username=%s and contactId=%s'
+ cursor.execute(sql, [new_conRemark, username, contactId])
+ db.commit()
+ return True
+
+
+def delete_contact(username, contactId):
+ sql = 'delete from contact where username=%s and contactId=%s'
+ cursor.execute(sql, [username, contactId])
+ db.commit()
+ return True
+
+
+def delete_group(uid, gid):
+ sql = 'delete from group_users where gu_uid=%s and gu_gid=%s'
+ cursor.execute(sql, [uid, gid])
+ db.commit()
+ return True
+
+
+def get_remark(username, talkerId):
+ sql = f'select conRemark from contact_view_{username} where contactId = %s'
+ cursor.execute(sql, [talkerId])
+ result = cursor.fetchone()
+ return result[0]
+
+
+def mycopyfile(srcfile, dstpath):
+ # 复制函数
+ """
+ 复制文件
+ :param srcfile: 原路径
+ :param dstpath: 新路径
+ :return:
+ """
+ # if 1:
+ try:
+ if not os.path.isfile(srcfile):
+ print("%s not exist!" % (srcfile))
+ return
+ else:
+ print(dstpath)
+ if os.path.isfile(dstpath):
+ os.remove(dstpath)
+ fpath, fname = os.path.split(srcfile) # 分离文件名和路径
+ dpath, dname = os.path.split(dstpath)
+ if not os.path.exists(dpath):
+ os.makedirs(dpath) # 创建路径
+ # dstpath = '/'.join(dstpath.split('/')[:-1])+'/'
+
+ # print(dpath,dname)
+ shutil.copy(srcfile, dpath) # 复制文件
+ os.rename(dpath + '/' + fname, dstpath)
+ # print ("copy %s -> %s"%(srcfile, dstpath + fname))
+ except:
+ print('文件已存在')
+
+
+def get_message(username, talkerId):
+ sql = f'select * from msg_view_{username} where talkerId = %s order by msgId'
+ cursor.execute(sql, [talkerId])
+ result = cursor.fetchall()
+ return result
+
+
+def create_group(g_name, g_admin, g_notice=None, g_intro=None):
+ g_id = random.randint(10000, 99999)
+ sql = '''insert into `group` (g_id,g_name,g_admin,g_notice,g_intro,g_time) values (%s, %s, %s, %s, %s, %s);'''
+ dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ param = [g_id, g_name, g_admin, g_notice, g_intro, dt]
+ print(param)
+ cursor.execute(sql, param)
+ sql = '''insert into `group_users` (gu_uid,gu_gid,gu_time,gu_nickname,gu_type) values (%s, %s, %s, %s, %s);'''
+ param = [g_admin, g_id, dt, None, 1]
+ cursor.execute(sql, param)
+ db.commit()
+ return g_id
+
+
+def add_group(username, g_id, nickname):
+ group = search_group(g_id)
+ if not group:
+ return False
+ if not nickname:
+ nickname = None
+ sql = 'insert into group_users (gu_uid,gu_gid,gu_time,gu_nickname,gu_type) values (%s,%s,%s,%s,%s)'
+ dt = dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ cursor.execute(sql, [username, g_id, dt, nickname, 2])
+ db.commit()
+ return [g_id, dt, nickname, 2]
+
+
+def search_group(gid):
+ sql = 'select * from `group` where g_id=%s'
+ cursor.execute(sql, [gid])
+ result = cursor.fetchone()
+ return result
+
+
+def get_groups(username):
+ sql = f'select * from group_view_{username}'
+ cursor.execute(sql)
+ result = cursor.fetchall()
+ return result
+
+
+def get_group_users(g_id):
+ db.commit()
+ sql = '''
+ select gu_uid,gu_gid,gu_time,gu_nickname,status
+ from group_users,userinfo
+ where gu_gid=%s and
+ gu_uid=userinfo.username
+ '''
+ cursor.execute(sql, [g_id])
+ db.commit()
+ result = cursor.fetchall()
+ return result
+
+
+# send_msg(3, '你好', '123456')
+if __name__ == '__main__':
+ # add_contact('2020303457', '周帅康')
+ # contacts = get_contacts('')
+ # print(contacts)
+ # messages = get_message('2020303457', '')
+ # print(messages)
+ # online('2020303457')
+ # status = check_online('2020303457')
+ # print('status:', status)
+ # offline('2020303457')
+ # status = check_online('2020303457')
+ # print('status:', status)
+ # print(get_remark('', 2020303457))
+ # print(get_groups('2020303457'))
+ # # print(create_group("DB实验", '2020303457', g_notice=str(12), g_intro="test"))
+ # print(get_groups(''))
+ # print(get_group_users(61067))
+ print(get_myinfo(''))
diff --git a/app/DataBase/__init__.py b/app/DataBase/__init__.py
new file mode 100644
index 0000000..c5d89be
--- /dev/null
+++ b/app/DataBase/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2023/1/5 0:10
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/DataBase/config.txt b/app/DataBase/config.txt
new file mode 100644
index 0000000..1a2ee2e
--- /dev/null
+++ b/app/DataBase/config.txt
@@ -0,0 +1,7 @@
+
+ PRAGMA key = '10f35f1';
+ PRAGMA cipher_migrate;
+ ATTACH DATABASE './app/DataBase/Msg.db' AS Msg KEY '';
+ SELECT sqlcipher_export('Msg');
+ DETACH DATABASE Msg;
+
\ No newline at end of file
diff --git a/app/DataBase/data.py b/app/DataBase/data.py
new file mode 100644
index 0000000..34c09c8
--- /dev/null
+++ b/app/DataBase/data.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+"""
+@File : data.py
+@Author : Shuaikang Zhou
+@Time : 2023/1/5 0:11
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import hashlib
+import os
+import sqlite3
+import time
+
+import requests
+
+DB = None
+cursor = None
+
+
+def mkdir(path):
+ path = path.strip()
+ path = path.rstrip("\\")
+ if os.path.exists(path):
+ return False
+ os.makedirs(path)
+ return True
+
+
+mkdir(os.path.abspath('.') + '/app/data/emoji')
+if os.path.exists('./app/DataBase/Msg.db'):
+ DB = sqlite3.connect("./app/DataBase/Msg.db", check_same_thread=False)
+ # '''创建游标'''
+ cursor = DB.cursor()
+if os.path.exists('./Msg.db'):
+ DB = sqlite3.connect("./Msg.db")
+ # '''创建游标'''
+ cursor = DB.cursor()
+
+
+class Me:
+ """个人信息"""
+
+ def __init__(self, username):
+ self.username = username # 自己的用户名
+ self.my_avatar = get_avator(self.username) # 自己的头像地址
+ self.city = None
+ self.province = None
+
+
+def decrypt(db, key):
+ if not key:
+ print('缺少数据库密钥')
+ return False
+ if not db:
+ print('没有数据库文件')
+ if os.path.exists('./app/DataBase/Msg.db'):
+ print('/app/DataBase/Msg.db 已经存在')
+ return True
+ cmd = '/sqlcipher-3.0.1/bin/sqlcipher-shell32.exe'
+ print(os.path.abspath('.'))
+ param = f"""
+ PRAGMA key = '{key}';
+ PRAGMA cipher_migrate;
+ ATTACH DATABASE './app/DataBase/Msg.db' AS Msg KEY '';
+ SELECT sqlcipher_export('Msg');
+ DETACH DATABASE Msg;
+ """
+ with open('./app/DataBase/config.txt', 'w') as f:
+ f.write(param)
+ p = os.system(f"{os.path.abspath('.')}{cmd} {db} < ./app/DataBase/config.txt")
+ global DB
+ global cursor
+ DB = sqlite3.connect("./app/DataBase/Msg.db")
+ # '''创建游标'''
+ cursor = DB.cursor()
+
+
+def get_myinfo():
+ sql = 'select * from userinfo where id=2'
+ cursor.execute(sql)
+ result = cursor.fetchone()
+ me = Me(result[2])
+ return me
+
+
+def get_contacts():
+ sql = 'select * from rcontact'
+ cursor.execute(sql)
+ result = cursor.fetchall()
+ return result
+
+
+def get_rconversation():
+ sql = '''
+ select msgCount,username,unReadCount,chatmode,status,isSend,conversationTime,msgType,digest,digestUser,hasTrunc,attrflag
+ from rconversation
+ where chatmode=1 or chatmode=0 and (msgType='1' or msgType='3' or msgType='47')
+ order by msgCount desc
+ '''
+ '''order by conversationTime desc'''
+ cursor.execute(sql)
+ result = cursor.fetchall()
+ return result
+
+
+def timestamp2str(timestamp):
+ # t2 = 1586102400
+ s_l = time.localtime(timestamp / 1000)
+ ts = time.strftime("%Y/%m/%d", s_l)
+ # print(ts)
+ return ts
+
+
+def get_conRemark(username):
+ sql = 'select conRemark,nickname from rcontact where username=?'
+ cursor.execute(sql, [username])
+ result = cursor.fetchone()
+ if result[0]:
+ return result[0]
+ else:
+ return result[1]
+
+
+def avatar_md5(wxid):
+ m = hashlib.md5()
+ # 参数必须是byte类型,否则报Unicode-objects must be encoded before hashing错误
+ m.update(bytes(wxid.encode('utf-8')))
+ return m.hexdigest()
+
+
+def get_avator(wxid):
+ if wxid == None:
+ return
+ wxid = str(wxid)
+ avatar = avatar_md5(wxid)
+ avatar_path = r"./app/data/avatar/"
+ path = avatar_path + avatar[:2] + '/' + avatar[2:4]
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if avatar in file:
+ avatar = file
+ break
+ return f'''{path}/{avatar}'''
+ # return f'''{path}/user_{avatar}.png'''
+
+
+def get_message(wxid, num):
+ sql = '''
+ select * from message
+ where talker = ?
+ order by msgId desc
+ limit ?,100
+ '''
+ cursor.execute(sql, [wxid, num * 100])
+ return cursor.fetchall()
+
+
+def get_emoji(imgPath):
+ newPath = f"{os.path.abspath('.')}/app/data/emoji/{imgPath}"
+ if os.path.exists(newPath):
+ return newPath
+ else:
+ sql = '''
+ select cdnUrl
+ from EmojiInfo
+ where md5=?
+ '''
+ cursor.execute(sql, [imgPath])
+ result = cursor.fetchone()
+ download_emoji(newPath,result[0])
+ return newPath
+
+
+def download_emoji(imgPath, url):
+ resp = requests.get(url)
+ with open(imgPath, 'wb') as f:
+ f.write(resp.content)
+ return imgPath
+
+
+if __name__ == '__main__':
+ # rconversation = get_rconversation()
+ # for i in rconversation:
+ # print(i)
+ timestamp2str(1632219794000)
+ conremark = get_conRemark('wxid_q3ozn70pweud22')
+ print(conremark)
+ print(get_avator('wxid_q3ozn70pweud22'))
+ me = get_myinfo()
+ print(me.__dict__)
+ # r = get_message('wxid_78ka0n92rzzj22', 0)
+ # r.reverse()
+ # for i in r:
+ # print(i)
+ # print(len(r), type(r))
+ print(get_emoji('f5f8a6116e177937ca9795c47f10113d'))
diff --git a/app/DataBase/to_docx.py b/app/DataBase/to_docx.py
new file mode 100644
index 0000000..087b2fc
--- /dev/null
+++ b/app/DataBase/to_docx.py
@@ -0,0 +1,564 @@
+import hashlib
+import os
+import re
+import threading
+import time
+import docx
+import pandas as pd
+import requests
+from docx import shared
+from docx.enum.table import WD_ALIGN_VERTICAL
+from docx.enum.text import WD_COLOR_INDEX, WD_PARAGRAPH_ALIGNMENT
+from docxcompose.composer import Composer
+import rcontact
+
+import sys
+from PyQt5.QtGui import QIcon
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import QPixmap
+
+path = 'D:\Project\PythonProject\WeChat'
+# conRemark = '曹雨萱'
+# self_wxid = rcontact.get_self_wxid()
+# ta_wxid = rcontact.get_one_wxid(conRemark)
+
+'''
+#! 创建emoji目录,存放emoji文件
+'''
+
+
+def mkdir(path):
+ path = path.strip()
+ path = path.rstrip("\\")
+ if os.path.exists(path):
+ return False
+ os.makedirs(path)
+ return True
+
+
+mkdir(path+'/emoji')
+# mkdir('..//db_tables')
+'''
+#! 将wxid使用MD5编码加密
+#! 加密结果是用户头像路径
+'''
+
+
+def avatar_md5(wxid):
+ m = hashlib.md5()
+ # 参数必须是byte类型,否则报Unicode-objects must be encoded before hashing错误
+ m.update(bytes(wxid.encode('utf-8')))
+ return m.hexdigest()
+
+
+'''
+#! 获取头像文件完整路径
+'''
+
+
+def get_avator(wxid):
+ avatar = avatar_md5(wxid)
+ avatar_path = path + "/avatar/"
+ Path = avatar_path + avatar[:2] + '/' + avatar[2:4]
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if avatar in file:
+ avatar = file
+ break
+ return Path + '/' + avatar
+
+def read_csv(conRemark):
+ '''
+ :param conRemark: (str) 要导出的联系人备注名
+ :return: pandas数据
+ '''
+ user_data = pd.read_csv(f'{path}/db_tables/{conRemark}.csv')
+ '''将浮点数转化成字符串类型,否则会舍入影响时间结果'''
+ user_data['createTime'] = user_data['createTime'].astype(str)
+ # print(user_data)
+ return user_data
+
+
+def download_emoji(content, img_path):
+ '''
+ #! 下载emoji文件
+ #!
+ #!
+ '''
+ # if 1:
+ try:
+ # print(img_path)
+ url = content.split('cdnurl = "')[1].split('"')[0]
+ print(url)
+ url = ':'.join(url.split('*#*'))
+ if 'amp;' in url:
+ url = ''.join(url.split('amp;'))
+ print('emoji downloading!!!')
+ resp = requests.get(url)
+ with open(f'{path}/emoji/{img_path}', 'wb') as f:
+ f.write(resp.content)
+ except Exception:
+ print("emoji download error")
+
+
+def time_format(timestamp):
+ '''
+ #! 将字符串类型的时间戳转换成日期
+ #! 返回格式化的时间字符串
+ #! %Y-%m-%d %H:%M:%S
+ '''
+ # print(timestamp)
+ # timestamp = timestamp[:-5]
+ timestamp = float(timestamp[:-3] + '.' + timestamp[-3:])
+ # print(timestamp)
+ time_tuple = time.localtime(timestamp)
+ # print(time.strftime("%Y-%m-%d %H:%M:%S", time_tuple))
+ # quit()
+ return time.strftime("%Y-%m-%d %H:%M:%S", time_tuple)
+
+
+
+
+
+def IS_5_min(last_m, now_m):
+ '''
+ #! 判断两次聊天时间是不是大于五分钟
+ #! 若大于五分钟则显示时间
+ #! 否则不显示
+ '''
+ last_m = last_m[:-5]
+ last_m = float(last_m + '.' + last_m[-5:2])
+ now_m = now_m[:-5]
+ now_m = float(now_m + '.' + now_m[-5:2])
+ '''两次聊天记录时间差,单位是秒'''
+ time_sub = now_m - last_m
+ return time_sub >= 300
+
+
+def judge_type(Type):
+ pass
+
+
+
+
+
+
+
+'''合并word文档到一个文件里'''
+
+
+def merge_docx(conRemark, n):
+ origin_docx_path = f"{path}/{conRemark}"
+ all_word = os.listdir(origin_docx_path)
+ all_file_path = []
+ for i in range(n):
+ file_name = f"{conRemark}{i}.docx"
+ all_file_path.append(origin_docx_path + '/' + file_name)
+ filename = f"{conRemark}.docx"
+ # print(all_file_path)
+ doc = docx.Document()
+ doc.save(origin_docx_path + '/' + filename)
+ master = docx.Document(origin_docx_path + '/' + filename)
+ middle_new_docx = Composer(master)
+ num = 0
+ for word in all_file_path:
+ word_document = docx.Document(word)
+ word_document.add_page_break()
+ if num != 0:
+ middle_new_docx.append(word_document)
+ num = num + 1
+ middle_new_docx.save(origin_docx_path + '/' + filename)
+
+
+class MyThread(QThread):
+ signal = pyqtSignal(str)
+ self_text = pyqtSignal(str)
+ ta_text = pyqtSignal(str)
+ bar = pyqtSignal(int)
+ def __init__(self):
+ super(MyThread, self).__init__()
+ self.ta_info = {}
+ self.self_info = {}
+ self.textBrowser = None
+ self.num = 0
+ self.total_num = 1
+
+ def get_avator(self):
+ self.wxid = self.self_info['wxid']
+ self.ta_wxid = self.ta_info['wxid']
+ self.avator = get_avator(self.wxid)
+ print(self.avator)
+ self.ta_avator = get_avator(self.ta_wxid)
+ print(self.ta_avator)
+ # quit()
+ self.img_self = open(self.avator, 'rb')
+ self.img_ta = open(self.ta_avator, 'rb')
+
+ def read_csv(self, conRemark):
+ '''
+ :param conRemark: (str) 要导出的联系人备注名
+ :return: pandas数据
+ '''
+ user_data = pd.read_csv(f'{path}/db_tables/{conRemark}.csv')
+ '''将浮点数转化成字符串类型,否则会舍入影响时间结果'''
+ user_data['createTime'] = user_data['createTime'].astype(str)
+ # print(user_data)
+ self.total_num = len(user_data)
+ return user_data
+ def create_table(self,doc, isSend):
+ '''
+ #! 创建一个1*2表格
+ #! isSend = 1 (0,0)存聊天内容,(0,1)存头像
+ #! isSend = 0 (0,0)存头像,(0,1)存聊天内容
+ #! 返回聊天内容的坐标
+ '''
+ table = doc.add_table(rows=1, cols=2, style='Normal Table')
+ table.cell(0, 1).height = shared.Inches(0.5)
+ table.cell(0, 0).height = shared.Inches(0.5)
+ text_size = 1
+ if isSend:
+ '''表格右对齐'''
+ table.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ avatar = table.cell(0, 1).paragraphs[0].add_run()
+ '''插入头像,设置头像宽度'''
+ avatar.add_picture(self.img_self, width=shared.Inches(0.5))
+ '''设置单元格宽度跟头像一致'''
+ table.cell(0, 1).width = shared.Inches(0.5)
+ content_cell = table.cell(0, 0)
+ '''聊天内容右对齐'''
+ content_cell.paragraphs[0].paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ else:
+ avatar = table.cell(0, 0).paragraphs[0].add_run()
+ avatar.add_picture(self.img_ta, width=shared.Inches(0.5))
+ '''设置单元格宽度'''
+ table.cell(0, 0).width = shared.Inches(0.5)
+ content_cell = table.cell(0, 1)
+ '''聊天内容垂直居中对齐'''
+ content_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
+ return content_cell
+
+ def text(self, doc, isSend, message, status):
+ if status == 5:
+ message += '(未发出) '
+ content_cell = self.create_table(doc, isSend)
+ content_cell.paragraphs[0].add_run(message)
+ content_cell.paragraphs[0].font_size = shared.Inches(0.5)
+ self.self_text.emit(message)
+ if isSend:
+ p = content_cell.paragraphs[0]
+ p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ doc.add_paragraph()
+
+ def image(self, doc, isSend, Type, content, imgPath):
+ '''
+ #! 插入聊天图片
+ #! isSend = 1 只有缩略图
+ #! isSend = 0 有原图
+ :param doc:
+ :param isSend:
+ :param Type:
+ :param content:
+ :param imgPath:
+ :return:
+ '''
+ content = self.create_table(doc, isSend)
+ run = content.paragraphs[0].add_run()
+ imgPath = imgPath.split('//th_')[-1]
+ Path = None
+ if Type == 3:
+ Path = f'{path}/image2//{imgPath[:2]}//{imgPath[2:4]}'
+ elif Type == 47:
+ Path = '{path}/emoji'
+ for root, dirs, files in os.walk(Path):
+ for file in files:
+ if isSend:
+ if imgPath + 'hd' in file:
+ imgPath = file
+ try:
+ run.add_picture(f'{Path}/{imgPath}', height=shared.Inches(2))
+ doc.add_paragraph()
+ except Exception:
+ print("Error!image")
+ return
+ elif imgPath in file:
+ imgPath = file
+ break
+ try:
+ run.add_picture(f'{Path}/{imgPath}', height=shared.Inches(2))
+ doc.add_paragraph()
+ except Exception:
+ print("Error!image")
+
+ # run.add_picture(f'{Path}/{imgPath}', height=shared.Inches(2))
+
+ def emoji(self, doc, isSend, content, imgPath):
+ '''
+ #! 添加表情包
+ :param isSend:
+ :param content:
+ :param imgPath:
+ :return:
+ '''
+ if 1:
+ # try:
+ Path = f'{path}/emoji/{imgPath}'
+ is_Exist = os.path.exists(Path)
+ if not is_Exist:
+ '''表情包不存在,则下载表情包到emoji文件夹中'''
+ download_emoji(content, imgPath)
+ self.image(doc, isSend, Type=47, content=content, imgPath=imgPath)
+ # except Exception:
+ # print("can't find emoji!")
+
+ def wx_file(self, doc, isSend, content, status):
+ '''
+ #! 添加微信文件
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ pattern = re.compile(r"(.*?)<")
+ r = pattern.search(content).group()
+ filename = r.lstrip('').rstrip('<')
+ self.text(doc, isSend, filename, status)
+
+ def retract_message(self,doc, isSend, content, status):
+ '''
+ #! 显示撤回消息
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ paragraph = doc.add_paragraph(content)
+ paragraph.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
+
+ def reply(self, doc, isSend, content, status):
+ '''
+ #! 添加回复信息
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ pattern1 = re.compile(r"(?P(.*?))")
+ title = pattern1.search(content).groupdict()['title']
+ pattern2 = re.compile(r"(?P(.*?))")
+ displayname = pattern2.search(content).groupdict()['displayname']
+ '''匹配回复的回复'''
+ pattern3 = re.compile(r"\n?title>(?P(.*?))\n?</title>")
+ if not pattern3.search(content):
+ if isSend == 0:
+ '''匹配对方的回复'''
+ pattern3 = re.compile(r"(?P(.*?))")
+ else:
+ '''匹配自己的回复'''
+ pattern3 = re.compile(r"\n?(?P(.*?))\n?")
+
+ '''这部分代码完全可以用if代替'''
+
+ try:
+ '''试错'''
+ text = pattern3.search(content).groupdict()['content']
+ except Exception:
+ try:
+ '''试错'''
+ text = pattern3.search(content).groupdict()['content']
+ except Exception:
+ '''试错'''
+ pattern3 = re.compile(r"\n?(?P(.*?))\n?")
+ '''试错'''
+ if pattern3.search(content):
+ text = pattern3.search(content).groupdict()['content']
+ else:
+ text = '图片'
+ if status == 5:
+ message = '(未发出) ' + ''
+ content_cell = self.create_table(doc, isSend)
+ content_cell.paragraphs[0].add_run(title)
+ content_cell.paragraphs[0].font_size = shared.Inches(0.5)
+ reply_p = content_cell.add_paragraph()
+ run = content_cell.paragraphs[1].add_run(displayname + ':' + text)
+ '''设置被回复内容格式'''
+ run.font.color.rgb = shared.RGBColor(121, 121, 121)
+ run.font_size = shared.Inches(0.3)
+ run.font.highlight_color = WD_COLOR_INDEX.GRAY_25
+
+ if isSend:
+ p = content_cell.paragraphs[0]
+ p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ reply_p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ doc.add_paragraph()
+
+
+ def pat_a_pat(self, doc, isSend, content, status):
+ '''
+ #! 添加拍一拍信息
+ todo 把wxid转化成昵称
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ ''''''
+ pattern = re.compile(r"(.*?))]]>")
+ result = pattern.search(content).groupdict()['it']
+ fromusername = '${fromusername@textstatusicon}'
+ pattedusername = '${pattedusername@textstatusicon}'
+ '''我拍别人'''
+ if result[0] == u'我':
+ result = ''.join(result.split(fromusername))
+ result = ''.join(result.split(pattedusername))
+ pat = result
+ else:
+ '''处理多余的引号'''
+ result = result.split('""') if '""' in result else result.split('"')
+ for i in range(len(result)):
+ if fromusername in result[i]:
+ result[i] = result[i].rstrip(fromusername)
+ elif pattedusername in result[i]:
+ result[i] = result[i].rstrip(pattedusername)
+
+ if len(result) >= 4:
+ '''别人拍别人
+ #! ""${s407575157}${fromusername@textstatusicon}"" \
+ #! 拍了拍 \
+ #! ""${wxid_7rs401fwlaje22}${pattedusername@textstatusicon}"" \
+ #! 的豪宅不小心塌了??
+ #! [' ', wxid0, '拍了拍', wxid1, '内容']
+ '''
+ wxid0 = result[1].lstrip('${').rstrip('}') # ! 第一个人的wxid
+ wxid1 = result[3].lstrip('${').rstrip('}') # ! 第二个人的wxid
+ nickname0 = rcontact.wxid_to_conRemark(wxid0) # ! 将wxid转换成昵称
+ nickname1 = rcontact.wxid_to_conRemark(wxid1) # ! 将wxid转换成昵称
+ pat = nickname0 + result[2] + nickname1 # todo 留着把wxid转换成昵称
+ if len(result) == 5:
+ pat += result[4]
+ else:
+ '''#! ""${wxid_8piw6sb4hvfm22}"" 拍了拍我 '''
+ '''
+ #! 别人拍我
+ #! [' ', wxid0, '拍了拍我']
+ '''
+ wxid0 = result[1].lstrip('${').rstrip('}')
+ pat = wxid0 + result[2]
+ print(pat)
+ p = doc.add_paragraph()
+ run = p.add_run(pat)
+ p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
+ '''设置拍一拍文字格式'''
+ run.font.color.rgb = shared.RGBColor(121, 121, 121)
+ run.font_size = shared.Inches(0.3)
+ # run.font.highlight_color=WD_COLOR_INDEX.GRAY_25
+
+ def video(self, doc, isSend, content, status, img_path):
+ print(content, img_path)
+
+ def to_docx(self, user_data, i, conRemark):
+ '''
+
+ :param user_data:
+ :param i:
+ :param conRemark:
+ :return:
+ '''
+ '''创建联系人目录'''
+ mkdir(f"{path}/{conRemark}")
+ filename = f"{path}/{conRemark}/{conRemark}{i}.docx"
+ doc = docx.Document()
+ now_timestamp = '1600008700000.0'
+ for row_index, row in user_data.iterrows():
+ self.num += 1
+ self.bar.emit(int((self.num)/self.total_num*100))
+ Type = row['type']
+ content = row['content']
+ isSend = row['isSend']
+ last_timestamp = now_timestamp
+ now_timestamp = row['createTime']
+ createTime = time_format(now_timestamp)
+ imgPath = row['imgPath']
+ status = row['status']
+ if IS_5_min(last_timestamp, now_timestamp):
+ doc.add_paragraph(createTime).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
+ if Type == 1:
+ # print('文本信息')
+ # print(createTime, content)
+ # continue
+ self.text(doc, isSend, content, status)
+ elif Type == 3:
+ # print('图片信息')
+ # continue
+ self.image(doc, isSend, 3, content, imgPath)
+ elif Type == 47:
+ # print(content)
+ print(imgPath,content)
+ self.emoji(doc, isSend, content, imgPath)
+ elif Type == 1090519089:
+ self.wx_file(doc, isSend, content, status)
+ elif Type == 268445456:
+ self.retract_message(doc, isSend, content, status)
+ elif Type == 822083633:
+ self.reply(doc, isSend, content, status)
+ elif Type == 922746929:
+ self.pat_a_pat(doc, isSend, content, status)
+ elif Type == 43:
+ # print(createTime)
+ self.video(doc, isSend, content, status, imgPath)
+ # doc.add_paragraph(str(i))
+ # print(filename)
+ doc.save(filename)
+ def run(self):
+ if 1:
+ # try:
+ self.get_avator()
+ conRemark = self.ta_info['conRemark']
+ self.self_text.emit(conRemark)
+ self.self_text.emit(path)
+ user_data = self.read_csv(conRemark)
+ l = len(user_data)
+ n = 50
+ threads = []
+ for i in range(n):
+ q = i * (l // n)
+ p = (i + 1) * (l // n)
+ if i == n - 1:
+ p = l
+ data = user_data[q:p]
+ self.to_docx(data, i, conRemark)
+ # # t = threading.Thread(target=self.to_docx, args=(data, i, conRemark))
+ # # threads.append(t)
+ # # for thr in threads:
+ # # thr.start()
+ # # thr.join()
+ self.self_text.emit('\n\n\n导出进度还差一点点!!!')
+ self.bar.emit(99)
+ merge_docx(conRemark, n)
+ self.self_text.emit(f'{conRemark}聊天记录导出成功!!!')
+ self.bar.emit(100)
+ # except Exception as e:
+ # self.self_text.emit('发生异常')
+ # print(e)
+ #self.self_text.emit(e)
+if __name__ == '__main__':
+ # # conRemark = '张三' #! 微信备注名
+ # n = 100 # ! 分割的文件个数
+ # main(conRemark, n)
+ # img_self.close()
+ # img_ta.close()
+ t = MyThread()
+ # t.ta_info = {
+ # 'wxid': 'wxid_q3ozn70pweud22',
+ # 'conRemark': '小钱'
+ # }
+ t.ta_info = {
+ 'wxid': 'wxid_8piw6sb4hvfm22',
+ 'conRemark': '曹雨萱'
+ }
+ #wxid_8piw6sb4hvfm22
+ t.self_info = {
+ 'wxid': 'wxid_27hqbq7vx5hf22',
+ 'conRemark': 'Shuaikang Zhou'
+ }
+ t.run()
diff --git a/app/Ui/__init__.py b/app/Ui/__init__.py
new file mode 100644
index 0000000..7a17873
--- /dev/null
+++ b/app/Ui/__init__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 14:19
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+#文件__init__.py
+# from login import login
+from . import mainview
+
+from .decrypt import decrypt
+__all__ = ["decrypt", 'mainview']
\ No newline at end of file
diff --git a/app/Ui/chat/__init__.py b/app/Ui/chat/__init__.py
new file mode 100644
index 0000000..fa6c268
--- /dev/null
+++ b/app/Ui/chat/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 20:33
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/Ui/chat/addContact/__init__.py b/app/Ui/chat/addContact/__init__.py
new file mode 100644
index 0000000..6d32454
--- /dev/null
+++ b/app/Ui/chat/addContact/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/24 10:34
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/Ui/chat/addContact/addContact.py b/app/Ui/chat/addContact/addContact.py
new file mode 100644
index 0000000..9f975e7
--- /dev/null
+++ b/app/Ui/chat/addContact/addContact.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+"""
+@File : addContact.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/17 14:26
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+from .addContactUi import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from ....DB import data
+import time
+
+
+class addControl(QWidget, Ui_Dialog):
+ backSignal = pyqtSignal(str)
+ contactSignal = pyqtSignal(tuple)
+ def __init__(self, username,parent=None):
+ super(addControl, self).__init__(parent)
+ self.setupUi(self)
+ self.tips.setVisible(False)
+ self.time.setVisible(False)
+ self.setWindowTitle('添加联系人')
+ self.setWindowIcon(QIcon('./data/icon.png'))
+ self.Username = username
+ # self.register_2.clicked.connect(self.login_)
+ self.back.clicked.connect(self.btnEnterClicked)
+ self.search.clicked.connect(self.search_user)
+ self.add_contact.clicked.connect(self.add_contact_)
+ self.avatar = None
+
+ def search_user(self):
+ username = self.username.text()
+ nickname = self.nickname.text()
+ print(username,nickname)
+ if data.searchUser(username):
+ imgpath = data.get_avator(username)
+ print(imgpath)
+ pixmap = QPixmap(imgpath).scaled(60, 60) # 按指定路径找到图片
+ self.avatar_img.setPixmap(pixmap) # 在label上显示图片
+ else:
+ self.error.setText('用户不存在')
+
+ def add_contact_(self):
+ username = self.username.text()
+ nickname = self.nickname.text()
+ flag = data.add_contact(self.Username,username, nickname)
+ if flag:
+ self.error.setText('添加成功')
+ self.contactSignal.emit(flag)
+ self.thread = MyThread() # 创建一个线程
+ self.thread.sec_changed_signal.connect(self._update) # 线程发过来的信号挂接到槽:update
+ self.thread.start()
+ else:
+ QMessageBox.critical(self, "错误", "用户不存在")
+
+
+ def _update(self, sec):
+ self.time.setProperty("value", float(sec))
+ # self.time.setDigitCount(sec)
+ # self.time.s
+ if sec == 0:
+ self.btnEnterClicked()
+
+ def btnEnterClicked(self):
+ print("退出添加联系人界面")
+ # 中间可以添加处理逻辑
+ self.backSignal.emit("back")
+ self.close()
+
+ def btnExitClicked(self):
+ print("Exit clicked")
+ self.close()
+
+
+class MyThread(QThread):
+ sec_changed_signal = pyqtSignal(int) # 信号类型:int
+
+ def __init__(self, sec=1000, parent=None):
+ super().__init__(parent)
+ self.sec = 2 # 默认1000秒
+
+ def run(self):
+ for i in range(self.sec, -1, -1):
+ self.sec_changed_signal.emit(i) # 发射信号
+ time.sleep(1)
diff --git a/app/Ui/chat/addContact/addContactUi.py b/app/Ui/chat/addContact/addContactUi.py
new file mode 100644
index 0000000..31149ce
--- /dev/null
+++ b/app/Ui/chat/addContact/addContactUi.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'addContactUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(400, 300)
+ self.label_3 = QtWidgets.QLabel(Dialog)
+ self.label_3.setGeometry(QtCore.QRect(120, 0, 221, 51))
+ font = QtGui.QFont()
+ font.setFamily("一纸情书")
+ font.setPointSize(20)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.error = QtWidgets.QLabel(Dialog)
+ self.error.setGeometry(QtCore.QRect(300, 70, 101, 16))
+ self.error.setAutoFillBackground(False)
+ self.error.setStyleSheet("color:red")
+ self.error.setText("")
+ self.error.setObjectName("error")
+ self.time = QtWidgets.QLCDNumber(Dialog)
+ self.time.setGeometry(QtCore.QRect(230, 230, 51, 61))
+ self.time.setLineWidth(5)
+ self.time.setDigitCount(1)
+ self.time.setProperty("value", 8.0)
+ self.time.setObjectName("time")
+ self.tips = QtWidgets.QLabel(Dialog)
+ self.tips.setGeometry(QtCore.QRect(140, 250, 61, 16))
+ self.tips.setObjectName("tips")
+ self.toolButton = QtWidgets.QToolButton(Dialog)
+ self.toolButton.setGeometry(QtCore.QRect(360, 0, 47, 21))
+ self.toolButton.setObjectName("toolButton")
+ self.back = QtWidgets.QPushButton(Dialog)
+ self.back.setGeometry(QtCore.QRect(10, 10, 41, 28))
+ self.back.setObjectName("back")
+ self.avatar_img = QtWidgets.QLabel(Dialog)
+ self.avatar_img.setGeometry(QtCore.QRect(310, 130, 80, 80))
+ self.avatar_img.setObjectName("avatar_img")
+ self.layoutWidget = QtWidgets.QWidget(Dialog)
+ self.layoutWidget.setGeometry(QtCore.QRect(71, 63, 227, 155))
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.label_2 = QtWidgets.QLabel(self.layoutWidget)
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout_2.addWidget(self.label_2)
+ self.username = QtWidgets.QLineEdit(self.layoutWidget)
+ self.username.setObjectName("username")
+ self.horizontalLayout_2.addWidget(self.username)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.label_4 = QtWidgets.QLabel(self.layoutWidget)
+ self.label_4.setMinimumSize(QtCore.QSize(38, 21))
+ self.label_4.setMaximumSize(QtCore.QSize(38, 21))
+ self.label_4.setObjectName("label_4")
+ self.horizontalLayout_4.addWidget(self.label_4)
+ self.nickname = QtWidgets.QLineEdit(self.layoutWidget)
+ self.nickname.setMinimumSize(QtCore.QSize(0, 0))
+ self.nickname.setMaximumSize(QtCore.QSize(100000, 10000))
+ self.nickname.setObjectName("nickname")
+ self.horizontalLayout_4.addWidget(self.nickname)
+ self.verticalLayout.addLayout(self.horizontalLayout_4)
+ self.search = QtWidgets.QPushButton(self.layoutWidget)
+ self.search.setObjectName("search")
+ self.verticalLayout.addWidget(self.search)
+ self.add_contact = QtWidgets.QPushButton(self.layoutWidget)
+ self.add_contact.setObjectName("add_contact")
+ self.verticalLayout.addWidget(self.add_contact)
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.label_3.setText(_translate("Dialog", "添加好友"))
+ self.tips.setText(_translate("Dialog", "即将返回"))
+ self.toolButton.setText(_translate("Dialog", "..."))
+ self.back.setText(_translate("Dialog", "返回"))
+ self.avatar_img.setText(_translate("Dialog", "+"))
+ self.label_2.setText(_translate("Dialog", "账号:"))
+ self.label_4.setText(_translate("Dialog", "备注:"))
+ self.search.setText(_translate("Dialog", "查找"))
+ self.add_contact.setText(_translate("Dialog", "添加联系人"))
diff --git a/app/Ui/chat/addContact/addContactUi.ui b/app/Ui/chat/addContact/addContactUi.ui
new file mode 100644
index 0000000..207082d
--- /dev/null
+++ b/app/Ui/chat/addContact/addContactUi.ui
@@ -0,0 +1,207 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Dialog
+
+
+
+
+ 120
+ 0
+ 221
+ 51
+
+
+
+
+ 一纸情书
+ 20
+
+
+
+ 添加好友
+
+
+
+
+
+ 300
+ 70
+ 101
+ 16
+
+
+
+ false
+
+
+ color:red
+
+
+
+
+
+
+
+
+ 230
+ 230
+ 51
+ 61
+
+
+
+ 5
+
+
+ 1
+
+
+ 8.000000000000000
+
+
+
+
+
+ 140
+ 250
+ 61
+ 16
+
+
+
+ 即将返回
+
+
+
+
+
+ 360
+ 0
+ 47
+ 21
+
+
+
+ ...
+
+
+
+
+
+ 10
+ 10
+ 41
+ 28
+
+
+
+ 返回
+
+
+
+
+
+ 310
+ 130
+ 80
+ 80
+
+
+
+ +
+
+
+
+
+
+ 71
+ 63
+ 227
+ 155
+
+
+
+ -
+
+
-
+
+
+ 账号:
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+ 38
+ 21
+
+
+
+
+ 38
+ 21
+
+
+
+ 备注:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 100000
+ 10000
+
+
+
+
+
+
+ -
+
+
+ 查找
+
+
+
+ -
+
+
+ 添加联系人
+
+
+
+
+
+
+
+
+
diff --git a/app/Ui/chat/backup/chatUi - 副本.py b/app/Ui/chat/backup/chatUi - 副本.py
new file mode 100644
index 0000000..ee581fb
--- /dev/null
+++ b/app/Ui/chat/backup/chatUi - 副本.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'mainviewUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(1280, 720)
+ Dialog.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ Dialog.setAutoFillBackground(False)
+ self.sign_up = QtWidgets.QPushButton(Dialog)
+ self.sign_up.setGeometry(QtCore.QRect(680, 10, 93, 28))
+ self.sign_up.setObjectName("sign_up")
+ self.message = QtWidgets.QTextBrowser(Dialog)
+ self.message.setGeometry(QtCore.QRect(480, 50, 780, 520))
+ self.message.setObjectName("message")
+ self.textEdit = QtWidgets.QTextEdit(Dialog)
+ self.textEdit.setGeometry(QtCore.QRect(480, 600, 800, 120))
+ self.textEdit.setObjectName("textEdit")
+ self.toolButton = QtWidgets.QToolButton(Dialog)
+ self.toolButton.setGeometry(QtCore.QRect(1240, 0, 47, 51))
+ self.toolButton.setObjectName("toolButton")
+ self.btn_sendMsg = QtWidgets.QPushButton(Dialog)
+ self.btn_sendMsg.setGeometry(QtCore.QRect(1162, 670, 121, 51))
+ font = QtGui.QFont()
+ font.setFamily("黑体")
+ font.setPointSize(15)
+ font.setBold(False)
+ font.setWeight(50)
+ self.btn_sendMsg.setFont(font)
+ self.btn_sendMsg.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.btn_sendMsg.setMouseTracking(False)
+ self.btn_sendMsg.setAutoFillBackground(False)
+ self.btn_sendMsg.setStyleSheet("QPushButton {\n"
+" background-color: #f0f0f0;\n"
+" \n"
+" padding: 10px;\n"
+" color:rgb(5,180,104);\n"
+"}")
+ self.btn_sendMsg.setIconSize(QtCore.QSize(40, 40))
+ self.btn_sendMsg.setCheckable(False)
+ self.btn_sendMsg.setAutoDefault(True)
+ self.btn_sendMsg.setObjectName("btn_sendMsg")
+ self.line = QtWidgets.QFrame(Dialog)
+ self.line.setGeometry(QtCore.QRect(480, 570, 800, 3))
+ self.line.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line.setObjectName("line")
+ self.line_2 = QtWidgets.QFrame(Dialog)
+ self.line_2.setGeometry(QtCore.QRect(480, 50, 800, 3))
+ self.line_2.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line_2.setObjectName("line_2")
+ self.line_3 = QtWidgets.QFrame(Dialog)
+ self.line_3.setGeometry(QtCore.QRect(480, 0, 3, 720))
+ self.line_3.setFrameShape(QtWidgets.QFrame.VLine)
+ self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line_3.setObjectName("line_3")
+ self.verticalScrollBar = QtWidgets.QScrollBar(Dialog)
+ self.verticalScrollBar.setGeometry(QtCore.QRect(460, 50, 16, 671))
+ self.verticalScrollBar.setOrientation(QtCore.Qt.Vertical)
+ self.verticalScrollBar.setObjectName("verticalScrollBar")
+ self.verticalScrollBar_2 = QtWidgets.QScrollBar(Dialog)
+ self.verticalScrollBar_2.setGeometry(QtCore.QRect(1260, 50, 16, 520))
+ self.verticalScrollBar_2.setStyleSheet("background-color: #f0f0f0;")
+ self.verticalScrollBar_2.setOrientation(QtCore.Qt.Vertical)
+ self.verticalScrollBar_2.setObjectName("verticalScrollBar_2")
+ self.pushButton_2 = QtWidgets.QPushButton(Dialog)
+ self.pushButton_2.setGeometry(QtCore.QRect(160, 50, 300, 80))
+ self.pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.pushButton_2.setAutoFillBackground(False)
+ self.pushButton_2.setText("")
+ self.pushButton_2.setIconSize(QtCore.QSize(80, 80))
+ self.pushButton_2.setObjectName("pushButton_2")
+ self.verticalLayoutWidget = QtWidgets.QWidget(Dialog)
+ self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 160, 111, 562))
+ self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_2.setSpacing(0)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.btn_chat = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_chat.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_chat.setObjectName("btn_chat")
+ self.verticalLayout_2.addWidget(self.btn_chat)
+ self.btn_contact = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_contact.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_contact.setObjectName("btn_contact")
+ self.verticalLayout_2.addWidget(self.btn_contact)
+ self.btn_addC = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_addC.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_addC.setObjectName("btn_addC")
+ self.verticalLayout_2.addWidget(self.btn_addC)
+ self.btn_delC = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_delC.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_delC.setObjectName("btn_delC")
+ self.verticalLayout_2.addWidget(self.btn_delC)
+ self.btn_create_group = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_create_group.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_create_group.setObjectName("btn_create_group")
+ self.verticalLayout_2.addWidget(self.btn_create_group)
+ self.btn_add_group = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_add_group.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_add_group.setObjectName("btn_add_group")
+ self.verticalLayout_2.addWidget(self.btn_add_group)
+ self.pushButton_6 = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.pushButton_6.setMinimumSize(QtCore.QSize(100, 80))
+ self.pushButton_6.setObjectName("pushButton_6")
+ self.verticalLayout_2.addWidget(self.pushButton_6)
+ self.verticalLayout_2.setStretch(0, 1)
+ self.verticalLayout_2.setStretch(2, 1)
+ self.verticalLayout_2.setStretch(3, 1)
+ self.verticalLayout_2.setStretch(6, 1)
+ self.myavatar = QtWidgets.QLabel(Dialog)
+ self.myavatar.setGeometry(QtCore.QRect(10, 30, 100, 100))
+ self.myavatar.setObjectName("myavatar")
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.sign_up.setText(_translate("Dialog", "退出登录"))
+ self.toolButton.setText(_translate("Dialog", "..."))
+ self.btn_sendMsg.setText(_translate("Dialog", "发送"))
+ self.btn_chat.setText(_translate("Dialog", "聊天"))
+ self.btn_contact.setText(_translate("Dialog", "联系人"))
+ self.btn_addC.setText(_translate("Dialog", "添加联系人"))
+ self.btn_delC.setText(_translate("Dialog", "删除联系人"))
+ self.btn_create_group.setText(_translate("Dialog", "建群"))
+ self.btn_add_group.setText(_translate("Dialog", "加群"))
+ self.pushButton_6.setText(_translate("Dialog", "设置"))
+ self.myavatar.setText(_translate("Dialog", "avatar"))
diff --git a/app/Ui/chat/backup/chatUi - 副本.ui b/app/Ui/chat/backup/chatUi - 副本.ui
new file mode 100644
index 0000000..705a441
--- /dev/null
+++ b/app/Ui/chat/backup/chatUi - 副本.ui
@@ -0,0 +1,845 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 1280
+ 720
+
+
+
+ ArrowCursor
+
+
+ Dialog
+
+
+ false
+
+
+
+
+ 580
+ 570
+ 93
+ 28
+
+
+
+ PushButton
+
+
+
+
+
+ 480
+ 50
+ 800
+ 520
+
+
+
+
+
+
+ 480
+ 600
+ 800
+ 120
+
+
+
+
+
+
+ 1240
+ 0
+ 47
+ 51
+
+
+
+ ...
+
+
+
+
+
+ 1162
+ 670
+ 121
+ 51
+
+
+
+
+ 黑体
+ 15
+ 50
+ false
+
+
+
+ ArrowCursor
+
+
+ false
+
+
+ false
+
+
+ QPushButton {
+ background-color: #f0f0f0;
+ border: 1px solid #dcdfe6;
+ padding: 10px;
+ color:rgb(5,180,104);
+}
+
+
+ 发送
+
+
+
+ 40
+ 40
+
+
+
+ false
+
+
+ true
+
+
+
+
+
+ 480
+ 570
+ 800
+ 3
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 480
+ 50
+ 800
+ 3
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 480
+ 0
+ 3
+ 720
+
+
+
+ Qt::Vertical
+
+
+
+
+
+ 40
+ 370
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 40
+ 290
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 40
+ 210
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 40
+ 130
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 40
+ 450
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 40
+ 530
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 40
+ 610
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+ 460
+ 50
+ 16
+ 671
+
+
+
+ Qt::Vertical
+
+
+
+
+
+ 1260
+ 50
+ 16
+ 520
+
+
+
+ background-color: #f0f0f0;
+
+
+ Qt::Vertical
+
+
+
+
+
+ 40
+ 50
+ 411
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+ -
+
+
+
+ 10
+
+
+
+ Qt::RightToLeft
+
+
+ 昨天
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 司小远
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+
+
+
+
+
+
diff --git a/app/Ui/chat/backup/chatUi.py b/app/Ui/chat/backup/chatUi.py
new file mode 100644
index 0000000..164372b
--- /dev/null
+++ b/app/Ui/chat/backup/chatUi.py
@@ -0,0 +1,184 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'chatUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(1280, 720)
+ Dialog.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ Dialog.setAutoFillBackground(False)
+ self.pushButton = QtWidgets.QPushButton(Dialog)
+ self.pushButton.setGeometry(QtCore.QRect(580, 570, 93, 28))
+ self.pushButton.setObjectName("pushButton")
+ self.textBrowser = QtWidgets.QTextBrowser(Dialog)
+ self.textBrowser.setGeometry(QtCore.QRect(480, 50, 800, 520))
+ self.textBrowser.setObjectName("textBrowser")
+ self.textEdit = QtWidgets.QTextEdit(Dialog)
+ self.textEdit.setGeometry(QtCore.QRect(480, 600, 800, 120))
+ self.textEdit.setObjectName("textEdit")
+ self.toolButton = QtWidgets.QToolButton(Dialog)
+ self.toolButton.setGeometry(QtCore.QRect(1240, 0, 47, 51))
+ self.toolButton.setObjectName("toolButton")
+ self.sendMsg = QtWidgets.QPushButton(Dialog)
+ self.sendMsg.setGeometry(QtCore.QRect(1162, 670, 121, 51))
+ font = QtGui.QFont()
+ font.setFamily("黑体")
+ font.setPointSize(15)
+ font.setBold(False)
+ font.setWeight(50)
+ self.sendMsg.setFont(font)
+ self.sendMsg.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.sendMsg.setMouseTracking(False)
+ self.sendMsg.setAutoFillBackground(False)
+ self.sendMsg.setStyleSheet("QPushButton {\n"
+" background-color: #f0f0f0;\n"
+" \n"
+" padding: 10px;\n"
+" color:rgb(5,180,104);\n"
+"}")
+ self.sendMsg.setIconSize(QtCore.QSize(40, 40))
+ self.sendMsg.setCheckable(False)
+ self.sendMsg.setAutoDefault(True)
+ self.sendMsg.setObjectName("sendMsg")
+ self.line = QtWidgets.QFrame(Dialog)
+ self.line.setGeometry(QtCore.QRect(480, 570, 800, 3))
+ self.line.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line.setObjectName("line")
+ self.line_2 = QtWidgets.QFrame(Dialog)
+ self.line_2.setGeometry(QtCore.QRect(480, 50, 800, 3))
+ self.line_2.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line_2.setObjectName("line_2")
+ self.line_3 = QtWidgets.QFrame(Dialog)
+ self.line_3.setGeometry(QtCore.QRect(480, 0, 3, 720))
+ self.line_3.setFrameShape(QtWidgets.QFrame.VLine)
+ self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line_3.setObjectName("line_3")
+ self.verticalScrollBar = QtWidgets.QScrollBar(Dialog)
+ self.verticalScrollBar.setGeometry(QtCore.QRect(460, 50, 16, 671))
+ self.verticalScrollBar.setOrientation(QtCore.Qt.Vertical)
+ self.verticalScrollBar.setObjectName("verticalScrollBar")
+ self.verticalScrollBar_2 = QtWidgets.QScrollBar(Dialog)
+ self.verticalScrollBar_2.setGeometry(QtCore.QRect(1260, 50, 16, 520))
+ self.verticalScrollBar_2.setStyleSheet("background-color: #f0f0f0;")
+ self.verticalScrollBar_2.setOrientation(QtCore.Qt.Vertical)
+ self.verticalScrollBar_2.setObjectName("verticalScrollBar_2")
+ self.pushButton_2 = QtWidgets.QPushButton(Dialog)
+ self.pushButton_2.setGeometry(QtCore.QRect(160, 50, 300, 80))
+ self.pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.pushButton_2.setAutoFillBackground(False)
+ self.pushButton_2.setText("")
+ self.pushButton_2.setIconSize(QtCore.QSize(80, 80))
+ self.pushButton_2.setObjectName("pushButton_2")
+ self.layoutWidget = QtWidgets.QWidget(Dialog)
+ self.layoutWidget.setGeometry(QtCore.QRect(150, 200, 318, 81))
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.gridLayout1 = QtWidgets.QGridLayout(self.layoutWidget)
+ self.gridLayout1.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
+ self.gridLayout1.setContentsMargins(10, 10, 10, 10)
+ self.gridLayout1.setSpacing(10)
+ self.gridLayout1.setObjectName("gridLayout1")
+ self.label_time = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.label_time.setFont(font)
+ self.label_time.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.label_time.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.label_time.setObjectName("label_time")
+ self.gridLayout1.addWidget(self.label_time, 0, 2, 1, 1)
+ self.label_remark = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setFamily("Adobe 黑体 Std R")
+ font.setPointSize(10)
+ font.setBold(False)
+ font.setWeight(50)
+ self.label_remark.setFont(font)
+ self.label_remark.setObjectName("label_remark")
+ self.gridLayout1.addWidget(self.label_remark, 0, 1, 1, 1)
+ self.label_msg = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.label_msg.setFont(font)
+ self.label_msg.setObjectName("label_msg")
+ self.gridLayout1.addWidget(self.label_msg, 1, 1, 1, 2)
+ self.label_avatar = QtWidgets.QLabel(self.layoutWidget)
+ self.label_avatar.setMinimumSize(QtCore.QSize(60, 60))
+ self.label_avatar.setMaximumSize(QtCore.QSize(60, 60))
+ self.label_avatar.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.label_avatar.setAutoFillBackground(False)
+ self.label_avatar.setStyleSheet("background-color: #ffffff;")
+ self.label_avatar.setInputMethodHints(QtCore.Qt.ImhNone)
+ self.label_avatar.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.label_avatar.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.label_avatar.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
+ self.label_avatar.setObjectName("label_avatar")
+ self.gridLayout1.addWidget(self.label_avatar, 0, 0, 2, 1)
+ self.gridLayout1.setColumnStretch(0, 1)
+ self.gridLayout1.setColumnStretch(1, 6)
+ self.gridLayout1.setRowStretch(0, 5)
+ self.gridLayout1.setRowStretch(1, 3)
+ self.verticalLayoutWidget = QtWidgets.QWidget(Dialog)
+ self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 160, 111, 402))
+ self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_2.setSpacing(0)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.btn_msg = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_msg.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_msg.setObjectName("btn_msg")
+ self.verticalLayout_2.addWidget(self.btn_msg)
+ self.btn_contact = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_contact.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_contact.setObjectName("btn_contact")
+ self.verticalLayout_2.addWidget(self.btn_contact)
+ self.btn_addC = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_addC.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_addC.setObjectName("btn_addC")
+ self.verticalLayout_2.addWidget(self.btn_addC)
+ self.btn_delC = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_delC.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_delC.setObjectName("btn_delC")
+ self.verticalLayout_2.addWidget(self.btn_delC)
+ self.pushButton_6 = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.pushButton_6.setMinimumSize(QtCore.QSize(100, 80))
+ self.pushButton_6.setObjectName("pushButton_6")
+ self.verticalLayout_2.addWidget(self.pushButton_6)
+ self.verticalLayout_2.setStretch(0, 1)
+ self.verticalLayout_2.setStretch(2, 1)
+ self.verticalLayout_2.setStretch(3, 1)
+ self.verticalLayout_2.setStretch(4, 1)
+ self.myavatar = QtWidgets.QLabel(Dialog)
+ self.myavatar.setGeometry(QtCore.QRect(10, 30, 100, 100))
+ self.myavatar.setObjectName("myavatar")
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.pushButton.setText(_translate("Dialog", "PushButton"))
+ self.toolButton.setText(_translate("Dialog", "..."))
+ self.sendMsg.setText(_translate("Dialog", "发送"))
+ self.label_time.setText(_translate("Dialog", "22/12/17"))
+ self.label_remark.setText(_translate("Dialog", "西北工业大学财务处"))
+ self.label_msg.setText(_translate("Dialog", "我没去"))
+ self.label_avatar.setText(_translate("Dialog", "TextLabel"))
+ self.btn_msg.setText(_translate("Dialog", "聊天"))
+ self.btn_contact.setText(_translate("Dialog", "联系人"))
+ self.btn_addC.setText(_translate("Dialog", "添加联系人"))
+ self.btn_delC.setText(_translate("Dialog", "删除联系人"))
+ self.pushButton_6.setText(_translate("Dialog", "设置"))
+ self.myavatar.setText(_translate("Dialog", "avatar"))
diff --git a/app/Ui/chat/backup/chatUi.ui b/app/Ui/chat/backup/chatUi.ui
new file mode 100644
index 0000000..227ff01
--- /dev/null
+++ b/app/Ui/chat/backup/chatUi.ui
@@ -0,0 +1,422 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 1280
+ 720
+
+
+
+ ArrowCursor
+
+
+ Dialog
+
+
+ false
+
+
+
+
+ 580
+ 570
+ 93
+ 28
+
+
+
+ PushButton
+
+
+
+
+
+ 480
+ 50
+ 800
+ 520
+
+
+
+
+
+
+ 480
+ 600
+ 800
+ 120
+
+
+
+
+
+
+ 1240
+ 0
+ 47
+ 51
+
+
+
+ ...
+
+
+
+
+
+ 1162
+ 670
+ 121
+ 51
+
+
+
+
+ 黑体
+ 15
+ 50
+ false
+
+
+
+ ArrowCursor
+
+
+ false
+
+
+ false
+
+
+ QPushButton {
+ background-color: #f0f0f0;
+
+ padding: 10px;
+ color:rgb(5,180,104);
+}
+
+
+ 发送
+
+
+
+ 40
+ 40
+
+
+
+ false
+
+
+ true
+
+
+
+
+
+ 480
+ 570
+ 800
+ 3
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 480
+ 50
+ 800
+ 3
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 480
+ 0
+ 3
+ 720
+
+
+
+ Qt::Vertical
+
+
+
+
+
+ 460
+ 50
+ 16
+ 671
+
+
+
+ Qt::Vertical
+
+
+
+
+
+ 1260
+ 50
+ 16
+ 520
+
+
+
+ background-color: #f0f0f0;
+
+
+ Qt::Vertical
+
+
+
+
+
+ 160
+ 50
+ 300
+ 80
+
+
+
+ Qt::LeftToRight
+
+
+ false
+
+
+
+
+
+
+ 80
+ 80
+
+
+
+
+
+
+ 150
+ 200
+ 318
+ 81
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 10
+
+
+ 10
+
+
+ 10
+
+
+ 10
+
+
+ 10
+
+ -
+
+
+
+ 8
+
+
+
+ Qt::RightToLeft
+
+
+ 22/12/17
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ Adobe 黑体 Std R
+ 10
+ 50
+ false
+
+
+
+ 西北工业大学财务处
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ 我没去
+
+
+
+ -
+
+
+
+ 60
+ 60
+
+
+
+
+ 60
+ 60
+
+
+
+ Qt::RightToLeft
+
+
+ false
+
+
+ background-color: #ffffff;
+
+
+ Qt::ImhNone
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ TextLabel
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+
+
+
+
+
+ 0
+ 160
+ 111
+ 402
+
+
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ 聊天
+
+
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ 联系人
+
+
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ 添加联系人
+
+
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ 删除联系人
+
+
+
+ -
+
+
+
+ 100
+ 80
+
+
+
+ 设置
+
+
+
+
+
+
+
+
+ 10
+ 30
+ 100
+ 100
+
+
+
+ avatar
+
+
+
+
+
+
diff --git a/app/Ui/chat/chat - 副本.py b/app/Ui/chat/chat - 副本.py
new file mode 100644
index 0000000..bf6b5da
--- /dev/null
+++ b/app/Ui/chat/chat - 副本.py
@@ -0,0 +1,428 @@
+# -*- coding: utf-8 -*-
+"""
+@File : mainview.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 15:07
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import os.path
+import socket # 导入socket模块
+import datetime
+import json
+import time
+
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from .chatUi import *
+from ...DataBase import data
+
+
+class ChatController(QWidget, Ui_Dialog):
+ exitSignal = pyqtSignal()
+
+ # username = ''
+
+ def __init__(self, Me, parent=None):
+ super(ChatController, self).__init__(parent)
+ self.ta_avatar = None
+ self.setupUi(self)
+ self.setWindowTitle('WeChat')
+ self.setWindowIcon(QIcon('./app/data/icon.png'))
+ self.initui()
+ self.Me = Me
+ self.Thread = ChatMsg(self.Me.username, None)
+ self.Thread.isSend_signal.connect(self.showMsg)
+ self.Thread.okSignal.connect(self.setScrollBarPos)
+ self.contacts = {}
+ self.last_btn = None
+ self.chat_flag = True
+ # self.showChat()
+ self.message.verticalScrollBar().valueChanged.connect(self.textbrower_verticalScrollBar)
+ self.show_flag = False
+ self.ta_username = None
+ self.last_pos = 0
+ self.last_msg_time = 0 # 上次信息的时间
+ self.last_talkerId = None
+ self.now_talkerId = None
+ self.showChat()
+
+ def initui(self):
+ self.textEdit = myTextEdit(self.frame)
+ self.textEdit.setGeometry(QtCore.QRect(9, 580, 821, 141))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.textEdit.setFont(font)
+ self.textEdit.setTabStopWidth(80)
+ self.textEdit.setCursorWidth(1)
+ self.textEdit.setObjectName("textEdit")
+ self.textEdit.sendSignal.connect(self.sendMsg)
+ self.btn_sendMsg = QtWidgets.QPushButton(self.frame)
+ self.btn_sendMsg.setGeometry(QtCore.QRect(680, 670, 121, 51))
+ font = QtGui.QFont()
+ font.setFamily("黑体")
+ font.setPointSize(15)
+ font.setBold(False)
+ font.setWeight(50)
+ self.btn_sendMsg.setFont(font)
+ self.btn_sendMsg.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.btn_sendMsg.setMouseTracking(False)
+ self.btn_sendMsg.setAutoFillBackground(False)
+ self.btn_sendMsg.setStyleSheet("QPushButton {background-color: #f0f0f0;\n"
+ "padding: 10px;\n"
+ "color:rgb(5,180,104);}\n"
+ "QPushButton:hover{background-color: rgb(198,198,198)}\n"
+ )
+ self.btn_sendMsg.setIconSize(QtCore.QSize(40, 40))
+ self.btn_sendMsg.setCheckable(False)
+ self.btn_sendMsg.setAutoDefault(True)
+ self.btn_sendMsg.setObjectName("btn_sendMsg")
+ _translate = QtCore.QCoreApplication.translate
+ self.btn_sendMsg.setText(_translate("Dialog", "发送"))
+ self.btn_sendMsg.setToolTip('按Enter键发送,按Ctrl+Enter键换行')
+
+ def showChat(self):
+ """
+ 显示聊天界面
+ :return:
+ """
+ print('show')
+ if self.show_flag:
+ return
+ self.show_flag = True
+ rconversations = data.get_rconversation()
+ max_hight = max(len(rconversations) * 80, 680)
+ self.scrollAreaWidgetContents.setGeometry(
+ QtCore.QRect(0, 0, 300, max_hight))
+ for i in range(len(rconversations)):
+ rconversation = rconversations[i]
+ username = rconversation[1]
+ # print('联系人:', i, rconversation)
+ pushButton_2 = Contact(self.scrollAreaWidgetContents, i, rconversation)
+ pushButton_2.setGeometry(QtCore.QRect(0, 80 * i, 300, 80))
+ pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ pushButton_2.clicked.connect(pushButton_2.show_msg)
+ pushButton_2.usernameSingal.connect(self.Chat)
+ self.contacts[username] = pushButton_2
+
+ def Chat(self, talkerId):
+ """
+ 聊天界面 点击联系人头像时候显示聊天数据
+ :param talkerId:
+ :return:
+ """
+ self.now_talkerId = talkerId
+ # 把当前按钮设置为灰色
+ if self.last_talkerId and self.last_talkerId != talkerId:
+ print('对方账号:', self.last_talkerId)
+ self.contacts[self.last_talkerId].setStyleSheet(
+ "QPushButton {background-color: rgb(253,253,253);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n"
+ )
+ self.last_talkerId = talkerId
+ self.contacts[talkerId].setStyleSheet(
+ "QPushButton {background-color: rgb(198,198,198);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n"
+ )
+ conRemark = data.get_conRemark(talkerId)
+ self.label_remark.setText(conRemark)
+ self.message.clear()
+ self.message.append(talkerId)
+ self.ta_username = talkerId
+ self.ta_avatar = data.get_avator(talkerId)
+ self.textEdit.setFocus()
+ self.Thread.ta_u = talkerId
+ self.Thread.msg_id = 0
+ self.Thread.start()
+ # 创建新的线程用于显示聊天记录
+
+ def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
+ print("closed")
+ self.exitSignal.emit()
+ self.close()
+
+ def textbrower_verticalScrollBar(self, pos):
+ """
+ 滚动条到0之后自动更新聊天记录
+ :param pos:
+ :return:
+ """
+ # print(pos)
+ if pos > 0:
+ return
+ self.last_pos = self.message.verticalScrollBar().maximum()
+ self.Thread.start()
+
+ def setScrollBarPos(self, pos):
+ """
+ 将滚动条位置设置为上次看到的地方
+ :param pos:
+ :return:
+ """
+ pos = self.message.verticalScrollBar().maximum() - self.last_pos
+
+ print(pos)
+ self.message.verticalScrollBar().setValue(pos)
+
+ def sendMsg(self, msg):
+ pass
+
+ def check_time(self, msg_time):
+ """
+ 判断两次聊天时间是否大于五分钟
+ 超过五分钟就显示时间
+ :param msg_time:
+ :return:
+ """
+ dt = msg_time - self.last_msg_time
+ # print(msg_time)
+ if dt // 1000 >= 300:
+ s_l = time.localtime(msg_time / 1000)
+ ts = time.strftime("%Y-%m-%d %H:%M", s_l)
+ html = '''
+ ''' % (ts)
+ # print(html)
+ self.last_msg_time = msg_time
+ self.message.insertHtml(html)
+
+ def showMsg(self, message):
+ """
+ 显示聊天消息
+ :param message:
+ :return:
+ """
+ msgId = message[0]
+ # print(msgId, type(msgId))
+ self.message.moveCursor(self.message.textCursor().Start)
+ ta_username = message[7]
+ msgType = str(message[2])
+ isSend = message[4]
+ content = message[8]
+ imgPath = message[9]
+ msg_time = message[6]
+ self.check_time(msg_time)
+
+ if msgType == '1':
+ self.show_text(isSend, content)
+ elif msgType == '3':
+ self.show_img(isSend,imgPath)
+ # self.message.moveCursor(self.message.textCursor().End)
+
+ def show_img(self, isSend, imgPath):
+ 'THUMBNAIL_DIRPATH://th_29cd0f0ca87652943be9ede365aabeaa'
+ imgPath = imgPath.split('th_')[1]
+ imgPath = f'./app/data/image2/{imgPath[0:2]}/{imgPath[2:4]}/th_{imgPath}'
+ html = '''
+  |
+ ''' % imgPath
+ style = 'vertical-align: top'
+ if isSend:
+ self.right(html,style=style)
+ else:
+ self.left(html,style=style)
+
+ def show_text(self, isSend, content):
+
+ if isSend:
+ html = '''%s : | ''' % (content)
+ self.right(html)
+ else:
+ html = ''': %s | ''' % (content)
+ self.left(html)
+
+ def right(self, content,style='vertical-align: middle'):
+ html = '''
+
+
+
+
+ %s
+  |
+ |
+
+
+
+
+ ''' % (style,content, self.Me.my_avatar)
+ # print('总的HTML')
+ # print(html)
+ self.message.insertHtml(html)
+
+ def left(self, content,style = 'vertical-align: middle'):
+ html = '''
+
+
+
+
+ |
+  |
+ %s
+
+
+
+
+ ''' % (style,self.ta_avatar, content)
+ self.message.insertHtml(html)
+
+ def destroy_me(self):
+ """注销账户"""
+ pass
+
+
+class Contact(QtWidgets.QPushButton):
+ """
+ 联系人类,继承自pyqt的按钮,里面封装了联系人头像等标签
+ """
+ usernameSingal = pyqtSignal(str)
+
+ def __init__(self, Ui, id=None, contact=None):
+ super(Contact, self).__init__(Ui)
+ self.layoutWidget = QtWidgets.QWidget(Ui)
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.gridLayout1 = QtWidgets.QGridLayout(self.layoutWidget)
+ self.gridLayout1.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
+ self.gridLayout1.setContentsMargins(10, 10, 10, 10)
+ self.gridLayout1.setSpacing(10)
+ self.gridLayout1.setObjectName("gridLayout1")
+ self.label_time = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.label_time.setFont(font)
+ self.label_time.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.label_time.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
+ self.label_time.setObjectName("label_time")
+ self.gridLayout1.addWidget(self.label_time, 0, 2, 1, 1)
+ self.label_remark = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setFamily("Adobe 黑体 Std R")
+ font.setPointSize(10)
+ self.label_remark.setFont(font)
+ self.label_remark.setObjectName("label_remark")
+ self.gridLayout1.addWidget(self.label_remark, 0, 1, 1, 1)
+ self.label_msg = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.label_msg.setFont(font)
+ self.label_msg.setObjectName("label_msg")
+ self.gridLayout1.addWidget(self.label_msg, 1, 1, 1, 2)
+ self.label_avatar = QtWidgets.QLabel(self.layoutWidget)
+ self.label_avatar.setMinimumSize(QtCore.QSize(60, 60))
+ self.label_avatar.setMaximumSize(QtCore.QSize(60, 60))
+ self.label_avatar.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.label_avatar.setAutoFillBackground(False)
+ self.label_avatar.setStyleSheet("background-color: #ffffff;")
+ self.label_avatar.setInputMethodHints(QtCore.Qt.ImhNone)
+ self.label_avatar.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.label_avatar.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.label_avatar.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+ self.label_avatar.setObjectName("label_avatar")
+ self.gridLayout1.addWidget(self.label_avatar, 0, 0, 2, 1)
+ self.gridLayout1.setColumnStretch(0, 1)
+ self.gridLayout1.setColumnStretch(1, 6)
+ self.gridLayout1.setRowStretch(0, 5)
+ self.gridLayout1.setRowStretch(1, 3)
+ self.setLayout(self.gridLayout1)
+ self.setStyleSheet(
+ "QPushButton {background-color: rgb(253,253,253);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n"
+ )
+ self.msgCount = contact[0]
+ self.username = contact[1]
+ self.conversationTime = contact[6]
+ self.msgType = contact[7]
+ self.digest = contact[8]
+ hasTrunc = contact[10]
+ attrflag = contact[11]
+ if hasTrunc == 0:
+ if attrflag == 0:
+ self.digest = '[动画表情]'
+ elif attrflag == 67108864:
+ try:
+ remark = data.get_conRemark(contact[9])
+ msg = self.digest.split(':')[1].strip('\n').strip()
+ self.digest = f'{remark}:{msg}'
+ except Exception as e:
+ # print(self.digest)
+ # print(e)
+ pass
+ else:
+ pass
+ self.show_info(id)
+
+ def show_info(self, id):
+ avatar = data.get_avator(self.username)
+ # print(avatar)
+ remark = data.get_conRemark(self.username)
+ time = datetime.datetime.now().strftime("%m-%d %H:%M")
+ msg = '还没说话'
+ pixmap = QPixmap(avatar).scaled(60, 60) # 按指定路径找到图片
+ self.label_avatar.setPixmap(pixmap) # 在label上显示图片
+ self.label_remark.setText(remark)
+ self.label_msg.setText(self.digest)
+ self.label_time.setText(data.timestamp2str(self.conversationTime)[2:])
+
+ def show_msg(self):
+ self.usernameSingal.emit(self.username)
+
+
+class ChatMsg(QThread):
+ """
+ 发送信息线程
+ """
+ isSend_signal = pyqtSignal(tuple)
+ okSignal = pyqtSignal(int)
+
+ def __init__(self, my_u, ta_u, parent=None):
+ super().__init__(parent)
+ self.sec = 2 # 默认1000秒
+ self.my_u = my_u
+ self.ta_u = ta_u
+ self.my_avatar = data.get_avator(my_u)
+ self.msg_id = 0
+
+ def run(self):
+ self.ta_avatar = data.get_avator(self.ta_u)
+ messages = data.get_message(self.ta_u, self.msg_id)
+ # messages.reverse()
+ for message in messages:
+ self.isSend_signal.emit(message)
+ self.msg_id += 1
+ self.okSignal.emit(1)
+
+
+class myTextEdit(QtWidgets.QTextEdit): # 继承 原本组件
+ sendSignal = pyqtSignal(str)
+
+ def __init__(self, parent):
+ QtWidgets.QTextEdit.__init__(self, parent)
+ self.parent = parent
+ _translate = QtCore.QCoreApplication.translate
+ self.setHtml(_translate("Dialog",
+ "\n"
+ "\n"
+ "
"))
+
+ def keyPressEvent(self, event):
+ QtWidgets.QTextEdit.keyPressEvent(self, event)
+ if event.key() == Qt.Key_Return: # 如果是Enter 按钮
+ modifiers = event.modifiers()
+ if modifiers == Qt.ControlModifier:
+ print('success press ctrl+enter key', self.toPlainText())
+ self.append('\0')
+ return
+ self.sendSignal.emit(self.toPlainText())
+ print('success press enter key', self.toPlainText())
+
+ # if modifiers == (Qt.ControlModifier) and event.key() == Qt.Key_Return:
+ # self.sendSignal.emit(self.toPlainText())
+ # print('success press enter key', self.toPlainText())
diff --git a/app/Ui/chat/chat.py b/app/Ui/chat/chat.py
new file mode 100644
index 0000000..2721174
--- /dev/null
+++ b/app/Ui/chat/chat.py
@@ -0,0 +1,459 @@
+# -*- coding: utf-8 -*-
+"""
+@File : mainview.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 15:07
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import os.path
+import socket # 导入socket模块
+import datetime
+import json
+import time
+from PIL import Image
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from .chatUi import *
+from ...DataBase import data
+
+
+class ChatController(QWidget, Ui_Dialog):
+ exitSignal = pyqtSignal()
+
+ # username = ''
+
+ def __init__(self, Me, parent=None):
+ super(ChatController, self).__init__(parent)
+ self.ta_avatar = None
+ self.setupUi(self)
+ self.setWindowTitle('WeChat')
+ self.setWindowIcon(QIcon('./app/data/icon.png'))
+ self.initui()
+ self.Me = Me
+ self.Thread = ChatMsg(self.Me.username, None)
+ self.Thread.isSend_signal.connect(self.showMsg)
+ self.Thread.okSignal.connect(self.setScrollBarPos)
+ self.contacts = {}
+ self.last_btn = None
+ self.chat_flag = True
+ # self.showChat()
+ self.message.verticalScrollBar().valueChanged.connect(self.textbrower_verticalScrollBar)
+ self.show_flag = False
+ self.ta_username = None
+ self.last_pos = 0
+ self.last_msg_time = 0 # 上次信息的时间
+ self.last_talkerId = None
+ self.now_talkerId = None
+ self.showChat()
+
+ def initui(self):
+ with open('./wechat.html', 'r', encoding='utf-8') as f:
+ self.message.setHtml(f.read())
+ self.textEdit = myTextEdit(self.frame)
+ self.textEdit.setGeometry(QtCore.QRect(9, 580, 821, 141))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.textEdit.setFont(font)
+ self.textEdit.setTabStopWidth(80)
+ self.textEdit.setCursorWidth(1)
+ self.textEdit.setObjectName("textEdit")
+ self.textEdit.sendSignal.connect(self.sendMsg)
+ self.btn_sendMsg = QtWidgets.QPushButton(self.frame)
+ self.btn_sendMsg.setGeometry(QtCore.QRect(680, 670, 121, 51))
+ font = QtGui.QFont()
+ font.setFamily("黑体")
+ font.setPointSize(15)
+ font.setBold(False)
+ font.setWeight(50)
+ self.btn_sendMsg.setFont(font)
+ self.btn_sendMsg.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.btn_sendMsg.setMouseTracking(False)
+ self.btn_sendMsg.setAutoFillBackground(False)
+ self.btn_sendMsg.setStyleSheet("QPushButton {background-color: #f0f0f0;\n"
+ "padding: 10px;\n"
+ "color:rgb(5,180,104);}\n"
+ "QPushButton:hover{background-color: rgb(198,198,198)}\n"
+ )
+ self.btn_sendMsg.setIconSize(QtCore.QSize(40, 40))
+ self.btn_sendMsg.setCheckable(False)
+ self.btn_sendMsg.setAutoDefault(True)
+ self.btn_sendMsg.setObjectName("btn_sendMsg")
+ _translate = QtCore.QCoreApplication.translate
+ self.btn_sendMsg.setText(_translate("Dialog", "发送"))
+ self.btn_sendMsg.setToolTip('按Enter键发送,按Ctrl+Enter键换行')
+
+ def showChat(self):
+ """
+ 显示聊天界面
+ :return:
+ """
+ print('show')
+ if self.show_flag:
+ return
+ self.show_flag = True
+ rconversations = data.get_rconversation()
+ max_hight = max(len(rconversations) * 80, 680)
+ self.scrollAreaWidgetContents.setGeometry(
+ QtCore.QRect(0, 0, 300, max_hight))
+ for i in range(len(rconversations)):
+ rconversation = rconversations[i]
+ username = rconversation[1]
+ # print('联系人:', i, rconversation)
+ pushButton_2 = Contact(self.scrollAreaWidgetContents, i, rconversation)
+ pushButton_2.setGeometry(QtCore.QRect(0, 80 * i, 300, 80))
+ pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ pushButton_2.clicked.connect(pushButton_2.show_msg)
+ pushButton_2.usernameSingal.connect(self.Chat)
+ self.contacts[username] = pushButton_2
+
+ def Chat(self, talkerId):
+ """
+ 聊天界面 点击联系人头像时候显示聊天数据
+ :param talkerId:
+ :return:
+ """
+ self.now_talkerId = talkerId
+ # 把当前按钮设置为灰色
+ if self.last_talkerId and self.last_talkerId != talkerId:
+ print('对方账号:', self.last_talkerId)
+ self.contacts[self.last_talkerId].setStyleSheet(
+ "QPushButton {background-color: rgb(253,253,253);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n"
+ )
+ self.last_talkerId = talkerId
+ self.contacts[talkerId].setStyleSheet(
+ "QPushButton {background-color: rgb(198,198,198);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n"
+ )
+ conRemark = data.get_conRemark(talkerId)
+ self.label_remark.setText(conRemark)
+ self.message.clear()
+ self.message.append(talkerId)
+ self.ta_username = talkerId
+ self.ta_avatar = data.get_avator(talkerId)
+ self.textEdit.setFocus()
+ self.Thread.ta_u = talkerId
+ self.Thread.msg_id = 0
+ self.Thread.start()
+ # 创建新的线程用于显示聊天记录
+
+ def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
+ print("closed")
+ self.exitSignal.emit()
+ self.close()
+
+ def textbrower_verticalScrollBar(self, pos):
+ """
+ 滚动条到0之后自动更新聊天记录
+ :param pos:
+ :return:
+ """
+ # print(pos)
+ if pos > 0:
+ return
+ self.last_pos = self.message.verticalScrollBar().maximum()
+ self.Thread.start()
+
+ def setScrollBarPos(self, pos):
+ """
+ 将滚动条位置设置为上次看到的地方
+ :param pos:
+ :return:
+ """
+ pos = self.message.verticalScrollBar().maximum() - self.last_pos
+
+ print(pos)
+ self.message.verticalScrollBar().setValue(pos)
+
+ def sendMsg(self, msg):
+ pass
+
+ def check_time(self, msg_time):
+ """
+ 判断两次聊天时间是否大于五分钟
+ 超过五分钟就显示时间
+ :param msg_time:
+ :return:
+ """
+ dt = msg_time - self.last_msg_time
+ # print(msg_time)
+ if abs(dt // 1000) >= 300:
+ s_l = time.localtime(msg_time / 1000)
+ ts = time.strftime("%Y-%m-%d %H:%M", s_l)
+ html = '''
+ ''' % (ts)
+ # print(html)
+ self.last_msg_time = msg_time
+ self.message.insertHtml(html)
+
+ def showMsg(self, message):
+ """
+ 显示聊天消息
+ :param message:
+ :return:
+ """
+ msgId = message[0]
+ # print(msgId, type(msgId))
+ self.message.moveCursor(self.message.textCursor().Start)
+ ta_username = message[7]
+ msgType = str(message[2])
+ isSend = message[4]
+ content = message[8]
+ imgPath = message[9]
+ msg_time = message[6]
+ self.check_time(msg_time)
+
+ if msgType == '1':
+ self.show_text(isSend, content)
+ elif msgType == '3':
+ self.show_img(isSend, imgPath)
+ elif msgType == '47':
+ self.show_emoji(isSend, imgPath)
+ # quit()
+ # self.message.moveCursor(self.message.textCursor().End)
+
+ def show_emoji(self, isSend, imagePath):
+ imgPath = data.get_emoji(imagePath)
+ image = Image.open(imgPath)
+ imagePixmap = image.size # 宽高像素
+ # 设置最大宽度
+ if imagePixmap[0]<150:
+ size = ""
+ else:
+ size = '''height="150" width="150"'''
+ html = '''
+
+
+ |
+ '''.format(imgPath,size)
+ style = 'vertical-align: top'
+ if isSend:
+ self.right(html, style=style)
+ else:
+ self.left(html, style=style)
+
+ def show_img(self, isSend, imgPath):
+ 'THUMBNAIL_DIRPATH://th_29cd0f0ca87652943be9ede365aabeaa'
+ imgPath = imgPath.split('th_')[1]
+ imgPath = f'./app/data/image2/{imgPath[0:2]}/{imgPath[2:4]}/th_{imgPath}'
+ html = '''
+  |
+ ''' % imgPath
+ style = 'vertical-align: top'
+ if isSend:
+ self.right(html, style=style)
+ else:
+ self.left(html, style=style)
+
+ def show_text(self, isSend, content):
+
+ if isSend:
+ html = '''
+ %s |
+ ''' % (content)
+ self.right(html)
+ else:
+ html = '''
+ %s |
+ ''' % (content)
+ self.left(html)
+
+ def right(self, content, style='vertical-align: middle'):
+ html = '''
+
+
+
+
+ %s
+ | : |
+  |
+ |
+
+
+
+
+ ''' % (style, content, self.Me.my_avatar)
+ # print('总的HTML')
+ # print(html)
+ self.message.insertHtml(html)
+
+ def left(self, content, style='vertical-align: middle'):
+ html = '''
+
+
+
+
+ |
+  |
+ : |
+ %s
+
+
+
+
+ ''' % (style, self.ta_avatar, content)
+ self.message.insertHtml(html)
+
+ def destroy_me(self):
+ """注销账户"""
+ pass
+
+
+class Contact(QtWidgets.QPushButton):
+ """
+ 联系人类,继承自pyqt的按钮,里面封装了联系人头像等标签
+ """
+ usernameSingal = pyqtSignal(str)
+
+ def __init__(self, Ui, id=None, contact=None):
+ super(Contact, self).__init__(Ui)
+ self.layoutWidget = QtWidgets.QWidget(Ui)
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.gridLayout1 = QtWidgets.QGridLayout(self.layoutWidget)
+ self.gridLayout1.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
+ self.gridLayout1.setContentsMargins(10, 10, 10, 10)
+ self.gridLayout1.setSpacing(10)
+ self.gridLayout1.setObjectName("gridLayout1")
+ self.label_time = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.label_time.setFont(font)
+ self.label_time.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.label_time.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
+ self.label_time.setObjectName("label_time")
+ self.gridLayout1.addWidget(self.label_time, 0, 2, 1, 1)
+ self.label_remark = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setFamily("Adobe 黑体 Std R")
+ font.setPointSize(10)
+ self.label_remark.setFont(font)
+ self.label_remark.setObjectName("label_remark")
+ self.gridLayout1.addWidget(self.label_remark, 0, 1, 1, 1)
+ self.label_msg = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.label_msg.setFont(font)
+ self.label_msg.setObjectName("label_msg")
+ self.gridLayout1.addWidget(self.label_msg, 1, 1, 1, 2)
+ self.label_avatar = QtWidgets.QLabel(self.layoutWidget)
+ self.label_avatar.setMinimumSize(QtCore.QSize(60, 60))
+ self.label_avatar.setMaximumSize(QtCore.QSize(60, 60))
+ self.label_avatar.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.label_avatar.setAutoFillBackground(False)
+ self.label_avatar.setStyleSheet("background-color: #ffffff;")
+ self.label_avatar.setInputMethodHints(QtCore.Qt.ImhNone)
+ self.label_avatar.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.label_avatar.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.label_avatar.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+ self.label_avatar.setObjectName("label_avatar")
+ self.gridLayout1.addWidget(self.label_avatar, 0, 0, 2, 1)
+ self.gridLayout1.setColumnStretch(0, 1)
+ self.gridLayout1.setColumnStretch(1, 6)
+ self.gridLayout1.setRowStretch(0, 5)
+ self.gridLayout1.setRowStretch(1, 3)
+ self.setLayout(self.gridLayout1)
+ self.setStyleSheet(
+ "QPushButton {background-color: rgb(253,253,253);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n"
+ )
+ self.msgCount = contact[0]
+ self.username = contact[1]
+ self.conversationTime = contact[6]
+ self.msgType = contact[7]
+ self.digest = contact[8]
+ hasTrunc = contact[10]
+ attrflag = contact[11]
+ if hasTrunc == 0:
+ if attrflag == 0:
+ self.digest = '[动画表情]'
+ elif attrflag == 67108864:
+ try:
+ remark = data.get_conRemark(contact[9])
+ msg = self.digest.split(':')[1].strip('\n').strip()
+ self.digest = f'{remark}:{msg}'
+ except Exception as e:
+ # print(self.digest)
+ # print(e)
+ pass
+ else:
+ pass
+ self.show_info(id)
+
+ def show_info(self, id):
+ avatar = data.get_avator(self.username)
+ # print(avatar)
+ remark = data.get_conRemark(self.username)
+ time = datetime.datetime.now().strftime("%m-%d %H:%M")
+ msg = '还没说话'
+ pixmap = QPixmap(avatar).scaled(60, 60) # 按指定路径找到图片
+ self.label_avatar.setPixmap(pixmap) # 在label上显示图片
+ self.label_remark.setText(remark)
+ self.label_msg.setText(self.digest)
+ self.label_time.setText(data.timestamp2str(self.conversationTime)[2:])
+
+ def show_msg(self):
+ self.usernameSingal.emit(self.username)
+
+
+class ChatMsg(QThread):
+ """
+ 发送信息线程
+ """
+ isSend_signal = pyqtSignal(tuple)
+ okSignal = pyqtSignal(int)
+
+ def __init__(self, my_u, ta_u, parent=None):
+ super().__init__(parent)
+ self.sec = 2 # 默认1000秒
+ self.my_u = my_u
+ self.ta_u = ta_u
+ self.my_avatar = data.get_avator(my_u)
+ self.msg_id = 0
+
+ def run(self):
+ self.ta_avatar = data.get_avator(self.ta_u)
+ messages = data.get_message(self.ta_u, self.msg_id)
+ # messages.reverse()
+ for message in messages:
+ self.isSend_signal.emit(message)
+ self.msg_id += 1
+ self.okSignal.emit(1)
+
+
+class myTextEdit(QtWidgets.QTextEdit): # 继承 原本组件
+ sendSignal = pyqtSignal(str)
+
+ def __init__(self, parent):
+ QtWidgets.QTextEdit.__init__(self, parent)
+ self.parent = parent
+ _translate = QtCore.QCoreApplication.translate
+ self.setHtml(_translate("Dialog",
+ "\n"
+ "\n"
+ "
"))
+
+ def keyPressEvent(self, event):
+ QtWidgets.QTextEdit.keyPressEvent(self, event)
+ if event.key() == Qt.Key_Return: # 如果是Enter 按钮
+ modifiers = event.modifiers()
+ if modifiers == Qt.ControlModifier:
+ print('success press ctrl+enter key', self.toPlainText())
+ self.append('\0')
+ return
+ self.sendSignal.emit(self.toPlainText())
+ print('success press enter key', self.toPlainText())
+
+ # if modifiers == (Qt.ControlModifier) and event.key() == Qt.Key_Return:
+ # self.sendSignal.emit(self.toPlainText())
+ # print('success press enter key', self.toPlainText())
diff --git a/app/Ui/chat/chatUi.py b/app/Ui/chat/chatUi.py
new file mode 100644
index 0000000..f5e2413
--- /dev/null
+++ b/app/Ui/chat/chatUi.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'chatUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(1120, 720)
+ Dialog.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ Dialog.setAutoFillBackground(False)
+ self.frame_2 = QtWidgets.QFrame(Dialog)
+ self.frame_2.setGeometry(QtCore.QRect(0, 0, 1120, 720))
+ self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_2.setObjectName("frame_2")
+ self.scrollArea = QtWidgets.QScrollArea(self.frame_2)
+ self.scrollArea.setEnabled(True)
+ self.scrollArea.setGeometry(QtCore.QRect(0, 40, 326, 680))
+ self.scrollArea.setMaximumSize(QtCore.QSize(400, 150000))
+ self.scrollArea.setAutoFillBackground(False)
+ self.scrollArea.setFrameShape(QtWidgets.QFrame.WinPanel)
+ self.scrollArea.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.scrollArea.setMidLineWidth(0)
+ self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
+ self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
+ self.scrollArea.setWidgetResizable(False)
+ self.scrollArea.setObjectName("scrollArea")
+ self.scrollAreaWidgetContents = QtWidgets.QWidget()
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 300, 12000))
+ self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
+ self.pushButton_2 = QtWidgets.QPushButton(self.scrollAreaWidgetContents)
+ self.pushButton_2.setGeometry(QtCore.QRect(0, 0, 300, 80))
+ self.pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.pushButton_2.setAutoFillBackground(False)
+ self.pushButton_2.setText("")
+ self.pushButton_2.setIconSize(QtCore.QSize(80, 80))
+ self.pushButton_2.setObjectName("pushButton_2")
+ self.label = QtWidgets.QLabel(self.scrollAreaWidgetContents)
+ self.label.setGeometry(QtCore.QRect(220, 10, 72, 15))
+ self.label.setObjectName("label")
+ self.scrollArea.setWidget(self.scrollAreaWidgetContents)
+ self.frame = QtWidgets.QFrame(self.frame_2)
+ self.frame.setGeometry(QtCore.QRect(321, 0, 801, 720))
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.message = QtWidgets.QTextBrowser(self.frame)
+ self.message.setGeometry(QtCore.QRect(9, 39, 801, 541))
+ self.message.setStyleSheet("background-color: #F5F5F5;")
+ self.message.setObjectName("message")
+ self.textEdit = QtWidgets.QTextEdit(self.frame)
+ self.textEdit.setGeometry(QtCore.QRect(9, 579, 821, 141))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.textEdit.setFont(font)
+ self.textEdit.setTabStopWidth(80)
+ self.textEdit.setCursorWidth(1)
+ self.textEdit.setObjectName("textEdit")
+ self.btn_sendMsg = QtWidgets.QPushButton(self.frame)
+ self.btn_sendMsg.setGeometry(QtCore.QRect(680, 670, 121, 51))
+ font = QtGui.QFont()
+ font.setFamily("黑体")
+ font.setPointSize(15)
+ font.setBold(False)
+ font.setWeight(50)
+ self.btn_sendMsg.setFont(font)
+ self.btn_sendMsg.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.btn_sendMsg.setMouseTracking(False)
+ self.btn_sendMsg.setAutoFillBackground(False)
+ self.btn_sendMsg.setStyleSheet("QPushButton {\n"
+" background-color: #f0f0f0;\n"
+" \n"
+" padding: 10px;\n"
+" color:rgb(5,180,104);\n"
+"}")
+ self.btn_sendMsg.setIconSize(QtCore.QSize(40, 40))
+ self.btn_sendMsg.setCheckable(False)
+ self.btn_sendMsg.setAutoDefault(True)
+ self.btn_sendMsg.setObjectName("btn_sendMsg")
+ self.toolButton = QtWidgets.QToolButton(self.frame)
+ self.toolButton.setGeometry(QtCore.QRect(760, 0, 47, 41))
+ self.toolButton.setObjectName("toolButton")
+ self.line_3 = QtWidgets.QFrame(self.frame)
+ self.line_3.setGeometry(QtCore.QRect(2, 0, 3, 720))
+ self.line_3.setLineWidth(6)
+ self.line_3.setFrameShape(QtWidgets.QFrame.VLine)
+ self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line_3.setObjectName("line_3")
+ self.line_2 = QtWidgets.QFrame(self.frame)
+ self.line_2.setGeometry(QtCore.QRect(9, 30, 831, 20))
+ self.line_2.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line_2.setObjectName("line_2")
+ self.label_remark = QtWidgets.QLabel(self.frame)
+ self.label_remark.setGeometry(QtCore.QRect(30, 0, 351, 41))
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ self.label_remark.setFont(font)
+ self.label_remark.setText("")
+ self.label_remark.setObjectName("label_remark")
+ self.line = QtWidgets.QFrame(self.frame)
+ self.line.setGeometry(QtCore.QRect(20, 580, 800, 3))
+ self.line.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line.setObjectName("line")
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.label.setText(_translate("Dialog", "TextLabel"))
+ self.textEdit.setHtml(_translate("Dialog", "\n"
+"\n"
+"
"))
+ self.btn_sendMsg.setText(_translate("Dialog", "发送"))
+ self.toolButton.setText(_translate("Dialog", "..."))
diff --git a/app/Ui/chat/chatUi.ui b/app/Ui/chat/chatUi.ui
new file mode 100644
index 0000000..a8de3c9
--- /dev/null
+++ b/app/Ui/chat/chatUi.ui
@@ -0,0 +1,315 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 1120
+ 720
+
+
+
+ ArrowCursor
+
+
+ Dialog
+
+
+ false
+
+
+
+
+ 0
+ 0
+ 1120
+ 720
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ true
+
+
+
+ 0
+ 40
+ 326
+ 680
+
+
+
+
+ 400
+ 150000
+
+
+
+ false
+
+
+ QFrame::WinPanel
+
+
+ QFrame::Raised
+
+
+ 0
+
+
+ Qt::ScrollBarAlwaysOn
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ QAbstractScrollArea::AdjustToContentsOnFirstShow
+
+
+ false
+
+
+
+
+ 0
+ 0
+ 300
+ 12000
+
+
+
+
+
+ 0
+ 0
+ 300
+ 80
+
+
+
+ Qt::LeftToRight
+
+
+ false
+
+
+
+
+
+
+ 80
+ 80
+
+
+
+
+
+
+ 220
+ 10
+ 72
+ 15
+
+
+
+ TextLabel
+
+
+
+
+
+
+
+ 321
+ 0
+ 801
+ 720
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+
+ 9
+ 39
+ 801
+ 541
+
+
+
+ Qt::ActionsContextMenu
+
+
+ background-color: #F5F5F5;
+
+
+
+
+
+ 9
+ 579
+ 821
+ 141
+
+
+
+
+ 15
+
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'SimSun'; font-size:15pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>
+
+
+ 80
+
+
+ 1
+
+
+
+
+
+ 680
+ 670
+ 121
+ 51
+
+
+
+
+ 黑体
+ 15
+ 50
+ false
+
+
+
+ ArrowCursor
+
+
+ false
+
+
+ false
+
+
+ QPushButton {
+ background-color: #f0f0f0;
+
+ padding: 10px;
+ color:rgb(5,180,104);
+}
+
+
+ 发送
+
+
+
+ 40
+ 40
+
+
+
+ false
+
+
+ true
+
+
+
+
+
+ 760
+ 0
+ 47
+ 41
+
+
+
+ ...
+
+
+
+
+
+ 2
+ 0
+ 3
+ 720
+
+
+
+ 6
+
+
+ Qt::Vertical
+
+
+
+
+
+ 9
+ 30
+ 831
+ 20
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 30
+ 0
+ 351
+ 41
+
+
+
+
+ 12
+
+
+
+
+
+
+
+
+
+ 20
+ 580
+ 800
+ 3
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+
+
diff --git a/app/Ui/chat/group/CreateGroup.py b/app/Ui/chat/group/CreateGroup.py
new file mode 100644
index 0000000..134fb2d
--- /dev/null
+++ b/app/Ui/chat/group/CreateGroup.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+"""
+@File : CreateGroup.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/20 22:55
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+from .create_groupUi import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from ....DB import data
+import time
+
+
+class CreateGroupView(QWidget, Ui_Frame):
+ backSignal = pyqtSignal(str)
+ gidSignal = pyqtSignal(int)
+ def __init__(self, username, parent=None):
+ super(CreateGroupView, self).__init__(parent)
+ self.setupUi(self)
+ self.tips.setVisible(False)
+ self.setWindowTitle('创建群聊')
+ self.setWindowIcon(QIcon('./data/icon.png'))
+ self.username = username
+ # self.register_2.clicked.connect(self.login_)
+ self.back.clicked.connect(self.btnEnterClicked)
+ self.back.clicked.connect(self.btnEnterClicked)
+ self.btn_set_gAvatar.clicked.connect(self.up_avatar)
+ self.btn_create.clicked.connect(self.create_group)
+ self.avatar = None
+
+ def up_avatar(self):
+ path, _ = QFileDialog.getOpenFileName(self, 'Open file', r'..', "Image files (*.png;*.jpg)")
+ if path:
+ try:
+ pixmap = QPixmap(path).scaled(80, 80) # 按指定路径找到图片
+ self.avatar_img.setPixmap(pixmap) # 在label上显示图片
+ self.avatar = path
+ except:
+ self.error.setText('头像不存在')
+
+ def create_group(self):
+ # self.close()
+ if not self.avatar:
+ self.error.setText('请上传头像')
+ return False
+ name = self.group_name.text()
+ intro = self.group_intro.toPlainText()
+ # print(intro,self.username)
+ flag = data.create_group(
+ g_name=name,
+ g_admin=self.username,
+ g_intro=intro
+ )
+ # print('123456')
+ # print(flag)
+ if not flag:
+ self.error.setText('创建失败')
+ # print('yonghu已经存在')
+ else:
+ self.error.setText('创建成功')
+ self.error.setStyleSheet("color:black")
+ avatar = data.get_avator(str(flag))
+ # new_path = '/'.join(avatar.split('/')[:-1])+'/'
+ # print(avatar)
+ if '.' in avatar[-10:]:
+ avatar = '.'.join(avatar.split('.')[:-1])
+ # print(avatar)
+ data.mycopyfile(self.avatar, avatar + '.png')
+ self.tips.setVisible(True)
+ self.thread = MyThread() # 创建一个线程
+ self.thread.sec_changed_signal.connect(self._update) # 线程发过来的信号挂接到槽:update
+ self.thread.start()
+ self.gidSignal.emit(int(flag))
+
+ def _update(self, sec):
+ # self.time.setProperty("value", float(sec))
+ # self.time.setDigitCount(sec)
+ # self.time.s
+ if sec == 0:
+ self.btnEnterClicked()
+
+ def btnEnterClicked(self):
+ print("退出创建群聊界面")
+ # 中间可以添加处理逻辑
+ self.backSignal.emit("back")
+ self.close()
+
+ def btnExitClicked(self):
+ print("Exit clicked")
+ self.close()
+
+
+class MyThread(QThread):
+ sec_changed_signal = pyqtSignal(int) # 信号类型:int
+
+ def __init__(self, sec=1000, parent=None):
+ super().__init__(parent)
+ self.sec = 2 # 默认1000秒
+
+ def run(self):
+ for i in range(self.sec, -1, -1):
+ self.sec_changed_signal.emit(i) # 发射信号
+ time.sleep(1)
diff --git a/app/Ui/chat/group/Group.py b/app/Ui/chat/group/Group.py
new file mode 100644
index 0000000..f4ba1ce
--- /dev/null
+++ b/app/Ui/chat/group/Group.py
@@ -0,0 +1,442 @@
+# -*- coding: utf-8 -*-
+"""
+@File : Group.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/20 20:26
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import datetime
+import json
+
+from .GroupUi import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from ....DB import data
+import time
+# from .chat import MainWinController
+from .create_groupUi import Ui_Frame
+from .CreateGroup import CreateGroupView
+from .addGroup import Ui_Frame as Add_GroupUi
+
+
+class GroupControl(QWidget, Ui_Form):
+ backSignal = pyqtSignal(str)
+ addSignal = pyqtSignal(int)
+
+ # createSignal = pyqtSignal(Group)
+
+ def __init__(self, parent=None, Me=None):
+ super(GroupControl, self).__init__(parent)
+ self.groups = None
+ self.setupUi(self)
+ self.Me = Me
+ self.btn_create_group.clicked.connect(self.create_group_view)
+ self.btn_add_group.clicked.connect(self.addGroupUi)
+ self.btn_sendMsg.clicked.connect(self.sendMsg) # 发送信息按钮
+ self.btn_del_group.clicked.connect(self.delete_group)
+ self.toolButton.clicked.connect(self.about)
+ self.frame_ag = QtWidgets.QFrame(self.frame)
+ self.frame_ag.setGeometry(QtCore.QRect(0, 0, 800, 720))
+ self.frame_ag.setFrameShape(QtWidgets.QFrame.Box)
+ self.frame_ag.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_ag.setObjectName("frame_cg")
+ self.frame_ag.setVisible(False)
+ self.addSignal.connect(self.new_groupUi)
+ # print(self.username)
+ self.groups = {}
+ self.last_gid = None
+ self.now_gid = None
+ self.group_users = None
+ self.last_msg_time = datetime.datetime(2022, 12, 19, 15, 4) # 上次信息的时间
+ self.initUi()
+
+ def initUi(self):
+
+ groups = data.get_groups(self.Me.username)
+ self.groups_num = len(groups)
+ print('群组:', groups)
+ max_hight = max(self.groups_num * 80, 680)
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 300, max_hight))
+ for i in range(len(groups)):
+ group = groups[i]
+ # print(contact)
+ g_id = group[0]
+ print('群聊信息:', group)
+ pushButton_2 = OneGroup(self.scrollAreaWidgetContents, group)
+ pushButton_2.setGeometry(QtCore.QRect(0, 80 * i, 300, 80))
+ pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ pushButton_2.clicked.connect(pushButton_2.show_msg)
+ pushButton_2.gidSingal.connect(self.chat)
+ self.groups[g_id] = pushButton_2
+
+ def chat(self, g_id):
+ # self.frame_ag.setVisible(False)
+ print('当前聊天群号:', g_id)
+ self.frame_msg.setVisible(True)
+ self.frame_ag.setVisible(False)
+ self.now_gid = g_id
+ self.group_users = data.get_group_users(g_id)
+ # 将当前群的界面设置为灰色
+ # if self.last_gid and self.last_gid != g_id:
+ # self.groups[self.last_gid].setStyleSheet("background-color : rgb(253,253,253)")
+ # self.last_gid = g_id
+ # self.groups[g_id].setStyleSheet("background-color : rgb(198,198,198)")
+ g_name = self.groups[g_id].g_name
+ self.l_g_name.setText(f'{g_name}({g_id})')
+ self.message.clear()
+ self.message.append(str(g_id))
+ # 创建新的线程用于显示聊天记录
+ self.Thread = ChatMsg(self.Me.username, g_id, self.Me.socket)
+ self.Thread.isSend_signal.connect(self.showMsg)
+ self.Thread.recvSignal.connect(self.showMsg)
+ self.Thread.sendSignal.connect(self.showMsg)
+ self.Thread.start()
+ pass
+
+ def sendMsg(self, msg):
+ """
+ 发送信息
+ :param msg:信息内容
+ :return:
+ """
+ msg = self.textEdit.toPlainText()
+ message = self.Thread.send_msg(msg)
+ if message:
+ print(msg, '发送成功')
+ # self.showMsg(message)
+ else:
+ print(msg, '发送失败')
+ self.textEdit.clear()
+
+ def create_group_view(self):
+ """建群界面"""
+ self.frame_msg.setVisible(False)
+ self.frame_ag.setVisible(False)
+ # self.frame_ag.setVisible(True)
+ # print(self.Me.__dict__)
+ self.create_view = CreateGroupView(username=self.Me.username)
+ self.create_view.gidSignal.connect(self.new_groupUi)
+ self.create_view.show()
+
+ def addGroupBack(self):
+ """加群界面"""
+ self.frame_msg.setVisible(True)
+ self.frame_ag.setVisible(False)
+ # self.CG_Ui = None
+
+ def delete_group(self):
+ """退群"""
+ a = QMessageBox.question(self, '警告', '你确定要退群吗?', QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No) # "退出"代表的是弹出框的标题,"你确认退出.."表示弹出框的内容
+ if a == QMessageBox.Yes:
+ data.delete_group(self.Me.username, self.now_gid)
+ self.frame_msg.setVisible(False)
+ self.last_gid = None
+ self.groups_num -= 1
+ self.l_g_name.setText('已退群')
+ self.groups[self.now_gid].setVisible(False)
+ self.groups.pop(self.now_gid)
+ else:
+ return
+
+ def show(self):
+ self.message.append('2020303457')
+
+ def addGroupUi(self):
+ """加群的界面"""
+ self.frame_msg.setVisible(False)
+ self.CG_Ui = Add_GroupUi()
+ self.CG_Ui.setupUi(self.frame_ag)
+ self.CG_Ui.back.clicked.connect(self.addGroupBack)
+ self.CG_Ui.btn_add.clicked.connect(self.addGroup)
+ self.CG_Ui.btn_search.clicked.connect(self.searchGroup)
+ self.frame_ag.setVisible(True)
+ self.CG_Ui.tips.setVisible(False)
+ self.CG_Ui.time.setVisible(False)
+ pass
+
+ def searchGroup(self):
+ """搜索群聊"""
+ gid = self.CG_Ui.line_g_id.text()
+ if not gid:
+ self.CG_Ui.error.setText('请输入群号')
+ return False
+ nickname = self.CG_Ui.line_nickname.text()
+ group = data.search_group(gid)
+ if not group:
+ self.CG_Ui.error.setText('未找到群聊')
+ return False
+ avatar = data.get_avator(gid)
+ pixmap = QPixmap(avatar).scaled(60, 60) # 按指定路径找到图片
+ self.CG_Ui.avatar_img.setPixmap(pixmap) # 在label上显示图片
+ return group
+
+ def addGroup(self):
+ """添加群聊"""
+ gid = self.CG_Ui.line_g_id.text()
+ gid = int(gid)
+ nickname = self.CG_Ui.line_nickname.text()
+ flag = data.add_group(self.Me.username, gid, nickname)
+ if not flag:
+ self.CG_Ui.error.setText('群聊不存在')
+ return False
+ avatar = data.get_avator(gid)
+ pixmap = QPixmap(avatar).scaled(60, 60) # 按指定路径找到图片
+ self.CG_Ui.avatar_img.setPixmap(pixmap) # 在label上显示图片
+ self.CG_Ui.error.setText('加群成功')
+ # self.addSignal.emit(gid)
+ self.new_groupUi(gid)
+
+ def new_groupUi(self, gid):
+ nickname = ''
+ group = data.search_group(gid)
+ if not group:
+ return False
+ g_name = group[1]
+ self.groups_num += 1
+ max_hight = max(self.groups_num * 80, 680)
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 300, max_hight))
+ group = [
+ gid, g_name, nickname, 3, 1
+ ]
+ # g_id = group[0]
+ print('群聊信息:', group)
+ pushButton_2 = OneGroup(self.scrollAreaWidgetContents, group)
+ pushButton_2.setGeometry(QtCore.QRect(0, 80 * self.groups_num - 80, 300, 80))
+ pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight)
+ pushButton_2.clicked.connect(pushButton_2.show_msg)
+ pushButton_2.gidSingal.connect(self.chat)
+ # pushButton_2.setVisible(True)
+ print('加群成功', gid)
+ print(pushButton_2.g_id)
+ self.groups[gid] = pushButton_2
+ pushButton_2.setVisible(True)
+ print(self.groups)
+ print(self.now_gid, self.last_gid)
+
+ def showMsg(self, message):
+ """
+ 显示聊天消息
+ :param message:
+ :return:
+ """
+ # print(message)
+ gid = message[1]
+ if self.now_gid is None or gid != self.now_gid:
+ return
+ # self.now_gid = gid
+ talker = message[5]
+ isSend = message[6]
+ content = message[3]
+ msg_time = message[4]
+ # print(message)
+ # print(msg_time, type(msg_time))
+ self.check_time(msg_time)
+ if isSend == 1 and talker == self.Me.username:
+ # 自己发的信息在右边显示
+ self.right(content, talker)
+ else:
+ # 收到的信息在左边显示
+ self.left(content, talker)
+ self.message.moveCursor(self.message.textCursor().End)
+
+ def about(self):
+ group = data.search_group(self.now_gid)
+ QMessageBox.about(
+ self,
+ "关于",
+ f"关于本群\n群名:{group[1]}\n群号:{self.now_gid}"
+ )
+
+ def check_time(self, msg_time):
+ """
+ 判断两次聊天时间是否大于五分钟
+ 超过五分钟就显示时间
+ :param msg_time:
+ :return:
+ """
+ dt = msg_time - self.last_msg_time
+ # print(msg_time)
+ if dt.seconds >= 300:
+ html = '''
+ ''' % (msg_time.strftime("%Y-%m-%d %H:%M"))
+ # print(html)
+ self.last_msg_time = msg_time
+ self.message.insertHtml(html)
+
+ def right(self, content, taklekId):
+ html = '''
+
+
+
+
+ | %s : |
+  |
+ |
+
+
+
+
+ ''' % (content, self.Me.my_avatar)
+ self.message.insertHtml(html)
+
+ def left(self, content, taklekId):
+ avatar = data.get_avator(taklekId)
+ html = '''
+
+
+
+
+ |
+  |
+ : %s |
+
+
+
+
+ ''' % (avatar, content)
+ self.message.insertHtml(html)
+
+
+class OneGroup(QtWidgets.QPushButton):
+ """
+ 联系人类,继承自pyqt的按钮,里面封装了联系人头像等标签
+ """
+ gidSingal = pyqtSignal(int)
+
+ def __init__(self, Ui, contact=None):
+ super(OneGroup, self).__init__(Ui)
+ self.layoutWidget = QtWidgets.QWidget(Ui)
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.gridLayout1 = QtWidgets.QGridLayout(self.layoutWidget)
+ self.gridLayout1.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
+ self.gridLayout1.setContentsMargins(10, 10, 10, 10)
+ self.gridLayout1.setHorizontalSpacing(20)
+ self.gridLayout1.setVerticalSpacing(10)
+ self.gridLayout1.setObjectName("gridLayout1")
+ self.time0_1 = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.time0_1.setFont(font)
+ self.time0_1.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.time0_1.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
+ self.time0_1.setObjectName("time0_1")
+ self.gridLayout1.addWidget(self.time0_1, 0, 2, 1, 1)
+ self.remark1 = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.remark1.setFont(font)
+ self.remark1.setObjectName("remark1")
+ self.gridLayout1.addWidget(self.remark1, 0, 1, 1, 1)
+ self.msg1 = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.msg1.setFont(font)
+ self.msg1.setObjectName("msg1")
+ self.gridLayout1.addWidget(self.msg1, 1, 1, 1, 2)
+ self.image1 = QtWidgets.QLabel(self.layoutWidget)
+ self.image1.setMinimumSize(QtCore.QSize(60, 60))
+ self.image1.setMaximumSize(QtCore.QSize(60, 60))
+ self.image1.setLayoutDirection(QtCore.Qt.RightToLeft)
+ self.image1.setAutoFillBackground(False)
+ self.image1.setStyleSheet("background-color: #ffffff;")
+ self.image1.setInputMethodHints(QtCore.Qt.ImhNone)
+ self.image1.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.image1.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.image1.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+ self.image1.setObjectName("image1")
+ self.gridLayout1.addWidget(self.image1, 0, 0, 2, 1)
+ self.gridLayout1.setColumnStretch(0, 1)
+ self.gridLayout1.setColumnStretch(1, 6)
+ self.gridLayout1.setRowStretch(0, 5)
+ self.gridLayout1.setRowStretch(1, 3)
+ self.setLayout(self.gridLayout1)
+ if contact:
+ self.g_id = contact[0]
+ self.g_name = contact[1]
+ self.nickname = contact[2]
+ self.type = contact[3]
+ self.addTime = contact[4]
+ self.show_info(id)
+
+ def show_info(self, id):
+ if 1:
+ # try:
+ avatar = data.get_avator(self.g_id)
+ remark = id
+ time = datetime.datetime.now().strftime("%m-%d %H:%M")
+ msg = '还没说话'
+ pixmap = QPixmap(avatar).scaled(60, 60) # 按指定路径找到图片
+ self.image1.setPixmap(pixmap) # 在label上显示图片
+ self.remark1.setText(self.g_name)
+ self.time0_1.setText(time)
+
+ def show_msg(self):
+ print('点击的群号', self.g_id)
+ self.gidSingal.emit(self.g_id)
+ pass
+
+
+class ChatMsg(QThread):
+ """
+ 发送信息线程
+ """
+ isSend_signal = pyqtSignal(tuple)
+ recvSignal = pyqtSignal(tuple)
+ sendSignal = pyqtSignal(tuple)
+
+ def __init__(self, my_u, g_id, socket, parent=None):
+ super().__init__(parent)
+ self.sec = 2 # 默认1000秒
+ self.my_u = my_u
+ self.g_id = g_id
+ self.group_users = data.get_group_users(g_id)
+ self.my_avatar = data.get_avator(my_u)
+ self.socket = socket
+
+ def send_msg(self, msg):
+ # 给群里所有在线的用户发送信息
+ for group_user in self.group_users:
+ username = group_user[0]
+ if username == self.my_u:
+ continue
+ ta_port = group_user[4]
+ nickname = group_user[3]
+ self.ta_addr = ('localhost', ta_port)
+ if ta_port == -1:
+ print(f'{nickname}不在线')
+ continue
+ send_data = {
+ 'type': 'G',
+ 'gid': self.g_id,
+ 'username': self.my_u,
+ 'content': msg
+ }
+ print(f'{nickname}在线,{msg} 发送成功')
+ self.socket.sendto(json.dumps(send_data).encode('utf-8'), self.ta_addr)
+ message = data.send_group_msg(
+ gid=self.g_id,
+ msg=msg,
+ talker=self.my_u,
+ IsSend=1,
+ _type=3
+ )
+ self.sendSignal.emit(message)
+ return message
+
+ def run(self):
+ # return
+ messages = data.get_group_message(self.g_id)
+ # print(messages)
+ for message in messages:
+ self.isSend_signal.emit(message)
+ # self.recv_msg()
diff --git a/app/Ui/chat/group/GroupUi.py b/app/Ui/chat/group/GroupUi.py
new file mode 100644
index 0000000..0ce7f8d
--- /dev/null
+++ b/app/Ui/chat/group/GroupUi.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'GroupUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(1120, 720)
+ self.scrollArea = QtWidgets.QScrollArea(Form)
+ self.scrollArea.setGeometry(QtCore.QRect(-1, 70, 322, 651))
+ self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
+ self.scrollArea.setWidgetResizable(True)
+ self.scrollArea.setObjectName("scrollArea")
+ self.scrollAreaWidgetContents = QtWidgets.QWidget()
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 299, 649))
+ self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
+ self.scrollArea.setWidget(self.scrollAreaWidgetContents)
+ self.horizontalLayoutWidget = QtWidgets.QWidget(Form)
+ self.horizontalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 321, 71))
+ self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.btn_create_group = QtWidgets.QPushButton(self.horizontalLayoutWidget)
+ self.btn_create_group.setObjectName("btn_create_group")
+ self.horizontalLayout.addWidget(self.btn_create_group)
+ self.btn_add_group = QtWidgets.QPushButton(self.horizontalLayoutWidget)
+ self.btn_add_group.setObjectName("btn_add_group")
+ self.horizontalLayout.addWidget(self.btn_add_group)
+ self.frame = QtWidgets.QFrame(Form)
+ self.frame.setGeometry(QtCore.QRect(320, 0, 800, 720))
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.frame_msg = QtWidgets.QFrame(self.frame)
+ self.frame_msg.setGeometry(QtCore.QRect(0, 0, 800, 720))
+ self.frame_msg.setFrameShape(QtWidgets.QFrame.Box)
+ self.frame_msg.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_msg.setObjectName("frame_msg")
+ self.message = QtWidgets.QTextBrowser(self.frame_msg)
+ self.message.setGeometry(QtCore.QRect(0, 40, 800, 540))
+ self.message.setObjectName("message")
+ self.textEdit = QtWidgets.QTextEdit(self.frame_msg)
+ self.textEdit.setGeometry(QtCore.QRect(0, 580, 800, 140))
+ self.textEdit.setObjectName("textEdit")
+ self.l_g_name = QtWidgets.QLabel(self.frame_msg)
+ self.l_g_name.setGeometry(QtCore.QRect(10, 0, 431, 41))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.l_g_name.setFont(font)
+ self.l_g_name.setText("")
+ self.l_g_name.setObjectName("l_g_name")
+ self.btn_sendMsg = QtWidgets.QPushButton(self.frame_msg)
+ self.btn_sendMsg.setGeometry(QtCore.QRect(678, 668, 121, 51))
+ font = QtGui.QFont()
+ font.setFamily("黑体")
+ font.setPointSize(15)
+ font.setBold(False)
+ font.setWeight(50)
+ self.btn_sendMsg.setFont(font)
+ self.btn_sendMsg.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.btn_sendMsg.setMouseTracking(False)
+ self.btn_sendMsg.setAutoFillBackground(False)
+ self.btn_sendMsg.setStyleSheet("QPushButton {\n"
+" background-color: #f0f0f0;\n"
+" \n"
+" padding: 10px;\n"
+" color:rgb(5,180,104);\n"
+"}")
+ self.btn_sendMsg.setIconSize(QtCore.QSize(40, 40))
+ self.btn_sendMsg.setCheckable(False)
+ self.btn_sendMsg.setAutoDefault(True)
+ self.btn_sendMsg.setObjectName("btn_sendMsg")
+ self.toolButton = QtWidgets.QToolButton(self.frame_msg)
+ self.toolButton.setGeometry(QtCore.QRect(750, 10, 47, 21))
+ self.toolButton.setObjectName("toolButton")
+ self.btn_del_group = QtWidgets.QPushButton(self.frame_msg)
+ self.btn_del_group.setGeometry(QtCore.QRect(650, 10, 93, 28))
+ self.btn_del_group.setObjectName("btn_del_group")
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
+ self.btn_create_group.setText(_translate("Form", "创建群聊"))
+ self.btn_add_group.setText(_translate("Form", "添加群聊"))
+ self.btn_sendMsg.setText(_translate("Form", "发送"))
+ self.toolButton.setText(_translate("Form", "..."))
+ self.btn_del_group.setText(_translate("Form", "退群"))
diff --git a/app/Ui/chat/group/GroupUi.ui b/app/Ui/chat/group/GroupUi.ui
new file mode 100644
index 0000000..8005f42
--- /dev/null
+++ b/app/Ui/chat/group/GroupUi.ui
@@ -0,0 +1,217 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 1120
+ 720
+
+
+
+ Form
+
+
+
+
+ -1
+ 70
+ 322
+ 651
+
+
+
+ Qt::ScrollBarAlwaysOn
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 299
+ 649
+
+
+
+
+
+
+
+ 0
+ 0
+ 321
+ 71
+
+
+
+ -
+
+
+ 创建群聊
+
+
+
+ -
+
+
+ 添加群聊
+
+
+
+
+
+
+
+
+ 320
+ 0
+ 800
+ 720
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+
+ 0
+ 0
+ 800
+ 720
+
+
+
+ QFrame::Box
+
+
+ QFrame::Raised
+
+
+
+
+ 0
+ 40
+ 800
+ 540
+
+
+
+
+
+
+ 0
+ 580
+ 800
+ 140
+
+
+
+
+
+
+ 10
+ 0
+ 431
+ 41
+
+
+
+
+ 15
+
+
+
+
+
+
+
+
+
+ 678
+ 668
+ 121
+ 51
+
+
+
+
+ 黑体
+ 15
+ 50
+ false
+
+
+
+ ArrowCursor
+
+
+ false
+
+
+ false
+
+
+ QPushButton {
+ background-color: #f0f0f0;
+
+ padding: 10px;
+ color:rgb(5,180,104);
+}
+
+
+ 发送
+
+
+
+ 40
+ 40
+
+
+
+ false
+
+
+ true
+
+
+
+
+
+ 750
+ 10
+ 47
+ 21
+
+
+
+ ...
+
+
+
+
+
+ 650
+ 10
+ 93
+ 28
+
+
+
+ 退群
+
+
+
+
+
+
+
+
diff --git a/app/Ui/chat/group/__init__.py b/app/Ui/chat/group/__init__.py
new file mode 100644
index 0000000..7db11e6
--- /dev/null
+++ b/app/Ui/chat/group/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/24 10:33
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/Ui/chat/group/addGroup.py b/app/Ui/chat/group/addGroup.py
new file mode 100644
index 0000000..5058198
--- /dev/null
+++ b/app/Ui/chat/group/addGroup.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'addGroup.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Frame(object):
+ def setupUi(self, Frame):
+ Frame.setObjectName("Frame")
+ Frame.resize(800, 720)
+ self.back = QtWidgets.QPushButton(Frame)
+ self.back.setGeometry(QtCore.QRect(0, 0, 93, 28))
+ self.back.setObjectName("back")
+ self.avatar_img = QtWidgets.QLabel(Frame)
+ self.avatar_img.setGeometry(QtCore.QRect(530, 130, 80, 80))
+ self.avatar_img.setObjectName("avatar_img")
+ self.label_3 = QtWidgets.QLabel(Frame)
+ self.label_3.setGeometry(QtCore.QRect(230, 50, 221, 51))
+ font = QtGui.QFont()
+ font.setFamily("一纸情书")
+ font.setPointSize(20)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.layoutWidget = QtWidgets.QWidget(Frame)
+ self.layoutWidget.setGeometry(QtCore.QRect(181, 113, 311, 281))
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.label_2 = QtWidgets.QLabel(self.layoutWidget)
+ self.label_2.setMinimumSize(QtCore.QSize(53, 0))
+ self.label_2.setMaximumSize(QtCore.QSize(58, 16777215))
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout_2.addWidget(self.label_2)
+ self.line_g_id = QtWidgets.QLineEdit(self.layoutWidget)
+ self.line_g_id.setMinimumSize(QtCore.QSize(0, 50))
+ self.line_g_id.setObjectName("line_g_id")
+ self.horizontalLayout_2.addWidget(self.line_g_id)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.label_4 = QtWidgets.QLabel(self.layoutWidget)
+ self.label_4.setMinimumSize(QtCore.QSize(38, 21))
+ self.label_4.setMaximumSize(QtCore.QSize(58, 21))
+ self.label_4.setObjectName("label_4")
+ self.horizontalLayout_4.addWidget(self.label_4)
+ self.line_nickname = QtWidgets.QLineEdit(self.layoutWidget)
+ self.line_nickname.setMinimumSize(QtCore.QSize(0, 50))
+ self.line_nickname.setMaximumSize(QtCore.QSize(100000, 50))
+ self.line_nickname.setObjectName("line_nickname")
+ self.horizontalLayout_4.addWidget(self.line_nickname)
+ self.verticalLayout.addLayout(self.horizontalLayout_4)
+ self.btn_search = QtWidgets.QPushButton(self.layoutWidget)
+ self.btn_search.setObjectName("btn_search")
+ self.verticalLayout.addWidget(self.btn_search)
+ self.btn_add = QtWidgets.QPushButton(self.layoutWidget)
+ self.btn_add.setObjectName("btn_add")
+ self.verticalLayout.addWidget(self.btn_add)
+ self.time = QtWidgets.QLCDNumber(Frame)
+ self.time.setGeometry(QtCore.QRect(390, 400, 51, 61))
+ self.time.setLineWidth(5)
+ self.time.setDigitCount(1)
+ self.time.setProperty("value", 8.0)
+ self.time.setObjectName("time")
+ self.error = QtWidgets.QLabel(Frame)
+ self.error.setGeometry(QtCore.QRect(510, 340, 101, 16))
+ self.error.setAutoFillBackground(False)
+ self.error.setStyleSheet("color:red")
+ self.error.setText("")
+ self.error.setObjectName("error")
+ self.toolButton = QtWidgets.QToolButton(Frame)
+ self.toolButton.setGeometry(QtCore.QRect(750, 0, 47, 21))
+ self.toolButton.setObjectName("toolButton")
+ self.tips = QtWidgets.QLabel(Frame)
+ self.tips.setGeometry(QtCore.QRect(230, 430, 61, 16))
+ self.tips.setObjectName("tips")
+
+ self.retranslateUi(Frame)
+ QtCore.QMetaObject.connectSlotsByName(Frame)
+
+ def retranslateUi(self, Frame):
+ _translate = QtCore.QCoreApplication.translate
+ Frame.setWindowTitle(_translate("Frame", "Frame"))
+ self.back.setText(_translate("Frame", "返回"))
+ self.avatar_img.setText(_translate("Frame", "+"))
+ self.label_3.setText(_translate("Frame", "添加群聊"))
+ self.label_2.setText(_translate("Frame", "群 号:"))
+ self.label_4.setText(_translate("Frame", "群昵称:"))
+ self.btn_search.setText(_translate("Frame", "查找"))
+ self.btn_add.setText(_translate("Frame", "添加"))
+ self.toolButton.setText(_translate("Frame", "..."))
+ self.tips.setText(_translate("Frame", "即将返回"))
diff --git a/app/Ui/chat/group/addGroup.ui b/app/Ui/chat/group/addGroup.ui
new file mode 100644
index 0000000..341ddfe
--- /dev/null
+++ b/app/Ui/chat/group/addGroup.ui
@@ -0,0 +1,226 @@
+
+
+ Frame
+
+
+
+ 0
+ 0
+ 800
+ 720
+
+
+
+ Frame
+
+
+
+
+ 0
+ 0
+ 93
+ 28
+
+
+
+ 返回
+
+
+
+
+
+ 530
+ 130
+ 80
+ 80
+
+
+
+ +
+
+
+
+
+
+ 230
+ 50
+ 221
+ 51
+
+
+
+
+ 一纸情书
+ 20
+
+
+
+ 添加群聊
+
+
+
+
+
+ 181
+ 113
+ 311
+ 281
+
+
+
+ -
+
+
-
+
+
+
+ 53
+ 0
+
+
+
+
+ 58
+ 16777215
+
+
+
+ 群 号:
+
+
+
+ -
+
+
+
+ 0
+ 50
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 38
+ 21
+
+
+
+
+ 58
+ 21
+
+
+
+ 群昵称:
+
+
+
+ -
+
+
+
+ 0
+ 50
+
+
+
+
+ 100000
+ 50
+
+
+
+
+
+
+ -
+
+
+ 查找
+
+
+
+ -
+
+
+ 添加
+
+
+
+
+
+
+
+
+ 390
+ 400
+ 51
+ 61
+
+
+
+ 5
+
+
+ 1
+
+
+ 8.000000000000000
+
+
+
+
+
+ 510
+ 340
+ 101
+ 16
+
+
+
+ false
+
+
+ color:red
+
+
+
+
+
+
+
+
+ 750
+ 0
+ 47
+ 21
+
+
+
+ ...
+
+
+
+
+
+ 230
+ 430
+ 61
+ 16
+
+
+
+ 即将返回
+
+
+
+
+
+
diff --git a/app/Ui/chat/group/create_groupUi.py b/app/Ui/chat/group/create_groupUi.py
new file mode 100644
index 0000000..6fe575b
--- /dev/null
+++ b/app/Ui/chat/group/create_groupUi.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'create_groupUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Frame(object):
+ def setupUi(self, Frame):
+ Frame.setObjectName("Frame")
+ Frame.resize(800, 720)
+ self.error = QtWidgets.QLabel(Frame)
+ self.error.setGeometry(QtCore.QRect(550, 80, 101, 16))
+ self.error.setAutoFillBackground(False)
+ self.error.setStyleSheet("color:red")
+ self.error.setText("")
+ self.error.setObjectName("error")
+ self.tips = QtWidgets.QLabel(Frame)
+ self.tips.setGeometry(QtCore.QRect(290, 630, 121, 16))
+ self.tips.setObjectName("tips")
+ self.layoutWidget = QtWidgets.QWidget(Frame)
+ self.layoutWidget.setGeometry(QtCore.QRect(140, 70, 401, 511))
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.label_2 = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.label_2.setFont(font)
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout_2.addWidget(self.label_2)
+ self.group_name = QtWidgets.QLineEdit(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.group_name.setFont(font)
+ self.group_name.setObjectName("group_name")
+ self.horizontalLayout_2.addWidget(self.group_name)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.label.setFont(font)
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.group_intro = QtWidgets.QTextEdit(self.layoutWidget)
+ self.group_intro.setObjectName("group_intro")
+ self.horizontalLayout.addWidget(self.group_intro)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.btn_set_gAvatar = QtWidgets.QPushButton(self.layoutWidget)
+ self.btn_set_gAvatar.setMinimumSize(QtCore.QSize(100, 50))
+ self.btn_set_gAvatar.setMaximumSize(QtCore.QSize(100, 50))
+ self.btn_set_gAvatar.setObjectName("btn_set_gAvatar")
+ self.horizontalLayout_3.addWidget(self.btn_set_gAvatar)
+ self.avatar_img = QtWidgets.QLabel(self.layoutWidget)
+ self.avatar_img.setMinimumSize(QtCore.QSize(100, 100))
+ self.avatar_img.setMaximumSize(QtCore.QSize(100, 100))
+ self.avatar_img.setText("")
+ self.avatar_img.setObjectName("avatar_img")
+ self.horizontalLayout_3.addWidget(self.avatar_img)
+ self.verticalLayout.addLayout(self.horizontalLayout_3)
+ self.btn_create = QtWidgets.QPushButton(self.layoutWidget)
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.btn_create.setFont(font)
+ self.btn_create.setObjectName("btn_create")
+ self.verticalLayout.addWidget(self.btn_create)
+ self.label_3 = QtWidgets.QLabel(Frame)
+ self.label_3.setGeometry(QtCore.QRect(260, 0, 221, 51))
+ font = QtGui.QFont()
+ font.setFamily("一纸情书")
+ font.setPointSize(20)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.back = QtWidgets.QPushButton(Frame)
+ self.back.setGeometry(QtCore.QRect(0, 0, 91, 28))
+ self.back.setObjectName("back")
+ self.toolButton = QtWidgets.QToolButton(Frame)
+ self.toolButton.setGeometry(QtCore.QRect(750, 0, 47, 21))
+ self.toolButton.setObjectName("toolButton")
+
+ self.retranslateUi(Frame)
+ QtCore.QMetaObject.connectSlotsByName(Frame)
+
+ def retranslateUi(self, Frame):
+ _translate = QtCore.QCoreApplication.translate
+ Frame.setWindowTitle(_translate("Frame", "Frame"))
+ self.tips.setText(_translate("Frame", "创建群聊成功"))
+ self.label_2.setText(_translate("Frame", "群 名:"))
+ self.group_name.setText(_translate("Frame", "请输入群名"))
+ self.label.setText(_translate("Frame", "群简介:"))
+ self.group_intro.setHtml(_translate("Frame", "\n"
+"\n"
+"在这里介绍你的群聊(不超过150字)
"))
+ self.btn_set_gAvatar.setText(_translate("Frame", "选择群头像"))
+ self.btn_create.setText(_translate("Frame", "创建"))
+ self.label_3.setText(_translate("Frame", "创建群聊"))
+ self.back.setText(_translate("Frame", "返回"))
+ self.toolButton.setText(_translate("Frame", "..."))
diff --git a/app/Ui/chat/group/create_groupUi.ui b/app/Ui/chat/group/create_groupUi.ui
new file mode 100644
index 0000000..80e35d3
--- /dev/null
+++ b/app/Ui/chat/group/create_groupUi.ui
@@ -0,0 +1,217 @@
+
+
+ Frame
+
+
+
+ 0
+ 0
+ 800
+ 720
+
+
+
+ Frame
+
+
+
+
+ 550
+ 80
+ 101
+ 16
+
+
+
+ false
+
+
+ color:red
+
+
+
+
+
+
+
+
+ 290
+ 630
+ 121
+ 16
+
+
+
+ 创建群聊成功
+
+
+
+
+
+ 140
+ 70
+ 401
+ 511
+
+
+
+ -
+
+
-
+
+
+
+ 15
+
+
+
+ 群 名:
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 请输入群名
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 15
+
+
+
+ 群简介:
+
+
+
+ -
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">在这里介绍你的群聊(不超过150字)</p></body></html>
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 100
+ 50
+
+
+
+
+ 100
+ 50
+
+
+
+ 选择群头像
+
+
+
+ -
+
+
+
+ 100
+ 100
+
+
+
+
+ 100
+ 100
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 创建
+
+
+
+
+
+
+
+
+ 260
+ 0
+ 221
+ 51
+
+
+
+
+ 一纸情书
+ 20
+
+
+
+ 创建群聊
+
+
+
+
+
+ 0
+ 0
+ 91
+ 28
+
+
+
+ 返回
+
+
+
+
+
+ 750
+ 0
+ 47
+ 21
+
+
+
+ ...
+
+
+
+
+
+
diff --git a/app/Ui/chat/myinfo/__init__.py b/app/Ui/chat/myinfo/__init__.py
new file mode 100644
index 0000000..7db11e6
--- /dev/null
+++ b/app/Ui/chat/myinfo/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/24 10:33
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/Ui/chat/myinfo/myinfo.py b/app/Ui/chat/myinfo/myinfo.py
new file mode 100644
index 0000000..a9ac0b6
--- /dev/null
+++ b/app/Ui/chat/myinfo/myinfo.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+"""
+@File : myinfo.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/23 11:45
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from .myinfoUi import *
+from ....DB import data
+
+
+class InfoControl(QWidget, Ui_Frame):
+ backSignal = pyqtSignal(str)
+
+ # createSignal = pyqtSignal(Group)
+ def __init__(self, parent=None, Me=None):
+ super(InfoControl, self).__init__(parent)
+ self.setupUi(self)
+ self.Me = Me
+ self.initUi()
+ self.btn_update.clicked.connect(self.update_)
+
+ def initUi(self):
+ self.ltips.setText('')
+ self.info = data.get_myinfo(self.Me.username)
+ nickname = self.info[1]
+ gender = self.info[2]
+ city = self.info[4]
+ province = self.info[5]
+ tel = self.info[6]
+ email = self.info[7]
+ signsture = self.info[8]
+ pixmap = QPixmap(self.Me.my_avatar).scaled(80, 80) # 按指定路径找到图片
+ self.l_avatar.setPixmap(pixmap) # 在label上显示图片
+ self.l_username.setText(f'账号:{self.Me.username}')
+ if gender=='男':
+ self.radioButton.setChecked(True)
+ elif gender=='女':
+ self.radioButton_2.setChecked(True)
+ self.line_nickname.setText(nickname)
+ self.line_city.setText(city)
+ self.line_tel.setText(tel)
+ self.line_province.setText(province)
+ self.line_email.setText(email)
+ self.line_signsture.setText(signsture)
+ def update_(self):
+ """更新信息"""
+ nickname = self.line_nickname.text()
+ if self.radioButton.isChecked():
+ gender = self.radioButton.text()
+ else:
+ gender = '女'
+ city = self.line_city.text()
+ province = self.line_province.text()
+ tel = self.line_tel.text()
+ email = self.line_email.text()
+ signsture = self.line_signsture.text()
+ userinfo = [
+ nickname,gender,city,province,tel,email,signsture,self.Me.username
+ ]
+ data.update_userinfo(userinfo)
+ self.ltips.setText('修改成功')
diff --git a/app/Ui/chat/myinfo/myinfoUi.py b/app/Ui/chat/myinfo/myinfoUi.py
new file mode 100644
index 0000000..9e055af
--- /dev/null
+++ b/app/Ui/chat/myinfo/myinfoUi.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'myinfoUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Frame(object):
+ def setupUi(self, Frame):
+ Frame.setObjectName("Frame")
+ Frame.resize(1120, 720)
+ self.l_avatar = QtWidgets.QLabel(Frame)
+ self.l_avatar.setGeometry(QtCore.QRect(370, 140, 80, 80))
+ self.l_avatar.setText("")
+ self.l_avatar.setPixmap(QtGui.QPixmap("../../../a_img/微信图片_20221215224740.jpg"))
+ self.l_avatar.setScaledContents(True)
+ self.l_avatar.setObjectName("l_avatar")
+ self.btn_update = QtWidgets.QPushButton(Frame)
+ self.btn_update.setGeometry(QtCore.QRect(370, 610, 93, 28))
+ self.btn_update.setObjectName("btn_update")
+ self.ltips = QtWidgets.QLabel(Frame)
+ self.ltips.setGeometry(QtCore.QRect(490, 620, 72, 15))
+ self.ltips.setText("")
+ self.ltips.setObjectName("ltips")
+ self.widget = QtWidgets.QWidget(Frame)
+ self.widget.setGeometry(QtCore.QRect(270, 260, 281, 311))
+ self.widget.setObjectName("widget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
+ self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.l_username = QtWidgets.QLabel(self.widget)
+ self.l_username.setObjectName("l_username")
+ self.verticalLayout.addWidget(self.l_username)
+ self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_6.setObjectName("horizontalLayout_6")
+ self.l_nickname = QtWidgets.QLabel(self.widget)
+ self.l_nickname.setObjectName("l_nickname")
+ self.horizontalLayout_6.addWidget(self.l_nickname)
+ self.line_nickname = QtWidgets.QLineEdit(self.widget)
+ self.line_nickname.setObjectName("line_nickname")
+ self.horizontalLayout_6.addWidget(self.line_nickname)
+ self.verticalLayout.addLayout(self.horizontalLayout_6)
+ self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_7.setObjectName("horizontalLayout_7")
+ self.l_gender = QtWidgets.QLabel(self.widget)
+ self.l_gender.setObjectName("l_gender")
+ self.horizontalLayout_7.addWidget(self.l_gender)
+ self.radioButton = QtWidgets.QRadioButton(self.widget)
+ self.radioButton.setObjectName("radioButton")
+ self.horizontalLayout_7.addWidget(self.radioButton)
+ self.radioButton_2 = QtWidgets.QRadioButton(self.widget)
+ self.radioButton_2.setObjectName("radioButton_2")
+ self.horizontalLayout_7.addWidget(self.radioButton_2)
+ self.verticalLayout.addLayout(self.horizontalLayout_7)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.l_nickname_3 = QtWidgets.QLabel(self.widget)
+ self.l_nickname_3.setObjectName("l_nickname_3")
+ self.horizontalLayout_2.addWidget(self.l_nickname_3)
+ self.line_city = QtWidgets.QLineEdit(self.widget)
+ self.line_city.setText("")
+ self.line_city.setObjectName("line_city")
+ self.horizontalLayout_2.addWidget(self.line_city)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.l_nickname_2 = QtWidgets.QLabel(self.widget)
+ self.l_nickname_2.setObjectName("l_nickname_2")
+ self.horizontalLayout.addWidget(self.l_nickname_2)
+ self.line_province = QtWidgets.QLineEdit(self.widget)
+ self.line_province.setObjectName("line_province")
+ self.horizontalLayout.addWidget(self.line_province)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.l_tel = QtWidgets.QLabel(self.widget)
+ self.l_tel.setObjectName("l_tel")
+ self.horizontalLayout_3.addWidget(self.l_tel)
+ self.line_tel = QtWidgets.QLineEdit(self.widget)
+ self.line_tel.setText("")
+ self.line_tel.setObjectName("line_tel")
+ self.horizontalLayout_3.addWidget(self.line_tel)
+ self.verticalLayout.addLayout(self.horizontalLayout_3)
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.l_nickname_5 = QtWidgets.QLabel(self.widget)
+ self.l_nickname_5.setObjectName("l_nickname_5")
+ self.horizontalLayout_4.addWidget(self.l_nickname_5)
+ self.line_email = QtWidgets.QLineEdit(self.widget)
+ self.line_email.setText("")
+ self.line_email.setObjectName("line_email")
+ self.horizontalLayout_4.addWidget(self.line_email)
+ self.verticalLayout.addLayout(self.horizontalLayout_4)
+ self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_5.setObjectName("horizontalLayout_5")
+ self.l_nickname_6 = QtWidgets.QLabel(self.widget)
+ self.l_nickname_6.setObjectName("l_nickname_6")
+ self.horizontalLayout_5.addWidget(self.l_nickname_6)
+ self.line_signsture = QtWidgets.QLineEdit(self.widget)
+ self.line_signsture.setText("")
+ self.line_signsture.setObjectName("line_signsture")
+ self.horizontalLayout_5.addWidget(self.line_signsture)
+ self.verticalLayout.addLayout(self.horizontalLayout_5)
+
+ self.retranslateUi(Frame)
+ QtCore.QMetaObject.connectSlotsByName(Frame)
+
+ def retranslateUi(self, Frame):
+ _translate = QtCore.QCoreApplication.translate
+ Frame.setWindowTitle(_translate("Frame", "Frame"))
+ self.btn_update.setText(_translate("Frame", "修改资料"))
+ self.l_username.setText(_translate("Frame", "账号:2020303457"))
+ self.l_nickname.setText(_translate("Frame", "昵称:"))
+ self.l_gender.setText(_translate("Frame", "性别:"))
+ self.radioButton.setText(_translate("Frame", "男"))
+ self.radioButton_2.setText(_translate("Frame", "女"))
+ self.l_nickname_3.setText(_translate("Frame", "城市:"))
+ self.l_nickname_2.setText(_translate("Frame", "省份:"))
+ self.l_tel.setText(_translate("Frame", "电话:"))
+ self.l_nickname_5.setText(_translate("Frame", "邮箱:"))
+ self.l_nickname_6.setText(_translate("Frame", "签名:"))
diff --git a/app/Ui/chat/myinfo/myinfoUi.ui b/app/Ui/chat/myinfo/myinfoUi.ui
new file mode 100644
index 0000000..1c862f9
--- /dev/null
+++ b/app/Ui/chat/myinfo/myinfoUi.ui
@@ -0,0 +1,211 @@
+
+
+ Frame
+
+
+
+ 0
+ 0
+ 1120
+ 720
+
+
+
+ Frame
+
+
+
+
+ 370
+ 140
+ 80
+ 80
+
+
+
+
+
+
+ ../../../a_img/微信图片_20221215224740.jpg
+
+
+ true
+
+
+
+
+
+ 370
+ 610
+ 93
+ 28
+
+
+
+ 修改资料
+
+
+
+
+
+ 490
+ 620
+ 72
+ 15
+
+
+
+
+
+
+
+
+
+ 270
+ 260
+ 281
+ 311
+
+
+
+
+ QLayout::SetMinAndMaxSize
+
+ -
+
+
+ 账号:2020303457
+
+
+
+ -
+
+
-
+
+
+ 昵称:
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+ 性别:
+
+
+
+ -
+
+
+ 男
+
+
+
+ -
+
+
+ 女
+
+
+
+
+
+ -
+
+
-
+
+
+ 城市:
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ 省份:
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+ 电话:
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ 邮箱:
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ 签名:
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/Ui/chat/userinfo/__init__.py b/app/Ui/chat/userinfo/__init__.py
new file mode 100644
index 0000000..6d32454
--- /dev/null
+++ b/app/Ui/chat/userinfo/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/24 10:34
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/Ui/chat/userinfo/userinfoUi.py b/app/Ui/chat/userinfo/userinfoUi.py
new file mode 100644
index 0000000..df3bb6d
--- /dev/null
+++ b/app/Ui/chat/userinfo/userinfoUi.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'userinfoUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Frame(object):
+ def setupUi(self, Frame):
+ Frame.setObjectName("Frame")
+ Frame.resize(800, 720)
+ Frame.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
+ Frame.setMouseTracking(True)
+ Frame.setTabletTracking(True)
+ self.widget = QtWidgets.QWidget(Frame)
+ self.widget.setGeometry(QtCore.QRect(210, 100, 429, 81))
+ self.widget.setObjectName("widget")
+ self.gridLayout = QtWidgets.QGridLayout(self.widget)
+ self.gridLayout.setContentsMargins(0, 0, 0, 0)
+ self.gridLayout.setObjectName("gridLayout")
+ self.l_avatar = QtWidgets.QLabel(self.widget)
+ self.l_avatar.setMinimumSize(QtCore.QSize(80, 80))
+ self.l_avatar.setMaximumSize(QtCore.QSize(80, 80))
+ self.l_avatar.setText("")
+ self.l_avatar.setPixmap(QtGui.QPixmap("../../../a_img/be0fa6c0c4707fb5f7b37b660de826d3.jpg"))
+ self.l_avatar.setScaledContents(True)
+ self.l_avatar.setObjectName("l_avatar")
+ self.gridLayout.addWidget(self.l_avatar, 0, 0, 3, 1)
+ self.l_remark = QtWidgets.QLabel(self.widget)
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.l_remark.setFont(font)
+ self.l_remark.setObjectName("l_remark")
+ self.gridLayout.addWidget(self.l_remark, 0, 1, 1, 1)
+ self.l_nickname = QtWidgets.QLabel(self.widget)
+ self.l_nickname.setObjectName("l_nickname")
+ self.gridLayout.addWidget(self.l_nickname, 1, 1, 1, 1)
+ self.l_username = QtWidgets.QLabel(self.widget)
+ self.l_username.setObjectName("l_username")
+ self.gridLayout.addWidget(self.l_username, 2, 1, 1, 1)
+ self.horizontalLayoutWidget = QtWidgets.QWidget(Frame)
+ self.horizontalLayoutWidget.setGeometry(QtCore.QRect(210, 210, 429, 71))
+ self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(self.horizontalLayoutWidget)
+ self.label.setMinimumSize(QtCore.QSize(80, 0))
+ self.label.setMaximumSize(QtCore.QSize(80, 16777215))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.label.setFont(font)
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.lineEdit = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
+ self.lineEdit.setMinimumSize(QtCore.QSize(0, 25))
+ self.lineEdit.setMaximumSize(QtCore.QSize(16777215, 25))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.lineEdit.setFont(font)
+ self.lineEdit.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.lineEdit.setAutoFillBackground(False)
+ self.lineEdit.setStyleSheet("background:transparent;border-width:0;border-style:outset")
+ self.lineEdit.setObjectName("lineEdit")
+ self.horizontalLayout.addWidget(self.lineEdit)
+ self.btn_update_remark = QtWidgets.QPushButton(self.horizontalLayoutWidget)
+ self.btn_update_remark.setMinimumSize(QtCore.QSize(140, 40))
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ self.btn_update_remark.setFont(font)
+ self.btn_update_remark.setObjectName("btn_update_remark")
+ self.horizontalLayout.addWidget(self.btn_update_remark)
+ self.btn_delete_contact = QtWidgets.QPushButton(Frame)
+ self.btn_delete_contact.setGeometry(QtCore.QRect(330, 370, 140, 40))
+ self.btn_delete_contact.setMinimumSize(QtCore.QSize(140, 40))
+ self.btn_delete_contact.setMaximumSize(QtCore.QSize(140, 40))
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ self.btn_delete_contact.setFont(font)
+ self.btn_delete_contact.setObjectName("btn_delete_contact")
+
+ self.retranslateUi(Frame)
+ QtCore.QMetaObject.connectSlotsByName(Frame)
+
+ def retranslateUi(self, Frame):
+ _translate = QtCore.QCoreApplication.translate
+ Frame.setWindowTitle(_translate("Frame", "Frame"))
+ self.l_remark.setText(_translate("Frame", "曹雨萱"))
+ self.l_nickname.setText(_translate("Frame", "昵称:997"))
+ self.l_username.setText(_translate("Frame", "账号:TextLabel"))
+ self.label.setText(_translate("Frame", "备注名"))
+ self.lineEdit.setText(_translate("Frame", "曹雨萱"))
+ self.btn_update_remark.setText(_translate("Frame", "修改备注"))
+ self.btn_delete_contact.setText(_translate("Frame", "删除好友"))
diff --git a/app/Ui/chat/userinfo/userinfoUi.ui b/app/Ui/chat/userinfo/userinfoUi.ui
new file mode 100644
index 0000000..c746e0e
--- /dev/null
+++ b/app/Ui/chat/userinfo/userinfoUi.ui
@@ -0,0 +1,208 @@
+
+
+ Frame
+
+
+
+ 0
+ 0
+ 800
+ 720
+
+
+
+ IBeamCursor
+
+
+ true
+
+
+ true
+
+
+ Frame
+
+
+
+
+ 210
+ 100
+ 429
+ 81
+
+
+
+ -
+
+
+
+ 80
+ 80
+
+
+
+
+ 80
+ 80
+
+
+
+
+
+
+ ../../../a_img/be0fa6c0c4707fb5f7b37b660de826d3.jpg
+
+
+ true
+
+
+
+ -
+
+
+
+ 15
+
+
+
+ 曹雨萱
+
+
+
+ -
+
+
+ 昵称:997
+
+
+
+ -
+
+
+ 账号:TextLabel
+
+
+
+
+
+
+
+
+ 210
+ 210
+ 429
+ 71
+
+
+
+ -
+
+
+
+ 80
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 15
+
+
+
+ 备注名
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+
+ 16777215
+ 25
+
+
+
+
+ 15
+
+
+
+ ArrowCursor
+
+
+ false
+
+
+ background:transparent;border-width:0;border-style:outset
+
+
+ 曹雨萱
+
+
+
+ -
+
+
+
+ 140
+ 40
+
+
+
+
+ 12
+
+
+
+ 修改备注
+
+
+
+
+
+
+
+
+ 330
+ 370
+ 140
+ 40
+
+
+
+
+ 140
+ 40
+
+
+
+
+ 140
+ 40
+
+
+
+
+ 12
+
+
+
+ 删除好友
+
+
+
+
+
+
diff --git a/app/Ui/decrypt/decrypt.py b/app/Ui/decrypt/decrypt.py
new file mode 100644
index 0000000..4538f96
--- /dev/null
+++ b/app/Ui/decrypt/decrypt.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+"""
+@File : decrypt.py
+@Author : Shuaikang Zhou
+@Time : 2023/1/5 18:13
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import hashlib
+import os
+import time
+
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from . import decryptUi
+from ...DataBase import data
+import xml.etree.ElementTree as ET
+
+
+class DecryptControl(QWidget, decryptUi.Ui_Dialog):
+ DecryptSignal = pyqtSignal(str)
+ registerSignal = pyqtSignal(str)
+
+ def __init__(self, parent=None):
+ super(DecryptControl, self).__init__(parent)
+ self.setupUi(self)
+ self.setWindowTitle('解密')
+ self.setWindowIcon(QIcon('./app/data/icon.png'))
+ self.btn_db.clicked.connect(self.get_db)
+ self.btn_xml.clicked.connect(self.get_xml)
+ self.pushButton_3.clicked.connect(self.decrypt)
+ self.xml_path = None
+ self.db_path = None
+ # self.db_exist()
+
+ def db_exist(self):
+ if os.path.exists('./app/DataBase/Msg.db'):
+ self.btnEnterClicked()
+ self.close()
+
+ def get_xml(self):
+ self.xml_path, _ = QFileDialog.getOpenFileName(self, 'Open file', r'..', "Xml files (*.xml)")
+ if self.xml_path:
+ self.label_xml.setText('xml已就绪')
+ key = self.parser_xml()
+ self.label_key.setText(f'数据库密钥:{key}')
+ return self.xml_path
+ return False
+
+ def get_db(self):
+ self.db_path, _ = QFileDialog.getOpenFileName(self, 'Open file', r'..', "Database files (*.db)")
+ if self.db_path:
+ self.label_db.setText('数据库已就绪')
+ return self.db_path
+ return False
+
+ def decrypt(self):
+ if not (self.xml_path and self.db_path):
+ QMessageBox.critical(self, "错误", "请把两个文件加载进来")
+ return
+ key = self.parser_xml()
+ self.label_key.setText(f'数据库密钥:{key}')
+ self.thread1 = MyThread()
+ self.thread1.signal.connect(self.progressBar_view)
+ self.thread1.start()
+ self.thread2 = DecryptThread(self.db_path, key)
+ self.thread2.signal.connect(self.progressBar_view)
+ self.thread2.start()
+
+ def parser_xml(self):
+ if not self.xml_path:
+ return False
+ pid = self.pid(self.xml_path)
+ print(pid)
+ if not pid:
+ return False
+ key = self.key(pid)
+ print(key)
+ return key
+
+ def pid(self, xml_path):
+ tree = ET.parse(xml_path)
+ # 根节点
+ root = tree.getroot()
+ # 标签名
+ print('root_tag:', root.tag)
+ for stu in root:
+ if stu.attrib["name"] == '_auth_uin':
+ return stu.attrib['value']
+ return False
+
+ def key(self, uin, IMEI='1234567890ABCDEF'):
+ print(IMEI, uin)
+ m = hashlib.md5()
+ m.update(bytes((IMEI + uin).encode('utf-8')))
+ psw = m.hexdigest()
+ return psw[:7]
+
+ def btnEnterClicked(self):
+ print("enter clicked")
+ # 中间可以添加处理逻辑
+ self.DecryptSignal.emit('ok')
+ self.close()
+
+ def progressBar_view(self, value):
+ """
+ 进度条显示
+ :param value: 进度0-100
+ :return: None
+ """
+ self.progressBar.setProperty('value', value)
+ if value == '100':
+ QMessageBox.information(self, "解密成功", "请退出该界面",
+ QMessageBox.Yes)
+ self.btnExitClicked()
+
+ def btnExitClicked(self):
+ print("Exit clicked")
+ self.close()
+
+
+class DecryptThread(QThread):
+ signal = pyqtSignal(str)
+
+ def __init__(self, db_path, key):
+ super(DecryptThread, self).__init__()
+ self.db_path = db_path
+ self.key = key
+ self.textBrowser = None
+
+ def __del__(self):
+ pass
+
+ def run(self):
+ data.decrypt(self.db_path, self.key)
+ self.signal.emit('100')
+
+
+class MyThread(QThread):
+ signal = pyqtSignal(str)
+
+ def __init__(self):
+ super(MyThread, self).__init__()
+
+ def __del__(self):
+ pass
+
+ def run(self):
+ for i in range(100):
+ self.signal.emit(str(i))
+ time.sleep(0.1)
diff --git a/app/Ui/decrypt/decryptUi.py b/app/Ui/decrypt/decryptUi.py
new file mode 100644
index 0000000..433f18c
--- /dev/null
+++ b/app/Ui/decrypt/decryptUi.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'decryptUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(400, 300)
+ self.label_3 = QtWidgets.QLabel(Dialog)
+ self.label_3.setGeometry(QtCore.QRect(110, 20, 221, 51))
+ font = QtGui.QFont()
+ font.setFamily("一纸情书")
+ font.setPointSize(20)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.progressBar = QtWidgets.QProgressBar(Dialog)
+ self.progressBar.setGeometry(QtCore.QRect(90, 260, 271, 23))
+ self.progressBar.setProperty("value", 50)
+ self.progressBar.setObjectName("progressBar")
+ self.label_key = QtWidgets.QLabel(Dialog)
+ self.label_key.setGeometry(QtCore.QRect(80, 230, 241, 20))
+ self.label_key.setText("")
+ self.label_key.setObjectName("label_key")
+ self.widget = QtWidgets.QWidget(Dialog)
+ self.widget.setGeometry(QtCore.QRect(80, 80, 245, 134))
+ self.widget.setObjectName("widget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.btn_xml = QtWidgets.QPushButton(self.widget)
+ self.btn_xml.setObjectName("btn_xml")
+ self.horizontalLayout.addWidget(self.btn_xml)
+ self.label_xml = QtWidgets.QLabel(self.widget)
+ self.label_xml.setObjectName("label_xml")
+ self.horizontalLayout.addWidget(self.label_xml)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.btn_db = QtWidgets.QPushButton(self.widget)
+ self.btn_db.setObjectName("btn_db")
+ self.horizontalLayout_2.addWidget(self.btn_db)
+ self.label_db = QtWidgets.QLabel(self.widget)
+ self.label_db.setObjectName("label_db")
+ self.horizontalLayout_2.addWidget(self.label_db)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.pushButton_3 = QtWidgets.QPushButton(self.widget)
+ self.pushButton_3.setObjectName("pushButton_3")
+ self.verticalLayout.addWidget(self.pushButton_3)
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.label_3.setText(_translate("Dialog", "解密数据库"))
+ self.btn_xml.setText(_translate("Dialog", "点击加载xml文件"))
+ self.label_xml.setText(_translate("Dialog", "xml未就绪"))
+ self.btn_db.setText(_translate("Dialog", "点击加载数据库文件"))
+ self.label_db.setText(_translate("Dialog", "数据库未就绪"))
+ self.pushButton_3.setText(_translate("Dialog", "开始解密数据库"))
diff --git a/app/Ui/decrypt/decryptUi.ui b/app/Ui/decrypt/decryptUi.ui
new file mode 100644
index 0000000..92552dc
--- /dev/null
+++ b/app/Ui/decrypt/decryptUi.ui
@@ -0,0 +1,119 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Dialog
+
+
+
+
+ 110
+ 20
+ 221
+ 51
+
+
+
+
+ 一纸情书
+ 20
+
+
+
+ 解密数据库
+
+
+
+
+
+ 90
+ 260
+ 271
+ 23
+
+
+
+ 50
+
+
+
+
+
+ 80
+ 230
+ 241
+ 20
+
+
+
+
+
+
+
+
+
+ 80
+ 80
+ 245
+ 134
+
+
+
+ -
+
+
-
+
+
+ 点击加载xml文件
+
+
+
+ -
+
+
+ xml未就绪
+
+
+
+
+
+ -
+
+
-
+
+
+ 点击加载数据库文件
+
+
+
+ -
+
+
+ 数据库未就绪
+
+
+
+
+
+ -
+
+
+ 开始解密数据库
+
+
+
+
+
+
+
+
+
diff --git a/app/Ui/mainview.py b/app/Ui/mainview.py
new file mode 100644
index 0000000..3253391
--- /dev/null
+++ b/app/Ui/mainview.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+"""
+@File : mainview.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 15:07
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
+import socket # 导入socket模块
+import datetime
+import json
+
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from .mainviewUi import *
+from app.DataBase import data
+from .chat import chat
+
+
+class MainWinController(QMainWindow, Ui_Dialog):
+ exitSignal = pyqtSignal()
+
+ # username = ''
+ def __init__(self, username, parent=None):
+ super(MainWinController, self).__init__(parent)
+ self.setupUi(self)
+ self.setWindowTitle('WeChat')
+ self.setWindowIcon(QIcon('./app/data/icon.png'))
+ self.Me = data.get_myinfo()
+ self.chatView = chat.ChatController(self.Me, parent=self.frame_main)
+ self.chatView.setVisible(False)
+
+ self.btn_chat.clicked.connect(self.chat_view) # 聊天按钮
+ self.btn_contact.clicked.connect(self.contact_view)
+ self.btn_myinfo.clicked.connect(self.myInfo)
+ self.btn_about.clicked.connect(self.about)
+ self.now_btn = self.btn_chat
+ self.btn_about.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.btn_about.customContextMenuRequested.connect(self.create_rightmenu) # 连接到菜单显示函数
+ self.last_btn = None
+ self.show_avatar()
+
+ # 创建右键菜单函数
+ def create_rightmenu(self):
+ # 菜单对象
+ self.groupBox_menu = QMenu(self)
+
+ self.actionA = QAction(QIcon('image/保存.png'), u'保存数据',
+ self) # self.actionA = self.contextMenu.addAction(QIcon("images/0.png"),u'| 动作A')
+ self.actionA.setShortcut('Ctrl+S') # 设置快捷键
+ self.groupBox_menu.addAction(self.actionA) # 把动作A选项添加到菜单
+
+ self.actionB = QAction(QIcon('image/删除.png'), u'删除数据', self)
+ self.groupBox_menu.addAction(self.actionB)
+
+ # self.actionA.triggered.connect(self.button) # 将动作A触发时连接到槽函数 button
+ # self.actionB.triggered.connect(self.button_2)
+
+ self.groupBox_menu.popup(QCursor.pos()) # 声明当鼠标在groupBox控件上右击时,在鼠标位置显示右键菜单 ,exec_,popup两个都可以,
+ def show_avatar(self):
+ avatar = data.get_avator(self.Me.username)
+ pixmap = QPixmap(avatar).scaled(80, 80) # 按指定路径找到图片
+ self.myavatar.setPixmap(pixmap) # 在label上显示图片
+
+ def chat_view(self):
+ self.now_btn = self.btn_chat
+ self.now_btn.setStyleSheet(
+ "QPushButton {background-color: rgb(198,198,198);}")
+ if self.last_btn and self.last_btn != self.now_btn:
+ self.last_btn.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n")
+ self.last_btn = self.btn_chat
+ self.setviewVisible(self.chatView)
+ self.chatView.showChat()
+
+ def contact_view(self):
+ self.now_btn = self.btn_contact
+ self.now_btn.setStyleSheet(
+ "QPushButton {background-color: rgb(198,198,198);}")
+ if self.last_btn and self.last_btn != self.now_btn:
+ self.last_btn.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n")
+ self.last_btn = self.btn_contact
+
+ def myInfo(self):
+ self.now_btn = self.btn_myinfo
+ self.now_btn.setStyleSheet(
+ "QPushButton {background-color: rgb(198,198,198);}")
+ if self.last_btn and self.last_btn != self.now_btn:
+ self.last_btn.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}"
+ "QPushButton:hover{background-color: rgb(209,209,209);}\n")
+ self.last_btn = self.now_btn
+
+ def about(self):
+ QMessageBox.about(self, "关于",
+ "关于作者\n姓名:周帅康\n学号:2020303457"
+ )
+
+ def setviewVisible(self, view):
+ view.setVisible(True)
+ if view != self.chatView:
+ self.chatView.setVisible(False)
diff --git a/app/Ui/mainviewUi.py b/app/Ui/mainviewUi.py
new file mode 100644
index 0000000..1a24102
--- /dev/null
+++ b/app/Ui/mainviewUi.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'mainviewUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(1280, 720)
+ Dialog.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ Dialog.setAutoFillBackground(False)
+ self.frame_main = QtWidgets.QFrame(Dialog)
+ self.frame_main.setGeometry(QtCore.QRect(160, 0, 1120, 720))
+ self.frame_main.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_main.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_main.setObjectName("frame_main")
+ self.frame_info = QtWidgets.QFrame(Dialog)
+ self.frame_info.setGeometry(QtCore.QRect(0, 0, 161, 721))
+ self.frame_info.setStyleSheet("background-color:rgb(240,240,240)")
+ self.frame_info.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_info.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.frame_info.setObjectName("frame_info")
+ self.verticalLayoutWidget = QtWidgets.QWidget(self.frame_info)
+ self.verticalLayoutWidget.setGeometry(QtCore.QRect(20, 190, 111, 501))
+ self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_2.setSpacing(0)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.btn_chat = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_chat.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_chat.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}\n"
+"QPushButton:hover{background-color: rgb(209,209,209);}")
+ self.btn_chat.setObjectName("btn_chat")
+ self.verticalLayout_2.addWidget(self.btn_chat)
+ self.btn_contact = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_contact.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_contact.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}\n"
+"QPushButton:hover{background-color: rgb(209,209,209);}")
+ self.btn_contact.setObjectName("btn_contact")
+ self.verticalLayout_2.addWidget(self.btn_contact)
+ self.btn_addC = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_addC.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_addC.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}\n"
+"QPushButton:hover{background-color: rgb(209,209,209);}")
+ self.btn_addC.setObjectName("btn_addC")
+ self.verticalLayout_2.addWidget(self.btn_addC)
+ self.btn_add_group = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_add_group.setMinimumSize(QtCore.QSize(0, 80))
+ self.btn_add_group.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}\n"
+"QPushButton:hover{background-color: rgb(209,209,209);}")
+ self.btn_add_group.setObjectName("btn_add_group")
+ self.verticalLayout_2.addWidget(self.btn_add_group)
+ self.btn_myinfo = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_myinfo.setMinimumSize(QtCore.QSize(100, 80))
+ self.btn_myinfo.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}\n"
+"QPushButton:hover{background-color: rgb(209,209,209);}")
+ self.btn_myinfo.setObjectName("btn_myinfo")
+ self.verticalLayout_2.addWidget(self.btn_myinfo)
+ self.btn_about = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.btn_about.setMinimumSize(QtCore.QSize(100, 80))
+ self.btn_about.setStyleSheet("QPushButton {background-color: rgb(240,240,240);}\n"
+"QPushButton:hover{background-color: rgb(209,209,209);}")
+ self.btn_about.setObjectName("btn_about")
+ self.verticalLayout_2.addWidget(self.btn_about)
+ self.verticalLayout_2.setStretch(0, 1)
+ self.verticalLayout_2.setStretch(2, 1)
+ self.verticalLayout_2.setStretch(4, 1)
+ self.myavatar = QtWidgets.QLabel(self.frame_info)
+ self.myavatar.setGeometry(QtCore.QRect(30, 50, 100, 100))
+ self.myavatar.setObjectName("myavatar")
+ self.sign_up = QtWidgets.QPushButton(self.frame_info)
+ self.sign_up.setGeometry(QtCore.QRect(0, 0, 71, 28))
+ self.sign_up.setObjectName("sign_up")
+ self.btn_destroy = QtWidgets.QPushButton(self.frame_info)
+ self.btn_destroy.setGeometry(QtCore.QRect(80, 0, 71, 28))
+ self.btn_destroy.setObjectName("btn_destroy")
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.btn_chat.setText(_translate("Dialog", "聊天"))
+ self.btn_contact.setText(_translate("Dialog", "联系人"))
+ self.btn_addC.setText(_translate("Dialog", "添加联系人"))
+ self.btn_add_group.setText(_translate("Dialog", "群聊"))
+ self.btn_myinfo.setText(_translate("Dialog", "我的"))
+ self.btn_about.setText(_translate("Dialog", "关于"))
+ self.myavatar.setText(_translate("Dialog", "avatar"))
+ self.sign_up.setText(_translate("Dialog", "退出登录"))
+ self.btn_destroy.setText(_translate("Dialog", "注销账户"))
diff --git a/app/Ui/mainviewUi.ui b/app/Ui/mainviewUi.ui
new file mode 100644
index 0000000..90406dd
--- /dev/null
+++ b/app/Ui/mainviewUi.ui
@@ -0,0 +1,216 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 1280
+ 720
+
+
+
+ ArrowCursor
+
+
+ Dialog
+
+
+ false
+
+
+
+
+ 160
+ 0
+ 1120
+ 720
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+
+
+ 0
+ 0
+ 161
+ 721
+
+
+
+ background-color:rgb(240,240,240)
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Sunken
+
+
+
+
+ 20
+ 190
+ 111
+ 501
+
+
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ QPushButton {background-color: rgb(240,240,240);}
+QPushButton:hover{background-color: rgb(209,209,209);}
+
+
+ 聊天
+
+
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ QPushButton {background-color: rgb(240,240,240);}
+QPushButton:hover{background-color: rgb(209,209,209);}
+
+
+ 联系人
+
+
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ QPushButton {background-color: rgb(240,240,240);}
+QPushButton:hover{background-color: rgb(209,209,209);}
+
+
+ 添加联系人
+
+
+
+ -
+
+
+
+ 0
+ 80
+
+
+
+ QPushButton {background-color: rgb(240,240,240);}
+QPushButton:hover{background-color: rgb(209,209,209);}
+
+
+ 群聊
+
+
+
+ -
+
+
+
+ 100
+ 80
+
+
+
+ QPushButton {background-color: rgb(240,240,240);}
+QPushButton:hover{background-color: rgb(209,209,209);}
+
+
+ 我的
+
+
+
+ -
+
+
+
+ 100
+ 80
+
+
+
+ QPushButton {background-color: rgb(240,240,240);}
+QPushButton:hover{background-color: rgb(209,209,209);}
+
+
+ 关于
+
+
+
+
+
+
+
+
+ 30
+ 50
+ 100
+ 100
+
+
+
+ avatar
+
+
+
+
+
+ 0
+ 0
+ 71
+ 28
+
+
+
+ 退出登录
+
+
+
+
+
+ 80
+ 0
+ 71
+ 28
+
+
+
+ 注销账户
+
+
+
+
+
+
+
diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 0000000..2def13e
--- /dev/null
+++ b/app/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2023/1/5 17:43
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/main/__init__.py b/app/main/__init__.py
new file mode 100644
index 0000000..d4d7b49
--- /dev/null
+++ b/app/main/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2023/1/5 18:11
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/auth_info_key_prefs.xml b/auth_info_key_prefs.xml
new file mode 100644
index 0000000..61bcf9c
--- /dev/null
+++ b/auth_info_key_prefs.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..498f0a7
--- /dev/null
+++ b/main.py
@@ -0,0 +1,51 @@
+import os
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+import sys
+from app.Ui import *
+
+
+class ViewController:
+ def loadDecryptView(self):
+ """
+ 登录界面
+ :return:
+ """
+ self.viewDecrypt = decrypt.DecryptControl() # 需要将viewlogin设为成员变量
+ self.viewDecrypt.DecryptSignal.connect(self.loadMainWinView)
+ self.viewDecrypt.registerSignal.connect(self.loadRegisterView)
+ self.viewDecrypt.show()
+ self.viewDecrypt.db_exist()
+
+ def loadRegisterView(self):
+ """
+ 注册界面
+ :return:
+ """
+ self.viewDecrypt = register.registerControl() # 需要将viewlogin设为成员变量
+ self.viewDecrypt.DecryptSignal.connect(self.loadDecryptView)
+ self.viewDecrypt.show()
+
+ def loadMainWinView(self, username):
+ """
+ 聊天界面
+ :param username: 账号
+ :return:
+ """
+ username = ''
+ self.viewMainWIn = mainview.MainWinController(username=username)
+ self.viewMainWIn.setWindowTitle("Chat")
+ # print(username)
+ self.viewMainWIn.username = username
+ # self.viewMainWIn.exitSignal.connect(self.loadDecryptView) # 不需要回到登录界面可以省略
+ self.viewMainWIn.show()
+ # self.viewMainWIn.signUp()
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ view = ViewController()
+ # view.loadDecryptView() # 进入登录界面,如果viewlogin不是成员变量,则离开作用域后失效。
+ view.loadMainWinView('102')
+ sys.exit(app.exec_())
diff --git a/sqlcipher-3.0.1/bin/adb.txt b/sqlcipher-3.0.1/bin/adb.txt
new file mode 100644
index 0000000..184bb5a
--- /dev/null
+++ b/sqlcipher-3.0.1/bin/adb.txt
@@ -0,0 +1,5 @@
+PRAGMA key = '10f35f1';
+PRAGMA cipher_migrate;
+ATTACH DATABASE 'plaintext.db' AS plaintext KEY '';
+SELECT sqlcipher_export('plaintext');
+DETACH DATABASE plaintext;
\ No newline at end of file
diff --git a/sqlcipher-3.0.1/bin/bat使用说明.txt b/sqlcipher-3.0.1/bin/bat使用说明.txt
new file mode 100644
index 0000000..e99e94c
--- /dev/null
+++ b/sqlcipher-3.0.1/bin/bat使用说明.txt
@@ -0,0 +1,3 @@
+1.ҪܵƸΪencrypt.dbŵǰbinĿ¼
+2.༭adb.txt ļ PRAGMA key = 'password';passwordǼݵ
+3.ı꣬˫sqlcipher.batļ
\ No newline at end of file
diff --git a/sqlcipher-3.0.1/bin/sqlcipher - 副本.txt b/sqlcipher-3.0.1/bin/sqlcipher - 副本.txt
new file mode 100644
index 0000000..cab6c11
--- /dev/null
+++ b/sqlcipher-3.0.1/bin/sqlcipher - 副本.txt
@@ -0,0 +1,3 @@
+sqlcipher-shell32.exe encrypt.db < adb.txt
+echo
+pause
diff --git a/sqlcipher-3.0.1/bin/sqlcipher-shell32-debug.exe b/sqlcipher-3.0.1/bin/sqlcipher-shell32-debug.exe
new file mode 100644
index 0000000000000000000000000000000000000000..b4cf0b0118716489afe82d3d57300c13a061b842
GIT binary patch
literal 2108416
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