def addName(ID: int, no: int = 0): """ 为characterID获取name。 ID(int):要搜索name的ID。 no(int):第no个搜索项。 """ url = r"https://esi.evetech.net/latest/characters/" + str( ID) + r"/?datasource=tranquility" Msg = {"addName": []} try: with rq.get(url, timeout=5) as ret: ret = loads(ret.content) except Exception as e: log("addName:" + str(e), level="error") return Msg log("addName:ID={ID}已获取到".format(ID=ID)) if "name" in ret: history.update({ret["name"]: {"characterID": ID}}) Msg["addName"].append([ ID, TMsgEntry(ret["name"], ClickEvent=SearchName, ClickArgs=(ret["name"], ID), style_str=MDStyleStr( color=settings["claddName"], font_size=settings["labelFontSize"])) ]) return Msg
def mousePressEvent(self, e): # 单击 global current_thread_set self.on_events = True self.parent().parent().LabelList_click_no = self.no if self.MsgEntry.ClickEvent != None: log("label:"+self.text()+",ClickEvent="+str(self.MsgEntry.ClickEvent)+",ClickArgs="+str(self.MsgEntry.ClickArgs)) current_thread_set=set() self.parent().parent().EndSearchEvent() if self.MsgEntry.ClickEvent == SearchName: self.parent().parent().RefreshLabelList({"Searching": TMsgEntry( text="正在搜索角色{name}...".format(name=str(self.MsgEntry.ClickArgs[0])), style_str=MDStyleStr( color=settings["clHint"], font_size=settings["labelFontSize"] ))}) self.MsgEntry.ClickReturn = self.parent().parent().MultiThreadRun(func=self.MsgEntry.ClickEvent, args=self.MsgEntry.ClickArgs) elif self.MsgEntry.ClickEvent == SearchKM: #TODO:增加一个判断,如果已经获取了km则不重复获取 if self.MsgEntry.ClickReturn == None: self.parent().parent().StatusBar.showMessage("正在获取km...") self.MsgEntry.ClickReturn = self.parent().parent().MultiThreadRun(func=self.MsgEntry.ClickEvent, args=self.MsgEntry.ClickArgs) # else: # self.MsgEntry.enable = not self.MsgEntry.enable # self.parent().parent().RefreshLabelList() self.on_events=False
def quit(self): """ 终止程序。 """ self.TrayIcon.hide() self.closeEvent(None) self.EndSearchEvent() log("退出") QApplication.quit()
def RefreshLabelList(self, Msg=None): """ 刷新LabelList显示。 Msg(None or dict):作为回调时需要在self.MsgEntryList中添加的信息 """ #作为多线程的回调,此进程需要加锁。 global MutLabelList while not MutLabelList.tryLock(1): sleep(0.1) self.statusBar().showMessage("错误:无法刷新信息列表") ret=0 #由于MsgLabel可能正在响应事件所以不能直接deleteLater #使用double buffer for i in self.LabelList_buffer: while (i.on_events): i.on_events=False sleep(0.1) i.deleteLater() for i in self.LabelList: i.hide() self.LabelList_buffer=self.LabelList[:] self.LabelList = [] if isinstance(Msg,dict): self.MsgEntryList.update(Msg) #把MsgEntryList展平 new_label_list = Serialize(self.MsgEntryList) count=-1 for i in new_label_list: #每个i都是一个TMsgEntry count += 1 if i.enable: self.LabelList.append(TMsgLabel(m=i,no=count)) for i in self.LabelList: i.setParent(self.centralwidget) i.setOpenExternalLinks(True) i.show() i.lower() #向上滚动至之前位置 wheel_pos = self.LabelList_pos self.LabelList_pos=0 for i in range(abs(wheel_pos)): if i != -self.LabelList_pos: break self.wheelEvent(int(wheel_pos / abs(wheel_pos))) log("RefreshLabelList:"+','.join([i.text() for i in self.LabelList])) MutLabelList.unlock() return ret
def StartSearchName(self,name:str=""): """ 开始一轮搜索。 """ global current_thread_set if name=="": name = self.EdtName.text() #检查这是否是一个合适的name name = name.strip(" \n\"\'“”‘’") if name == "": self.statusBar().showMessage("错误:没有输入名字") return - 1 #如果是一个zkb链接 fetch = re.search(r"https://zkillboard\.com/character/([0-9]*)/", name) if fetch != None: log("导入zkb链接"+fetch.group(0)) self.RefreshLabelList({"LoadFromzkbLink": [TMsgEntry( "导入zkb链接 " + fetch.group(0), style_str=MDStyleStr(color=settings["clHint"], font_size=settings["labelFontSize"]) ),TMsgEntry( "正在搜索...", style_str=MDStyleStr(color=settings["clHint"], font_size=settings["labelFontSize"]) )]}) self.MultiThreadRun(func=SearchName, args=(None, int(fetch.group(1)))) else: name=re.search(r"^[a-zA-Z0-9 '\-_]*$", name) if name==None: #名字中含有非法字符 self.statusBar().showMessage("错误:名字中含有非法字符") return - 1 else: name=name.group(0) self.MsgEntryList = {} current_thread_set = set() log("搜索"+name) self.RefreshLabelList({"SearchName": { "Searching": [name, TMsgEntry("正在搜索名字包含" + name + '的角色...', style_str=MDStyleStr( color=settings["clHint"], font_size=settings["labelFontSize"] ) ), ] }}) self.MultiThreadRun(func=SearchName, args=(name,)) return 0
def run(self): log("thread:"+self.__name__+",args="+self.args.__str__()) self.Msg = self.func(*self.args)
def EndSearchEvent(self,e=None): """ 搜索结束事件。 将传递信息的线程传来的 """ if isinstance(self.sender(), TThread): log("EndSearch:"+str(self.sender().__name__)) MutEndSearch.lock() Msg = self.sender().Msg log("Msg="+str(Msg)) if id(self.sender()) not in current_thread_set: #说明不是此次搜索的返回线程,直接丢弃 MutEndSearch.unlock() return - 1 #由SearchName返回 if "getKMList" in Msg:#完成了一轮完整的搜索流程 self.MsgEntryList = {} self.MsgEntryList.update(Msg) self.statusBar().showMessage("搜索完成") self.EdtName.setStyleSheet(""" TEdtName{ border: 2px groove white; border-radius: 3px; padding:2px 3px; color:rgb(255,0,0); background-color:rgba(0,0,0,0) } TEdtName:focus{ color:rgb(0,255,0); background-color: rgb(0,0,0) } """) elif "Error" in Msg: #SearchName返回错误 self.MsgEntryList = {} self.MsgEntryList.update(Msg) if Msg["Error"] == "getKMListError": self.MsgEntryList.update({"ErrorLabel": TMsgEntry("获取KM列表失败",style_str=MDStyleStr(color=settings["clFailed"],font_size=settings["labelFontSize"]))}) elif Msg["Error"] == "zkbError": self.MsgEntryList.update({"ErrorLabel": TMsgEntry("zkb查询失败",style_str=MDStyleStr(color=settings["clFailed"],font_size=settings["labelFontSize"]))}) elif Msg["Error"] == "esiError": self.MsgEntryList.update({"ErrorLabel": TMsgEntry("查询角色ID失败", style_str=MDStyleStr(color=settings["clFailed"], font_size=settings["labelFontSize"]))}) elif Msg["Error"] == "SearchKMError": self.MsgEntryList.update({"ErrorLabel": TMsgEntry("查询KM失败", style_str=MDStyleStr(color=settings["clFailed"], font_size=settings["labelFontSize"]))}) elif Msg["Error"] == "NoSuchCharacterError": self.MsgEntryList.update({"ErrorLabel": TMsgEntry("无此角色", style_str=MDStyleStr(color=settings["clFailed"], font_size=settings["labelFontSize"]))}) elif Msg["Error"] == "PlayerNoPVPData": self.MsgEntryList.update({"ErrorLabel": TMsgEntry("没有该角色的统计数据",style_str=MDStyleStr(color=settings["clHint"],font_size=settings["labelFontSize"]))}) elif "NameList" in Msg: #多个搜索结果命中 self.MsgEntryList.update({"MultipleHits": TMsgEntry("命中" + str(len(Msg["NameList"])) + "条搜索结果...",style_str=MDStyleStr(color=settings["clHint"],font_size=settings["labelFontSize"]))}) NameList = Msg["NameList"][:] no=0 for c in NameList: no+=1 self.MultiThreadRun(func=addName, args=(c, no)) elif "TooManyResults" in Msg: #搜索结果命中数超过ResultCountLimit self.MsgEntryList.update(Msg) self.MultiThreadRun(func=SearchName,args=(Msg["name"],-1,True)) #由addName返回 elif "addName" in Msg: #处理addName返回的情况 #addName会多并发调用EndSearchEvent,因此需要QMutex #addName会返回成对的name和characterID,每个返回都应被添加至self.MsgEntryList["addName"] if "addName" not in self.MsgEntryList: self.MsgEntryList["addName"]=[] self.MsgEntryList["addName"] += Msg["addName"] #由SearchKM返回 elif "SearchKM" in Msg: if self.LabelList_click_no != -1: #需要找到被单击的Label在self.MsgEntryList中的位置 for i in range(len(self.MsgEntryList["getKMList"])): #如果getKMList中记录的killmail_id==LabelList中记录的killmail_id if (self.MsgEntryList["getKMList"][i][0][0]==self.LabelList[self.LabelList_click_no].MsgEntry.ClickArgs[1]): self.MsgEntryList["getKMList"][i][2]=Msg break self.statusBar().showMessage("KM已获取") MutEndSearch.unlock() self.RefreshLabelList()
def setupUi(self, MainWindow): #载入资源文件 #载入字体 for i in font_path: if exists(settings["workingDir"]+font_path[i]): QtGui.QFontDatabase.addApplicationFont(settings["workingDir"]+font_path[i]) else: log("字体不存在:" + font_path[i] + "(" + settings["lang"] + ")", level="error") font_path[i] = "Arial" self.setObjectName("MainWindow") self.resize(400, 300) self.setFixedSize(MainWindow.width(), MainWindow.height()) self.setWindowFlags(Qt.FramelessWindowHint) self.setWindowOpacity(settings["WindowOpacity"]) self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowFlags(QtCore.Qt.FramelessWindowHint|QtCore.Qt.WindowStaysOnTopHint) #widget if exists(settings["workingDir"]+settings["backgroundPath"]): backgroundPath=settings["workingDir"]+settings["backgroundPath"] log("加载背景图片:" + backgroundPath) else: backgroundPath = ":/black.png" log("未找到背景图片") self.RoundWidget = QWidget(parent=MainWindow) self.RoundWidget.setGeometry(MainWindow.geometry()) self.RoundWidget.setStyleSheet("QWidget{border-image: url("+backgroundPath.replace(sep,"/")+r") 100% 100% round;border-radius:15px;}") self.RoundWidget.show() #拖动功能 self._startPos,self._endPos=QPoint(0,0),QPoint(0,0) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") QApplication.setQuitOnLastWindowClosed(False) self.BtnSearch = QtWidgets.QPushButton(self.centralwidget) self.BtnSearch.setGeometry(QtCore.QRect(350, 10, 40, 40)) self.BtnSearch.setText("") self.BtnSearch.setIcon(QIcon(":/AuraProII.ico")) self.BtnSearch.setIconSize(QtCore.QSize(40, 40)) self.BtnSearch.setStyleSheet(r"QPushButton{border:1px solid;padding:5px;border-radius:10px;}") self.BtnSearch.setObjectName("BtnSearch") QShortcut(QKeySequence(settings["SearchShortCut"]), self.BtnSearch).activated.connect(self.BtnSearchClickEvent) self.BtnSearch.setToolTip("快捷键:"+settings["SearchShortCut"]) self.BtnSearch.clicked.connect(self.EndSearchEvent) self.BtnSearch.clicked.connect(self.BtnSearchClickEvent) self.StatusBar = QtWidgets.QStatusBar(self.centralwidget) self.StatusBar.setStyleSheet("QStatusBar{color:rgba"+settings["clStatusBar"]+";}") self.setStatusBar(self.StatusBar) self.StatusBar.showMessage("增强型奥拉 II:"+version) self.EdtName = TEdtName(self.centralwidget) self.EdtName.setGeometry(QtCore.QRect(10, 10, 340, 40)) self.EdtName.setPlaceholderText("在这里输入名字...") self.EdtName.setStyleSheet(""" TEdtName{ border:1px grey; border-style: none; border-radius:10px; padding:1px 2px; color:rgb(255,0,0); background-color:rgba(0,0,0,127) } """) self.EdtName.show() self.TrayMenu = QMenu(self) self.TrayMenu.addAction(QAction("显示/隐藏主界面", parent=self.TrayMenu,checkable=True,checked=True, triggered=self.ToggleShowHide)) self.TrayMenu.addAction(QAction("主界面置顶", parent=self.TrayMenu, checkable=True, checked=True, triggered=self.ToggleStayOnTop)) self.TrayMenu.addSeparator() self.TrayMenu.addAction(QAction("关于...", parent=self.TrayMenu, triggered=self.ShowAbout)) self.TrayMenu.addSeparator() self.TrayMenu.addAction(QAction("停用 增强型奥拉II",parent=self.TrayMenu, triggered=self.quit)) self.TrayIcon = QSystemTrayIcon(self) self.TrayIcon.setIcon(QIcon(":/AuraProII.ico")) self.TrayIcon.setToolTip(u'增强型奥拉 II:激活中') self.TrayIcon.show() self.TrayIcon.showMessage(u"增强型奥拉 II:"+version, "Fly safe o/", 0) self.TrayIcon.activated.connect(self.TrayIconClickEvent) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(lambda:self.TrayMenu.exec_(QCursor.pos())) self.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) #窗体创建完毕 #信息列表 self.MsgEntryList = {} # MsgEntryList结构 # 每项为每个过程加入的信息 # 如,StartSearchKB后,MsgEntryList中的内容应为: # {"SearchName":{ # "Label":"正在搜索角色", # "ID":******* # } # "SearchKB":{...} # RefreshLabelList只会显示被SerializeMsgEntryList抽出的TMsgLabel对象 #用于显示信息列表的Label列表 self.LabelList = [] self.LabelList_buffer = [] self.LabelList_click_no = -1 self.LabelList_pos=0 #窗体创建完毕 log("窗体创建完毕")
def dropEvent(self, e): #如果是zkb链接: log("拖入"+e.mimeData().text()) self.setText(e.mimeData().text()) self.parent().parent().StartSearchName(e.mimeData().text())
def SearchName(name: str = '', ID: int = -1, is_strict=False): """ 获取角色ID与zkb统计信息。 name(str):角色名 ID(int):如果已有角色ID则跳过搜索characterID的步骤 is_strict(bool):严格模式,只搜索与name完全一致的角色名 """ global MutSearchName MutSearchName.lock() Msg = {} log("SearchName:name={name},ID={ID},is_strict={is_strict}".format( name=name, ID=ID, is_strict=is_strict)) #characterID if ID < 0: #如果没有给出characterID name = name.strip(" \n") for i in history: if i.upper() == name.upper(): name = i ID = history[name]["characterID"] break if ID < 0: #搜索历史记录未命中 log("SearchName:历史记录未命中") strict = "&strict=true" if is_strict else "&strict=false" url = r"https://esi.evetech.net/latest/search/?categories=character&datasource=tranquility&language=en-us&search=" + name.replace( " ", "+") + strict try: with rq.get(url, timeout=5) as ret: ret = loads(ret.content) except Exception as e: log("SearchName:" + str(e), level="error") Msg.update({"Error": "esiError"}) MutSearchName.unlock() return Msg if "character" in ret: ID = ret["character"] if (len(ID) > 1): log("SearchName:命中{l}/{lmax}个结果".format( l=len(ID), lmax=settings["ResultCountLimit"])) #有多个命中结果 if len(ID) < settings["ResultCountLimit"]: Msg.update({"NameList": ID}) MutSearchName.unlock() return Msg else: #结果过多 Msg.update({ "TooManyResults": TMsgEntry("搜索结果数量超过" + \ str(settings["ResultCountLimit"]) + "个,改为严格模式...", style_str=MDStyleStr( color=settings["clHint"], font_size=settings["labelFontSize"] )), "name": name }) MutSearchName.unlock() return Msg else: ID = ID[0] else: Msg.update({"Error": "NoSuchCharacterError"}) log("SearchName:esi查询失败:{name}".format(name=name), level="warning") MutSearchName.unlock() return Msg Msg.update({"SearchName": [name, ID]}) #zkb url = r"https://zkillboard.com/api/stats/characterID/" + str(ID) + r"/" try: with rq.get(url, timeout=5) as ret: ret = loads(ret.content) except Exception as e: Msg.update({"Error": "zkbError"}) MutSearchName.unlock() return Msg #角色信息为None if (ret == {}) or (ret["info"] == None): Msg.update({"Error": "PlayerNoPVPData"}) MutSearchName.unlock() return Msg if "dangerRatio" not in ret: ret["dangerRatio"] = 0 danger_ratio_color = (int(ret["dangerRatio"] / 100 * 255), int((1 - (ret["dangerRatio"] / 100)) * 255), 0) if "gangRatio" not in ret: ret["gangRatio"] = 100 solo_ratio_color = (int((1 - (ret["gangRatio"] / 100)) * 255), int(ret["gangRatio"] / 100 * 255), 0) Msg.update({ "SearchKB": { "name": [ ret["info"]["name"], TMsgEntry( r"<a href='https://zkillboard.com/character/" + str(ID) + r"' style='color:blue'>" + ret["info"]["name"] + "</a>", style_str=MDStyleStr(color=settings["clURL"], font_size=settings["labelFontSize"])) ], "dangerRatio": [ ret["dangerRatio"], TMsgEntry("危险度:" + str(ret["dangerRatio"]) + "%", style_str=MDStyleStr( color=danger_ratio_color, font_size=settings["labelFontSize"])) ], "soloRatio": [ ret["gangRatio"], TMsgEntry("solo率:" + str(100 - ret["gangRatio"]) + "%", style_str=MDStyleStr( color=solo_ratio_color, font_size=settings["labelFontSize"])) ], "topShips": [[i["shipTypeID"] for i in ret["topLists"][3]["values"][:3]], TMsgEntry("最高击杀舰船:" + ','.join([ getNamebyID(i["shipTypeID"]) + "(" + str(i["kills"]) + "次)" for i in ret["topLists"][3]["values"][:3] if i["shipName"] != "Capsule" ]), style_str=MDStyleStr( color=settings["cltopShips"], font_size=settings["labelFontSize"]))], "topSolarSystem": [[i["solarSystemName"] for i in ret["topLists"][4]["values"][:3]], TMsgEntry("最常出没:" + ','.join([ i["solarSystemName"] + "(" + str(i["kills"]) + "次)" for i in ret["topLists"][4]["values"][:3] ]), style_str=MDStyleStr( color=settings["cltopSolarSystem"], font_size=settings["labelFontSize"]))] } }) history.update({ret["info"]["name"]: {"characterID": ID}}) SaveFile(history, settings["workingDir"] + "history.json") #getKMList url = r"https://zkillboard.com/api/kills/characterID/" + str(ID) + r"/" try: with rq.get(url, timeout=5) as ret: ret = loads(ret.content) except Exception as e: log("getKMList:" + str(e), level="error") Msg.update({"Error": "getKMListError"}) MutSearchName.unlock() return Msg #玩家可能没有击杀km if ret == []: Msg.update({"getKMList": []}) MutSearchName.unlock() return Msg km_count = min(settings["KMCounts"], len(ret)) killmail_pairs = [(ret[i]["killmail_id"], ret[i]["zkb"]["hash"]) for i in range(km_count)] remap = [] label_list = [ TMsgEntry(str(i + 1) + ".最近km(" + Valuable(ret[i]["zkb"]["totalValue"]) + ' isk)', style_str=MDStyleStr(color=settings["clKM"], font_size=settings["labelFontSize"]), ClickEvent=SearchKM, ClickArgs=(ID, killmail_pairs[i][0], killmail_pairs[i][1])) for i in range(len(killmail_pairs)) ] for i in killmail_pairs: remap.append([i, label_list[0], {}]) label_list = label_list[1:] Msg.update({"getKMList": remap}) log("getKMList:完成") MutSearchName.unlock() return Msg
def SearchKM(character_id: int, killmail_id: int = 0, killmail_hash: str = ''): """ 获取一个特定的km character_id(int):角色id killmail_id(int):该killmail的id killmail_hash(str):由ccp给出的km哈希值 """ global MutSearchKM MutSearchKM.lock() Msg = {} log("SearchKM:character_id={character_id},killmail_id={killmail_id},killmail_hash={killmail_hash}" .format(character_id=character_id, killmail_id=killmail_id, killmail_hash=killmail_hash)) url = r"https://esi.evetech.net/latest/killmails/"\ + str(killmail_id)\ + r"/" + killmail_hash + r"/?datasource=tranquility" try: with rq.get(url, timeout=5) as ret: ret = loads(ret.content) except Exception as e: log("SearchKM:" + str(e), level="error") Msg.update({"Error": "SearchKMError"}) MutSearchKM.unlock() return -1 Msg = { "SearchKM": { "time": [ ret["killmail_time"].replace('T', ' ').replace('Z', ''), TMsgEntry( " (" + ret["killmail_time"].replace('T', ' ').replace( 'Z', '' + ")"), style_str=MDStyleStr(color=settings["clHint"], font_size=settings["labelFontSize"])) ], "victimShip": [ ret["victim"]["ship_type_id"], TMsgEntry( r" <a href='https://zkillboard.com/kill/" + str(killmail_id) + r"/' style='color:blue'>击毁:" + getNamebyID(ret["victim"]["ship_type_id"]) + r"</a>", style_str=MDStyleStr(color=settings["clURL"], font_size=settings["labelFontSize"])) ] } } for i in ret["attackers"]: if ("character_id" in i) and (i["character_id"] == character_id): ship = getNamebyID( i["ship_type_id"]) if "ship_type_id" in i else "(?)" weapon = getNamebyID( i["weapon_type_id"]) if "weapon_type_id" in i else "(?)" if ship == weapon: weapon = "(混合)" Msg["SearchKM"].update({ "shipType": [ ship, TMsgEntry(" · " + ship, style_str=MDStyleStr( color=settings["clshipType"], font_size=settings["labelFontSize"])) ], "weaponType": [ weapon, TMsgEntry(" · " + weapon, style_str=MDStyleStr( color=settings["clweaponType"], font_size=settings["labelFontSize"])) ] }) break log("SearchKM:完成") MutSearchKM.unlock() return Msg