steam上有一款很火的游戏,桌面虚拟宠物,我想做一个简单的。第一个程序用来显示画面,一组图片构成的动图,参考Python代码实现桌面宠物,真IKUN才能养_哔哩哔哩_bilibili。第二个程序用模拟鼠标点击的方式,将gpt与自己的微信小号相连接,即gpt接入微信,我所使用的gpt是中转chatgpt,参考使用chatgpt实现微信自动回复_wechat-chatgpt-CSDN博客。gpt接入微信,长时间的使用wxauto库,可能会导致封号。目前的方式有两种,一种是将设备设置为手表接入微信,一种是土方法模拟鼠标点击,从安全性和简单程度来讲,我选择了后者。代码分享。
import sys
import os
from functools import partial
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import QProcess
import wxchat
import threading
event = threading.Event() # 共享的标志用于告知线程2何时结束
class Qt_pet(QtWidgets.QWidget):
def __init__(self):
super(Qt_pet, self).__init__()
self.dis_file = "img1"
self.windowinit()
self.icon_quit()
self.pos_first = self.pos()
self.timer = QTimer()
self.timer.timeout.connect(self.img_update)
self.timer.start(100)
def img_update(self):
if self.img_num < len(self.dir2img[self.current_dir])-1:
self.img_num += 1
else:
self.img_num = 0
self.qpixmap = QtGui.QPixmap(os.path.join(self.current_dir, self.dir2img[self.current_dir][self.img_num]))
self.lab.setMaximumSize(self.pet_width, self.pet_height)
self.lab.setScaledContents(True)
# 重新设置lab的大小与图片保持一致
self.lab.setGeometry(0, 0, self.qpixmap.width(), self.qpixmap.height())
self.lab.setPixmap(self.qpixmap)
# 获取放图片的路径,图片文件必须放在D:/Program Files (x86)/pet_conf/或者D:/Program Files/pet_conf/中,在里面放一个名为 imgN(比如img1,img2,img3的文件夹)的文件夹,文件夹中放具体的图片,图片的格式为N.png(比如1.png,2.png等)
def get_conf_dir(self):
conf_dirs = [r"你的图片地址"]
for conf_dir in conf_dirs:
if os.path.exists(conf_dir) and os.path.isdir(conf_dir):
self.conf_dir = conf_dir
for root, dirs, files in os.walk(self.conf_dir):
if root in conf_dirs:
for dir in dirs:
for r, _, f in os.walk(os.path.join(root, dir)):
if r == os.path.join(root, dir) and len(f)>0:
try:
f.sort(key=lambda x: int(x.split(sep='.', maxsplit=1)[0]))
except ValueError:
f.sort(key=lambda x: x.split(sep='.', maxsplit=1)[0])
self.dir2img.update({r: f})
return True
QtWidgets.QMessageBox.warning(None, "警告", "没有找到配置文件!请查看使用说明", QtWidgets.QMessageBox.StandardButton.Ok)
return False
def windowinit(self):
# 初始窗口设置大一点以免放入的图片显示不全
self.pet_width = 500
self.pet_height = 500
# 获取桌面桌面大小决定宠物的初始位置为右上角
desktop = QtWidgets.QApplication.desktop()
self.x = desktop.width()-self.pet_width
self.y = 100
self.setGeometry(self.x, self.y, self.pet_width, self.pet_height)
self.setWindowTitle('桌面宠物-by')
self.img_num = 0
# 找到配置文件,失败则退出
self.dir2img = {}
if not self.get_conf_dir():
self.quit()
self.lab = QtWidgets.QLabel(self)
self.current_dir = list(self.dir2img.keys())[0]
self.qpixmap = QtGui.QPixmap(os.path.join(self.current_dir, self.dir2img[self.current_dir][self.img_num]))
self.lab.setPixmap(self.qpixmap)
# 设置窗口为 无边框 | 保持顶部显示
self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint| QtCore.Qt.WindowType.WindowStaysOnTopHint)
# 设置窗口透明
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True)
# 设置窗口的属性,使其不在任务栏显示
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.Tool)
self.show()
# 设置系统托盘
def icon_quit(self):
mini_icon = QtWidgets.QSystemTrayIcon(self)
mini_icon.setIcon(QtGui.QIcon(os.path.join(self.current_dir, self.dir2img[self.current_dir][0])))
mini_icon.setToolTip("桌面宠物-by")
# 1 toggle()、triggered()、clicked()区别
# 这三个信号都是按钮点击后发射的信号,区别在于:
# clicked()用于Button发射的信号
# triggered()用于QAction发射的信号,原型:void triggered(bool checked = false);
# toggle()用于ChekBox,非开即关,原型:void toggled(bool);
quit_menu = QtWidgets.QAction('退出', self, triggered=self.quit)
tpMenu = QtWidgets.QMenu(self)
changeSubMenu = QtWidgets.QMenu(self)
changeSubMenu.setTitle("切换")
for dir in self.dir2img.keys():
act = QtWidgets.QAction(os.path.basename(dir), self, triggered=partial(self.changeImg, dir))
changeSubMenu.addAction(act)
tpMenu.addMenu(changeSubMenu)
tpMenu.addAction(quit_menu)
mini_icon.setContextMenu(tpMenu)
mini_icon.show()
# 鼠标左键按下的时候获取当前位置
def mousePressEvent(self, QMouseEvent):
if QMouseEvent.button() == QtCore.Qt.MouseButton.LeftButton:
self.pos_first = QMouseEvent.globalPos() - self.pos()
QMouseEvent.accept()
self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.OpenHandCursor))
# 拖动移动
def mouseMoveEvent(self, QMouseEvent):
self.move(QMouseEvent.globalPos() - self.pos_first)
# self.x, self.y = self.pos().x, self.pos().y
QMouseEvent.accept()
def quit(self):
event.set()
self.close()
sys.exit()
def changeImg(self, dir):
self.current_dir = dir
def mypet():
app = QApplication(sys.argv)
pet = Qt_pet()
sys.exit(app.exec_())
if __name__ == '__main__':
t1 = threading.Thread(target=wxchat.resize_wechat_window)
t2 = threading.Thread(target=mypet)
t1.start()
t2.start()
程序的名称是windows_pet.py,在原作者的基础上加入了多线程,因为两个程序都是无限循环的逻辑,两个线程不影响,设置了监听事件event用于同时结束两个线程,即当前线程退出,另一个线程也退出。另外补充,设置窗口的属性,使其不在任务栏显示。
import pygetwindow as gw
import pyautogui
import pyperclip
import time
import get_API
from windows_pet import event
def resize_wechat_window(): # 调整窗口大小
window_titles = gw.getAllTitles() # 获取所有打开的窗口
wechat_window = None
for title in window_titles: # 判断是否存在标题包含“微信”的窗口
if "微信" in title:
wechat_window = gw.getWindowsWithTitle(title)[0]
break
if wechat_window:
wechat_window.restore() # 还原窗口
wechat_window.activate() # 激活窗口显示在前台
wechat_window.resizeTo(700, 500)
autowx()
else:
print("微信窗口未找到")
def autowx():
red = r'you_red.png' # 消息来了的红图标
name = r'you_name.png' # 接收人的图标
while True:
#print(event.is_set())
if event.is_set():
break
else:
location_red = pyautogui.locateCenterOnScreen(red, confidence=0.90)
location_name = pyautogui.locateCenterOnScreen(name, confidence=0.90)
if location_red and location_name:
if location_red[0] - location_name[0] < 50:
pyautogui.click(location_red)
text = find_txt()
else:
time.sleep(1)
def find_txt():
pos3 = r'you_pos3.png' # 聊天窗口
while True:
locations_pos3 = pyautogui.locateAllOnScreen(pos3, confidence=0.98) # 所有坐标
if locations_pos3:
break
else:
print("没有收到窗口消息,1秒后重试")
time.sleep(1)
time.sleep(0.1)
list_text = list(locations_pos3)
if list_text != []:
pos = sorted(list_text, key=lambda x:x[1], reverse=True)[0] # lambda x:x[1]元组的第二个元素从小到大排序的顺 # reverse降序
pyautogui.doubleClick(x = pos[0] + 15 + pos[2] / 2,y = pos[1] + pos[3] / 2)
time.sleep(0.1)
pyautogui.hotkey('ctrl', 'c')
time.sleep(0.1)
text = pyperclip.paste()
get_API.getchat(text)
def givechat(text):
pos1 = r'you_pos1.png' # 笑脸图标
send = r'you_send.png' # 发送图标
while True:
location_pos1 = pyautogui.locateCenterOnScreen(pos1, confidence=0.90)
location_send = pyautogui.locateCenterOnScreen(send, confidence=0.90)
if location_pos1 and location_send:
break
else:
print("没有收到gpt消息,1秒后重试")
time.sleep(1)
pyautogui.click(location_pos1[0], location_pos1[1]+30)
pyperclip.copy(text)
pyautogui.hotkey('ctrl', 'v')
time.sleep(0.1)
pyautogui.click(location_send)
#if __name__ == '__main__':
#resize_wechat_window()
程序的名称是wxchat.py,在原作者的原理上基本重写了代码,加入了首先设置微信程序的大小,后面也是使用while True轮询和pyautogui模拟鼠标点击,简化了原作者的代码只实现我要的一些基础功能。
from openai import OpenAI
import httpx
import wxchat
def getchat(access):
client = OpenAI(
base_url="https://oneapi.xty.app/v1",
api_key="you_API",
http_client=httpx.Client(
base_url="https://oneapi.xty.app/v1",
follow_redirects=True,
),
)
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
temperature=0.99,
max_tokens=2048,
messages=[{"role": "system",
"content": "you_初始化文本"},
{"role": "user", "content": access}]
)
response = completion.choices[0].message.content
wxchat.givechat(response)
程序的名称是get_API.py,使用的是中专API,官方的三月封号,至于我的桌面虚拟模拟器Vpet则使用了chatglm的API。至此我就有两个伙伴了。
未来的展望,后面有时间再瞎折腾了,大概就是,和桌面虚拟模拟器Vpet不一样,玩游戏的时候是真的在玩游戏,简单的是模拟鼠标玩一个“种田“游戏。在我写代码的时候在旁边鼓励之类的。