mirror of
https://github.com/LC044/WeChatMsg
synced 2025-02-22 19:02:17 +08:00
Merge branch 'LC044:master' into master
This commit is contained in:
commit
f1c0ba2678
@ -15,9 +15,20 @@ from .media_msg import MediaMsg
|
||||
from .misc import Misc
|
||||
from .msg import Msg
|
||||
from .msg import MsgType
|
||||
|
||||
misc_db = Misc()
|
||||
msg_db = Msg()
|
||||
micro_msg_db = MicroMsg()
|
||||
hard_link_db = HardLink()
|
||||
media_msg_db = MediaMsg()
|
||||
__all__ = ["data", 'output', 'misc_db', 'micro_msg_db', 'msg_db', 'hard_link_db','MsgType', "media_msg_db"]
|
||||
|
||||
|
||||
def close_db():
|
||||
misc_db.close()
|
||||
msg_db.close()
|
||||
micro_msg_db.close()
|
||||
hard_link_db.close()
|
||||
media_msg_db.close()
|
||||
|
||||
|
||||
__all__ = ['output', 'misc_db', 'micro_msg_db', 'msg_db', 'hard_link_db', 'MsgType', "media_msg_db"]
|
||||
|
@ -100,7 +100,17 @@ class MediaMsg:
|
||||
return transtext
|
||||
except:
|
||||
return ""
|
||||
def close(self):
|
||||
if self.open_flag:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
self.open_flag = False
|
||||
self.DB.close()
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
db_path = './Msg/MediaMSG.db'
|
||||
|
@ -3,8 +3,6 @@ import sqlite3
|
||||
import threading
|
||||
|
||||
lock = threading.Lock()
|
||||
DB = None
|
||||
cursor = None
|
||||
db_path = "./app/Database/Msg/MicroMsg.db"
|
||||
|
||||
|
||||
@ -22,61 +20,6 @@ def singleton(cls):
|
||||
def is_database_exist():
|
||||
return os.path.exists(db_path)
|
||||
|
||||
lockMSG = threading.Lock()
|
||||
DBMSG = None
|
||||
cursorMSG = None
|
||||
db_msg_path = "./app/Database/Msg/MSG.db"
|
||||
|
||||
@singleton
|
||||
class MicroMSGMsg:
|
||||
def __init__(self):
|
||||
self.DBMSG = None
|
||||
self.cursorMSG = None
|
||||
self.open_flag = False
|
||||
self.init_database()
|
||||
|
||||
def init_database(self):
|
||||
if not self.open_flag:
|
||||
if os.path.exists(db_msg_path):
|
||||
self.DBMSG = sqlite3.connect(db_msg_path, check_same_thread=False)
|
||||
# '''创建游标'''
|
||||
self.cursorMSG = self.DBMSG.cursor()
|
||||
self.open_flag = True
|
||||
if lockMSG.locked():
|
||||
lockMSG.release()
|
||||
|
||||
def get_contact(self, contacts):
|
||||
if not self.open_flag:
|
||||
return None
|
||||
try:
|
||||
lockMSG.acquire(True)
|
||||
sql = '''select StrTalker, MAX(CreateTime) from MSG group by StrTalker'''
|
||||
self.cursorMSG.execute(sql)
|
||||
res = self.cursorMSG.fetchall()
|
||||
res = {StrTalker: CreateTime for StrTalker, CreateTime in res}
|
||||
contacts = [list(cur_contact) for cur_contact in contacts]
|
||||
for i, cur_contact in enumerate(contacts):
|
||||
if cur_contact[0] in res:
|
||||
contacts[i].append(res[cur_contact[0]])
|
||||
else:
|
||||
contacts[i].append(0)
|
||||
contacts.sort(key=lambda cur_contact: cur_contact[-1], reverse=True)
|
||||
finally:
|
||||
lockMSG.release()
|
||||
return contacts
|
||||
|
||||
def close(self):
|
||||
if self.open_flag:
|
||||
try:
|
||||
lockMSG.acquire(True)
|
||||
self.open_flag = False
|
||||
self.DBMSG.close()
|
||||
finally:
|
||||
lockMSG.release()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
|
||||
@singleton
|
||||
class MicroMsg:
|
||||
@ -116,7 +59,8 @@ class MicroMsg:
|
||||
result = self.cursor.fetchall()
|
||||
finally:
|
||||
lock.release()
|
||||
return MicroMSGMsg().get_contact(result)
|
||||
from app.DataBase import msg_db
|
||||
return msg_db.get_contact(result)
|
||||
|
||||
def get_contact_by_username(self, username):
|
||||
if not self.open_flag:
|
||||
|
@ -208,7 +208,25 @@ class Msg:
|
||||
))
|
||||
print(keyword,res)
|
||||
return res
|
||||
|
||||
def get_contact(self, contacts):
|
||||
if not self.open_flag:
|
||||
return None
|
||||
try:
|
||||
lock.acquire(True)
|
||||
sql = '''select StrTalker, MAX(CreateTime) from MSG group by StrTalker'''
|
||||
self.cursor.execute(sql)
|
||||
res = self.cursor.fetchall()
|
||||
finally:
|
||||
lock.release()
|
||||
res = {StrTalker: CreateTime for StrTalker, CreateTime in res}
|
||||
contacts = [list(cur_contact) for cur_contact in contacts]
|
||||
for i, cur_contact in enumerate(contacts):
|
||||
if cur_contact[0] in res:
|
||||
contacts[i].append(res[cur_contact[0]])
|
||||
else:
|
||||
contacts[i].append(0)
|
||||
contacts.sort(key=lambda cur_contact: cur_contact[-1], reverse=True)
|
||||
return contacts
|
||||
def get_messages_by_days(self, username_, is_Annual_report_=False, year_='2023'):
|
||||
if is_Annual_report_:
|
||||
sql = '''
|
||||
|
@ -383,17 +383,23 @@ class ChildThread(QThread):
|
||||
if video_path is None and image_path is not None:
|
||||
image_path = path.get_relative_path(image_path, base_path=f'/data/聊天记录/{self.contact.remark}/image')
|
||||
image_path = image_path
|
||||
os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp))
|
||||
print(origin_docx_path + image_path[1:])
|
||||
image_path = image_path.replace('\\', '/')
|
||||
# print(f"tohtml:---{image_path}")
|
||||
if self.is_5_min(timestamp):
|
||||
try:
|
||||
# todo 网络图片问题
|
||||
print(origin_docx_path + image_path[1:])
|
||||
os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp))
|
||||
image_path = image_path.replace('\\', '/')
|
||||
# print(f"tohtml:---{image_path}")
|
||||
if self.is_5_min(timestamp):
|
||||
doc.write(
|
||||
f'''{{ type:0, text: '{str_time}',is_send:0,avatar_path:''}},'''
|
||||
)
|
||||
doc.write(
|
||||
f'''{{ type:0, text: '{str_time}',is_send:0,avatar_path:''}},'''
|
||||
f'''{{ type:3, text: '{image_path}',is_send:{is_send},avatar_path:'{avatar}'}},'''
|
||||
)
|
||||
except:
|
||||
doc.write(
|
||||
f'''{{ type:1, text: '视频丢失',is_send:{is_send},avatar_path:'{avatar}'}},'''
|
||||
)
|
||||
doc.write(
|
||||
f'''{{ type:3, text: '{image_path}',is_send:{is_send},avatar_path:'{avatar}'}},'''
|
||||
)
|
||||
return
|
||||
if video_path is None and image_path is None:
|
||||
return
|
||||
|
@ -168,7 +168,7 @@ def get_key(db_path, addr_len):
|
||||
return key_bytes
|
||||
|
||||
def verify_key(key, wx_db_path):
|
||||
if not wx_db_path:
|
||||
if wx_db_path == "None":
|
||||
return True
|
||||
KEY_SIZE = 32
|
||||
DEFAULT_PAGESIZE = 4096
|
||||
@ -287,6 +287,8 @@ def read_info(version_list, is_logging=False):
|
||||
print("=" * 32)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -297,7 +299,6 @@ def resource_path(relative_path):
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
|
||||
|
||||
def get_info(VERSION_LIST):
|
||||
result = read_info(VERSION_LIST, True) # 读取微信信息
|
||||
return result
|
||||
|
@ -19,6 +19,7 @@
|
||||
<file>version_list.json</file>
|
||||
<file>icons/logo.ico</file>
|
||||
<file>icons/logo.png</file>
|
||||
<file>icons/logo99.png</file>
|
||||
<file>icons/tool.svg</file>
|
||||
<file>icons/home.svg</file>
|
||||
<file>icons/help.svg</file>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,8 @@ var = resource_rc.qt_resource_name
|
||||
class Icon:
|
||||
Default_avatar_path = ':/icons/icons/default_avatar.svg'
|
||||
Default_image_path = ':/icons/icons/404.png'
|
||||
logo_path = ':/icons/icons/logo99.png'
|
||||
logo_ico_path = ':/icons/icons/logo.ico'
|
||||
MainWindow_Icon = QIcon(':/icons/icons/logo.svg')
|
||||
Default_avatar = QIcon(Default_avatar_path)
|
||||
Output = QIcon(':/icons/icons/output.svg')
|
||||
@ -30,3 +32,4 @@ class Icon:
|
||||
Folder_Icon = QIcon(':/icons/icons/folder.svg')
|
||||
Start_Icon = QIcon(':/icons/icons/start.svg')
|
||||
Decrypt_Icon = QIcon(':/icons/icons/decrypt.svg')
|
||||
# Logo_Icon = QIcon(':/icons/icons/logo.png')
|
||||
|
@ -11,11 +11,11 @@ import json
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, Qt, QThread, QSize
|
||||
from PyQt5.QtGui import QPixmap, QFont, QDesktopServices
|
||||
from PyQt5.QtGui import QPixmap, QFont, QDesktopServices, QIcon
|
||||
from PyQt5.QtWidgets import QMainWindow, QLabel, QListWidgetItem, QMessageBox
|
||||
|
||||
from app import config
|
||||
from app.DataBase import msg_db, misc_db, micro_msg_db, hard_link_db
|
||||
from app.DataBase import msg_db, misc_db, micro_msg_db, hard_link_db, close_db
|
||||
from app.ui.Icon import Icon
|
||||
from . import mainwindow
|
||||
from .chat import ChatWindow
|
||||
@ -79,7 +79,10 @@ class MainWinController(QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.outputThread0 = None
|
||||
self.outputThread = None
|
||||
self.setupUi(self)
|
||||
self.setWindowIcon(Icon.MainWindow_Icon)
|
||||
# self.setWindowIcon(Icon.MainWindow_Icon)
|
||||
pixmap = QPixmap(Icon.logo_ico_path)
|
||||
icon = QIcon(pixmap)
|
||||
self.setWindowIcon(icon)
|
||||
self.setStyleSheet(Stylesheet)
|
||||
self.listWidget.clear()
|
||||
self.resize(QSize(800, 600))
|
||||
@ -231,12 +234,19 @@ class MainWinController(QMainWindow, mainwindow.Ui_MainWindow):
|
||||
QMessageBox.about(self, "解密成功", "请重新启动")
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, event):
|
||||
reply = QMessageBox.question(self, '确认退出', '确定要退出吗?',
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
close_db()
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
def close(self) -> bool:
|
||||
close_db()
|
||||
super().close()
|
||||
misc_db.close()
|
||||
msg_db.close()
|
||||
micro_msg_db.close()
|
||||
hard_link_db.close()
|
||||
self.contact_window.close()
|
||||
self.exitSignal.emit(True)
|
||||
|
||||
|
@ -22,6 +22,7 @@ class Ui_MainWindow(object):
|
||||
font.setWeight(50)
|
||||
MainWindow.setFont(font)
|
||||
MainWindow.setStyleSheet("")
|
||||
MainWindow.setIconSize(QtCore.QSize(50, 24))
|
||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
|
||||
@ -69,7 +70,7 @@ class Ui_MainWindow(object):
|
||||
self.horizontalLayout.addWidget(self.stackedWidget)
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(MainWindow)
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 28))
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 27))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("微软雅黑")
|
||||
font.setPointSize(12)
|
||||
|
@ -7,7 +7,7 @@ from PyQt5.QtCore import pyqtSignal, QThread, QUrl, QFile, QIODevice, QTextStrea
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QWidget, QMessageBox, QFileDialog
|
||||
|
||||
from app.DataBase import msg_db, misc_db
|
||||
from app.DataBase import msg_db, misc_db, media_msg_db, close_db
|
||||
from app.DataBase.merge import merge_databases, merge_MediaMSG_databases
|
||||
from app.decrypt import get_wx_info, decrypt
|
||||
from app.log import logger
|
||||
@ -131,6 +131,7 @@ class DecryptControl(QWidget, decryptUi.Ui_Dialog):
|
||||
return
|
||||
if self.info.get('key') == 'none':
|
||||
QMessageBox.critical(self, "错误", "密钥错误\n请检查微信版本是否为最新和微信路径是否正确")
|
||||
close_db()
|
||||
self.label_tip.setVisible(True)
|
||||
self.label_tip.setText('点我之后没有反应那就多等儿吧,不要再点了')
|
||||
self.thread2 = DecryptThread(db_dir, self.info['key'])
|
||||
@ -171,6 +172,7 @@ class DecryptControl(QWidget, decryptUi.Ui_Dialog):
|
||||
'name': self.info['name'],
|
||||
'mobile': self.info['mobile']
|
||||
}
|
||||
|
||||
try:
|
||||
os.makedirs('./app/data', exist_ok=True)
|
||||
with open('./app/data/info.json', 'w', encoding='utf-8') as f:
|
||||
@ -183,6 +185,8 @@ class DecryptControl(QWidget, decryptUi.Ui_Dialog):
|
||||
# 源数据库文件列表
|
||||
source_databases = [f"app/DataBase/Msg/MSG{i}.db" for i in range(1, 200)]
|
||||
import shutil
|
||||
if os.path.exists(target_database):
|
||||
os.remove(target_database)
|
||||
shutil.copy2("app/DataBase/Msg/MSG0.db", target_database) # 使用一个数据库文件作为模板
|
||||
# 合并数据库
|
||||
try:
|
||||
@ -193,6 +197,8 @@ class DecryptControl(QWidget, decryptUi.Ui_Dialog):
|
||||
# 音频数据库文件
|
||||
target_database = "app/DataBase/Msg/MediaMSG.db"
|
||||
# 源数据库文件列表
|
||||
if os.path.exists(target_database):
|
||||
os.remove(target_database)
|
||||
source_databases = [f"app/DataBase/Msg/MediaMSG{i}.db" for i in range(1, 200)]
|
||||
shutil.copy2("app/DataBase/Msg/MediaMSG0.db", target_database) # 使用一个数据库文件作为模板
|
||||
# 合并数据库
|
||||
|
@ -35,23 +35,47 @@ def mkdir(path):
|
||||
|
||||
def wx_path():
|
||||
try:
|
||||
## 获取当前用户名
|
||||
user_home = os.environ.get("USERPROFILE")
|
||||
## 找到3ebffe94.ini配置文件
|
||||
f = open(user_home + '\\AppData\\Roaming\\Tencent\\WeChat\\All Users\\config\\3ebffe94.ini', encoding='utf-8')
|
||||
txt = f.read()
|
||||
f.close()
|
||||
# 打开Windows注册表
|
||||
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
|
||||
# 获取“我的文档”路径的注册表键值
|
||||
documents_path_value = winreg.QueryValueEx(reg_key, "Personal")
|
||||
# 输出路径
|
||||
##读取文件将路径放到wx_location变量里
|
||||
if txt == 'MyDocument:':
|
||||
wx_location = documents_path_value[0] + '\WeChat Files'
|
||||
else:
|
||||
wx_location = txt + "\WeChat Files"
|
||||
return wx_location
|
||||
is_w_dir = False
|
||||
|
||||
try:
|
||||
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Tencent\WeChat", 0, winreg.KEY_READ)
|
||||
value, _ = winreg.QueryValueEx(key, "FileSavePath")
|
||||
winreg.CloseKey(key)
|
||||
w_dir = value
|
||||
is_w_dir = True
|
||||
except Exception as e:
|
||||
w_dir = "MyDocument:"
|
||||
|
||||
if not is_w_dir:
|
||||
try:
|
||||
user_profile = os.environ.get("USERPROFILE")
|
||||
path_3ebffe94 = os.path.join(user_profile, "AppData", "Roaming", "Tencent", "WeChat", "All Users",
|
||||
"config",
|
||||
"3ebffe94.ini")
|
||||
with open(path_3ebffe94, "r", encoding="utf-8") as f:
|
||||
w_dir = f.read()
|
||||
is_w_dir = True
|
||||
except Exception as e:
|
||||
w_dir = "MyDocument:"
|
||||
|
||||
if w_dir == "MyDocument:":
|
||||
try:
|
||||
# 打开注册表路径
|
||||
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
|
||||
documents_path = winreg.QueryValueEx(key, "Personal")[0] # 读取文档实际目录路径
|
||||
winreg.CloseKey(key) # 关闭注册表
|
||||
documents_paths = os.path.split(documents_path)
|
||||
if "%" in documents_paths[0]:
|
||||
w_dir = os.environ.get(documents_paths[0].replace("%", ""))
|
||||
w_dir = os.path.join(w_dir, os.path.join(*documents_paths[1:]))
|
||||
# print(1, w_dir)
|
||||
else:
|
||||
w_dir = documents_path
|
||||
except Exception as e:
|
||||
profile = os.environ.get("USERPROFILE")
|
||||
w_dir = os.path.join(profile, "Documents")
|
||||
msg_dir = os.path.join(w_dir, "WeChat Files")
|
||||
return msg_dir
|
||||
except FileNotFoundError:
|
||||
return '.'
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
5
main.py
5
main.py
@ -6,6 +6,7 @@ import traceback
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
from app.DataBase import close_db
|
||||
from app.log import logger
|
||||
from app.ui import mainview
|
||||
from app.ui.tool.pc_decrypt import pc_decrypt
|
||||
@ -51,7 +52,9 @@ class ViewController(QWidget):
|
||||
def show_success(self):
|
||||
QMessageBox.about(self, "解密成功", "数据库文件存储在\napp/DataBase/Msg\n文件夹下")
|
||||
|
||||
|
||||
def close(self) -> bool:
|
||||
close_db()
|
||||
super().close()
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
font = QFont('微软雅黑', 12) # 使用 Times New Roman 字体,字体大小为 14
|
||||
|
23
readme.md
23
readme.md
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a><img src="./doc/images/logo.png" height="240"/></a>
|
||||
<a><img src="https://blog.lc044.love/static/img/91cadaae3a6f7ee133dafd4f9b5d8680.logo.webp" height="240"/></a>
|
||||
</div>
|
||||
<blockquote>
|
||||
<div style="background-color: #eaf7ea; border-radius: 10px; padding: 20px; position: relative;">
|
||||
@ -24,6 +24,7 @@
|
||||
<div style="position: absolute;top: 0;bottom: 0;left: 0;width: 2px;background-color: #000000;"></div>
|
||||
<h2>前言</h2>
|
||||
<div style="text-indent: 2em;">
|
||||
<div align="center"><img src="https://blog.lc044.love/static/img/7393a67ac602f7761e3c7b806165c892.logo99.webp"/></div>
|
||||
<p style="text-indent:2em;">我深信有意义的不是微信,而是隐藏在对话框背后的一个个<strong>深刻故事</strong>。未来,每个人都能拥有AI的陪伴,而你的数据能够赋予它有关与你过去的珍贵记忆。我希望每个人都有将自己的生活痕迹👨👩👦👚🥗🏠️🚴🧋⛹️🛌🛀留存的权利,而不是将之遗忘💀。</p>
|
||||
<p style="text-indent:2em;">AI的发展不仅仅是技术的提升,更是情感💞的延续。每一个对话、每一个互动都是生活中独一无二的片段,是真实而动人的情感交流。因此,我希望AI工作者们能够<strong>善用这些自己的数据</strong>,用于培训独特的、属于个体的人工智能。让<strong>个人AI成为生活中的朋友</strong>,能够理解、记录并分享我们的欢笑、泪水和成长。</p>
|
||||
<p style="text-indent:2em;">那天,AI不再是高不可攀的存在,而是融入寻常百姓家的一部分。因为<strong>每个人能拥有自己的AI</strong>,将科技的力量融入生活的方方面面。这是一场关于真情实感的革命,一场让技术变得更加人性化的探索,让我们共同见证未来的美好。</p>
|
||||
@ -58,21 +59,21 @@
|
||||
|
||||
<details>
|
||||
|
||||
<img alt="聊天界面" src="doc/images/chat_.png"/>
|
||||
<img alt="聊天界面" src="https://blog.lc044.love/static/img/b355c16c4a4037deec96fd87efecb74b.image.webp"/>
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@ -220,7 +221,8 @@ python main.py
|
||||
* 我的得力助手:[ChatGPT](https://chat.openai.com/)
|
||||
|
||||
---
|
||||
|
||||
> \[!IMPORTANT]
|
||||
>
|
||||
> 声明:该项目有且仅有一个目的:“留痕”——我的数据我做主,前提是“我的数据”其次才是“我做主”,禁止任何人以任何形式将其用于任何非法用途,对于使用该程序所造成的任何后果,所有创作者不承担任何责任🙄<br>
|
||||
> 该软件不会对您使用的微信造成任何影响,更不会对他人的微信造成任何影响,不能找回删除的聊天记录,任何企图篡改微信聊天数据的想法都是无稽之谈。本项目所有功能均建立在”前言“的基础之上,基于该项目的所有开发者均不能接受任何有悖于”前言“的功能需求,违者后果自负。
|
||||
> <br>该软件不存在任何收费,谨防上当受骗
|
||||
@ -250,6 +252,7 @@ python main.py
|
||||
感谢以下赞助者的慷慨支持:
|
||||
|
||||
- [STDquantum](https://github.com/STDquantum)
|
||||
- [xuanli](https://github.com/xuanli)
|
||||
|
||||
如果您提供赞助并希望出现在赞助者名单中,请在提交赞助时提供您的 GitHub 用户名或其他相关信息。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user