├── .gitignores ├── assets ├── tft.png └── api.js ├── requirements.txt ├── settings.py ├── README.md ├── simulator.py ├── spider.py ├── TFTDB.py ├── gui ├── Form_hero.ui └── Form_hero.py └── main.py /.gitignores: -------------------------------------------------------------------------------- 1 | .idea 2 | **/__pycache__/** 3 | **/__pycache__/ 4 | -------------------------------------------------------------------------------- /assets/tft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzhy324/TFTAuto/HEAD/assets/tft.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzhy324/TFTAuto/HEAD/requirements.txt -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logLevel = logging.INFO # 控制日志的输出等级,可选项[DEBUG,INFO,WARNING,ERROR] 4 | myBuild = ['布兰德','泰隆','蕾欧娜','德莱文','辛德拉','泽丽','卡萨丁','斯维因','波比','卡蜜尔'] 5 | 6 | 7 | def settings_init(): 8 | logging.basicConfig(level=logLevel, # 设置日志的默认响应级别为INFO,按需要更改成为debug,默认等级为warning 9 | format='[%(asctime)s] %(filename)s:%(lineno)s - [%(levelname)s] %(message)s') # 规定logging的输出格式 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **云顶官方接口**: 2 | 3 | https://lol.qq.com/tft/js/api.js 4 | 5 | # Prerequisite 6 | 1. python 3.6+ 7 | 2. PyQt5 8 | 3. requests 9 | 10 | # 需求分析 11 | ## 阶段1. 制作一个能够筛选英雄的GUI界面 12 | 1. 完成英雄列表及基本信息的拉取 13 | 2. 制作筛选界面 14 | 3. 设想: 15 | 1. updater.py 从api中爬取原始信息的爬虫 16 | 2. TFTDB.py 数据库类 17 | 3. main.py 主控逻辑 18 | 4. settings.py 保存基本配置 19 | 5. GUI.Form_hero.py 英雄筛选器的GUI实现 20 | 21 | ### 架构示意 22 | main.py 作为程序入口,依次完成如下功能 23 | 1. 加载配置项settings.py 24 | 2. 初始化数据库对象TFTDB 25 | 3. 加载英雄筛选器的GUI界面 26 | 27 | ### 开发进度 28 | * 已经开发的部分 29 | * api的信息拉取以及筛选函数 30 | * gui的大部分 31 | * gui中的搜索功能 32 | * 待开发的部分 33 | * 当前选定英雄队列的可视化 34 | * gui英雄头像显示功能 35 | 36 | 37 | ## 阶段2:客户端的ocr识别 38 | 已经完成了客户端图片的识别功能 39 | ## 阶段3:自动拿牌与卖牌的逻辑 40 | TBC... -------------------------------------------------------------------------------- /simulator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from time import sleep 3 | from aip import AipOcr 4 | from io import BytesIO 5 | import pyautogui as pag 6 | from pynput import keyboard 7 | 8 | # 初始化对百度api的连接 9 | _APP_ID = '26013200' 10 | _API_KEY = 'OSWuIzTPnunvVbXTosQEtZ8i' 11 | _SECRET_KEY = " TDODzQaboN1SF36uXPf2vQDChEsuKu5k" 12 | _client = AipOcr(_APP_ID, _API_KEY, _SECRET_KEY) 13 | 14 | 15 | # 获取图片并返回识别结果 16 | def getFiveTitles() -> [str]: 17 | # 1080p分辨率,无边框显示下,英雄名字定位为480 1040 1475 1065 18 | img = pag.screenshot().crop((480, 1040, 1475, 1065)) 19 | imgByteArr = BytesIO() 20 | img.save(imgByteArr, format='PNG') 21 | data: dict = _client.basicGeneral(imgByteArr.getvalue()) 22 | if 'words_result' in data: 23 | data = data["words_result"] 24 | else: 25 | logging.error("cannot recognize words_result from baidu API") 26 | return [] 27 | return [item["words"] for item in data if not ('0' <= item["words"][-1] <= '9')] 28 | -------------------------------------------------------------------------------- /spider.py: -------------------------------------------------------------------------------- 1 | import requests, json 2 | 3 | _headers = { 4 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'} 5 | 6 | 7 | # 根据api提供的json文件特点构造的request请求函数 8 | def __myRequestGet(url) -> [dict]: 9 | rq = requests.get(url=url, headers=_headers) 10 | ret = json.loads(rq.text) 11 | return ret['data'] 12 | 13 | 14 | # 获取弈子信息 15 | def getChessList() -> [dict]: 16 | rawData = __myRequestGet("https://game.gtimg.cn/images/lol/act/img/tft/js/chess.js") 17 | ret = [] 18 | for data in rawData: 19 | dic = dict.fromkeys(("title", "displayName", "price", "jobIds", "raceIds")) 20 | dic["title"] = data["title"] 21 | dic["displayName"] = data["displayName"] 22 | dic["price"] = int(data["price"]) 23 | dic["jobIds"] = data["jobIds"] 24 | dic["raceIds"] = data["raceIds"] 25 | ret.append(dic) 26 | return ret 27 | 28 | 29 | # 获取种族列表 30 | def getRaceList() -> [dict]: 31 | return __myRequestGet("https://game.gtimg.cn/images/lol/act/img/tft/js/race.js") 32 | 33 | 34 | # 获取职业列表 35 | def getJobList() -> [dict]: 36 | return __myRequestGet("https://game.gtimg.cn/images/lol/act/img/tft/js/job.js") 37 | -------------------------------------------------------------------------------- /TFTDB.py: -------------------------------------------------------------------------------- 1 | import spider 2 | import logging 3 | 4 | 5 | class TFTDB: 6 | def __init__(self): 7 | logging.info("fetching data from tft api!") 8 | self.chessList = spider.getChessList() 9 | self.raceList = spider.getRaceList() 10 | self.jobList = spider.getJobList() 11 | logging.info("data successfully fetched!") 12 | 13 | def printTFTDB(self): 14 | print("printing TFTDB==========================") 15 | print(self.chessList) 16 | print(self.raceList) 17 | print(self.jobList) 18 | print("========================================") 19 | 20 | # 判断形如 "7,5,3"的字符串是不是"2,7,3,5"的数字意义上的子串 21 | def isSubIDString(self, pattern: str, src: str) -> bool: 22 | p_nums = pattern.split(',') 23 | s_nums = src.split(',') 24 | for p_num in p_nums: 25 | if p_num not in s_nums: 26 | return False 27 | return True 28 | 29 | # 只有当输入不是默认值的时候才会附加该搜索条件,全部采用字符串搜索 30 | def searchTFTDB(self, name: str = '', raceIds: str = '', jobIds: str = '', price: int = 0) -> [dict]: 31 | # 时间复杂度:O(n2),这里的筛选可以优化到O(n),优化关键是不使用remove 32 | ret = self.chessList[:] 33 | for item in self.chessList: 34 | if name: 35 | if name not in item["title"] and name not in item["displayName"]: 36 | ret.remove(item) 37 | continue 38 | if raceIds: 39 | if not self.isSubIDString(raceIds, item["raceIds"]): 40 | ret.remove(item) 41 | continue 42 | if jobIds: 43 | if not self.isSubIDString(jobIds, item["jobIds"]): 44 | ret.remove(item) 45 | continue 46 | if price: 47 | if item["price"] != price: 48 | ret.remove(item) 49 | continue 50 | return ret 51 | 52 | # 通过种族名字查询种族id 53 | def getRaceIdByName(self, race_name: str) -> str: 54 | for item in self.raceList: 55 | if item["name"] == race_name: 56 | return item["raceId"] 57 | return '' 58 | 59 | # 通过职业名字查询职业id 60 | def getJobIdByName(self, job_name: str) -> str: 61 | for item in self.jobList: 62 | if item["name"] == job_name: 63 | return item["jobId"] 64 | return '' 65 | 66 | # 返回纯名字的职业列表 67 | def getJobNameList(self) -> [str]: 68 | return [item["name"] for item in self.jobList] 69 | 70 | # 返回纯名字的种族列表 71 | def getRaceNameList(self) -> [str]: 72 | return [item["name"] for item in self.raceList] 73 | 74 | 75 | # 仅供调试用 76 | if __name__ == '__main__': 77 | tftdb = TFTDB() 78 | tftdb.printTFTDB() 79 | result = tftdb.searchTFTDB(raceIds="9", jobIds="7", name="赛娜") 80 | print(result) 81 | -------------------------------------------------------------------------------- /gui/Form_hero.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form_hero 4 | 5 | 6 | 7 | 0 8 | 0 9 | 811 10 | 695 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | QTableView 18 | { 19 | font:13px "微软雅黑"; 20 | color: rgb(255, 255, 255); 21 | border: None; 22 | background: rgba(22,26,32, 200) 23 | 24 | 25 | } 26 | QLabel{ 27 | font:13px "微软雅黑"; 28 | color: rgb(255, 255, 255); 29 | } 30 | QGroupBox 31 | { 32 | font:13px "微软雅黑"; 33 | color: rgb(255, 255, 255); 34 | border: None; 35 | background: rgba(22,26,32, 200) 36 | 37 | 38 | } 39 | QLineEdit 40 | { 41 | background:none; 42 | } 43 | QTableView::pane 44 | { 45 | font:13px "微软雅黑"; 46 | color: rgb(255, 255, 255); 47 | border: None; 48 | 49 | } 50 | QScrollBar{ 51 | background-color:rgb(0, 0, 0); 52 | width:10px; 53 | } 54 | QScrollBar::handle{ 55 | image: url(data/Center.png) ; 56 | border:none; 57 | border-radius:5px; 58 | } 59 | QScrollBar::handle:hover{image: url(data/Center.png) ; } 60 | QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{background:none;} 61 | QToolTip{ 62 | border: 2px solid qconicalgradient(cx:0, cy:0, angle:135, stop:0 rgba(255, 255, 0, 69), stop:0.375 rgba(255, 255, 0, 69), stop:0.423533 rgba(251, 255, 0, 145), stop:0.45 rgba(247, 255, 0, 208), stop:0.477581 rgba(255, 244, 71, 130), stop:0.518717 rgba(255, 218, 71, 130), stop:0.55 rgba(255, 255, 0, 255), stop:0.57754 rgba(255, 203, 0, 130), stop:0.625 rgba(255, 255, 0, 69), stop:1 rgba(255, 255, 0, 69)); 63 | background-color: rgb(22,26,32); 64 | ridge:ridge; 65 | padding: 4px; 66 | border-radius:10px; 67 | } 68 | QPushButton{ 69 | color: qconicalgradient(cx:0, cy:0, angle:135, stop:0 rgba(255, 255, 0, 69), stop:0.375 rgba(255, 255, 0, 69), stop:0.423533 rgba(251, 255, 0, 145), stop:0.45 rgba(247, 255, 0, 208), stop:0.477581 rgba(255, 244, 71, 130), stop:0.518717 rgba(255, 218, 71, 130), stop:0.55 rgba(255, 255, 0, 255), stop:0.57754 rgba(255, 203, 0, 130), stop:0.625 rgba(255, 255, 0, 69), stop:1 rgba(255, 255, 0, 69)); 70 | border: 2px solid qconicalgradient(cx:0, cy:0, angle:135, stop:0 rgba(255, 255, 0, 69), stop:0.375 rgba(255, 255, 0, 69), stop:0.423533 rgba(251, 255, 0, 145), stop:0.45 rgba(247, 255, 0, 208), stop:0.477581 rgba(255, 244, 71, 130), stop:0.518717 rgba(255, 218, 71, 130), stop:0.55 rgba(255, 255, 0, 255), stop:0.57754 rgba(255, 203, 0, 130), stop:0.625 rgba(255, 255, 0, 69), stop:1 rgba(255, 255, 0, 69)); 71 | background-color: rgb(22,26,32); 72 | ridge:ridge; 73 | padding: 4px; 74 | border-radius:10px; 75 | 76 | font: 75 14pt "微软雅黑"; 77 | } 78 | QPushButton:hover { 79 | color: rgb(255, 255, 255); 80 | border: 2px solid qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 rgba(0, 0, 0, 255), stop:0.19397 rgba(0, 0, 0, 255), stop:0.202312 rgba(122, 97, 0, 255), stop:0.495514 rgba(76, 58, 0, 255), stop:0.504819 rgba(255, 255, 255, 255), stop:0.79 rgba(255, 255, 255, 255), stop:1 rgba(255, 158, 158, 255)); 81 | } 82 | QGroupBox{ 83 | color:rgb(255, 255, 255); 84 | font: 75 11pt "微软雅黑"; 85 | } 86 | 87 | QRadioButton{ 88 | color:rgb(182, 182, 182); 89 | font: 75 10pt "微软雅黑"; 90 | } 91 | QLineEdit{ 92 | color: rgb(255, 85, 0); 93 | font: 75 12pt "微软雅黑"; 94 | with:200px; 95 | } 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 费用: 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 职业: 113 | 114 | 115 | 116 | 6 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 羁绊: 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /gui/Form_hero.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'Form_hero.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.4 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form_hero(object): 15 | def setupUi(self, Form_hero): 16 | Form_hero.setObjectName("Form_hero") 17 | Form_hero.resize(811, 695) 18 | Form_hero.setStyleSheet("QTableView\n" 19 | "{\n" 20 | " font:13px \"微软雅黑\";\n" 21 | " color: rgb(255, 255, 255);\n" 22 | " border: None;\n" 23 | " background: rgba(22,26,32, 200) \n" 24 | "\n" 25 | "\n" 26 | "}\n" 27 | "QLabel{\n" 28 | "font:13px \"微软雅黑\";\n" 29 | "color: rgb(255, 255, 255);\n" 30 | "}\n" 31 | "QGroupBox\n" 32 | "{\n" 33 | " font:13px \"微软雅黑\";\n" 34 | " color: rgb(255, 255, 255);\n" 35 | " border: None;\n" 36 | " background: rgba(22,26,32, 200) \n" 37 | "\n" 38 | "\n" 39 | "}\n" 40 | "QLineEdit\n" 41 | "{\n" 42 | "background:none;\n" 43 | "}\n" 44 | "QTableView::pane\n" 45 | "{\n" 46 | " font:13px \"微软雅黑\";\n" 47 | " color: rgb(255, 255, 255);\n" 48 | " border: None;\n" 49 | " \n" 50 | "}\n" 51 | "QScrollBar{\n" 52 | " background-color:rgb(0, 0, 0);\n" 53 | " width:10px;\n" 54 | "}\n" 55 | "QScrollBar::handle{\n" 56 | " image: url(data/Center.png) ; \n" 57 | " border:none; \n" 58 | " border-radius:5px;\n" 59 | "} \n" 60 | "QScrollBar::handle:hover{image: url(data/Center.png) ; }\n" 61 | "QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{background:none;}\n" 62 | "QToolTip{\n" 63 | "border: 2px solid qconicalgradient(cx:0, cy:0, angle:135, stop:0 rgba(255, 255, 0, 69), stop:0.375 rgba(255, 255, 0, 69), stop:0.423533 rgba(251, 255, 0, 145), stop:0.45 rgba(247, 255, 0, 208), stop:0.477581 rgba(255, 244, 71, 130), stop:0.518717 rgba(255, 218, 71, 130), stop:0.55 rgba(255, 255, 0, 255), stop:0.57754 rgba(255, 203, 0, 130), stop:0.625 rgba(255, 255, 0, 69), stop:1 rgba(255, 255, 0, 69)); \n" 64 | " background-color: rgb(22,26,32);\n" 65 | " ridge:ridge;\n" 66 | " padding: 4px;\n" 67 | " border-radius:10px;\n" 68 | "}\n" 69 | "QPushButton{\n" 70 | " color: qconicalgradient(cx:0, cy:0, angle:135, stop:0 rgba(255, 255, 0, 69), stop:0.375 rgba(255, 255, 0, 69), stop:0.423533 rgba(251, 255, 0, 145), stop:0.45 rgba(247, 255, 0, 208), stop:0.477581 rgba(255, 244, 71, 130), stop:0.518717 rgba(255, 218, 71, 130), stop:0.55 rgba(255, 255, 0, 255), stop:0.57754 rgba(255, 203, 0, 130), stop:0.625 rgba(255, 255, 0, 69), stop:1 rgba(255, 255, 0, 69));\n" 71 | " border: 2px solid qconicalgradient(cx:0, cy:0, angle:135, stop:0 rgba(255, 255, 0, 69), stop:0.375 rgba(255, 255, 0, 69), stop:0.423533 rgba(251, 255, 0, 145), stop:0.45 rgba(247, 255, 0, 208), stop:0.477581 rgba(255, 244, 71, 130), stop:0.518717 rgba(255, 218, 71, 130), stop:0.55 rgba(255, 255, 0, 255), stop:0.57754 rgba(255, 203, 0, 130), stop:0.625 rgba(255, 255, 0, 69), stop:1 rgba(255, 255, 0, 69)); \n" 72 | " background-color: rgb(22,26,32);\n" 73 | " ridge:ridge;\n" 74 | " padding: 4px;\n" 75 | " border-radius:10px;\n" 76 | " \n" 77 | " font: 75 14pt \"微软雅黑\";\n" 78 | "}\n" 79 | "QPushButton:hover {\n" 80 | " color: rgb(255, 255, 255);\n" 81 | " border: 2px solid qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 rgba(0, 0, 0, 255), stop:0.19397 rgba(0, 0, 0, 255), stop:0.202312 rgba(122, 97, 0, 255), stop:0.495514 rgba(76, 58, 0, 255), stop:0.504819 rgba(255, 255, 255, 255), stop:0.79 rgba(255, 255, 255, 255), stop:1 rgba(255, 158, 158, 255)); \n" 82 | "}\n" 83 | "QGroupBox{\n" 84 | " color:rgb(255, 255, 255);\n" 85 | " font: 75 11pt \"微软雅黑\";\n" 86 | "}\n" 87 | "\n" 88 | "QRadioButton{\n" 89 | "color:rgb(182, 182, 182);\n" 90 | "font: 75 10pt \"微软雅黑\";\n" 91 | "}\n" 92 | "QLineEdit{\n" 93 | "color: rgb(255, 85, 0);\n" 94 | "font: 75 12pt \"微软雅黑\";\n" 95 | "with:200px;\n" 96 | "}\n" 97 | "\n" 98 | "\n" 99 | "") 100 | self.verticalLayout = QtWidgets.QVBoxLayout(Form_hero) 101 | self.verticalLayout.setObjectName("verticalLayout") 102 | self.groupBox_price = QtWidgets.QGroupBox(Form_hero) 103 | self.groupBox_price.setObjectName("groupBox_price") 104 | self.hbox_price = QtWidgets.QHBoxLayout(self.groupBox_price) 105 | self.hbox_price.setObjectName("hbox_price") 106 | self.verticalLayout.addWidget(self.groupBox_price) 107 | self.groupBox_job = QtWidgets.QGroupBox(Form_hero) 108 | self.groupBox_job.setObjectName("groupBox_job") 109 | self.hbox_job = QtWidgets.QHBoxLayout(self.groupBox_job) 110 | self.hbox_job.setSpacing(6) 111 | self.hbox_job.setObjectName("hbox_job") 112 | self.verticalLayout.addWidget(self.groupBox_job) 113 | self.groupBox_race = QtWidgets.QGroupBox(Form_hero) 114 | self.groupBox_race.setObjectName("groupBox_race") 115 | self.hbox_race = QtWidgets.QHBoxLayout(self.groupBox_race) 116 | self.hbox_race.setObjectName("hbox_race") 117 | self.verticalLayout.addWidget(self.groupBox_race) 118 | self.tabHero = QtWidgets.QTableWidget(Form_hero) 119 | self.tabHero.setObjectName("tabHero") 120 | self.tabHero.setColumnCount(0) 121 | self.tabHero.setRowCount(0) 122 | self.verticalLayout.addWidget(self.tabHero) 123 | 124 | self.retranslateUi(Form_hero) 125 | QtCore.QMetaObject.connectSlotsByName(Form_hero) 126 | 127 | def retranslateUi(self, Form_hero): 128 | _translate = QtCore.QCoreApplication.translate 129 | Form_hero.setWindowTitle(_translate("Form_hero", "Form")) 130 | self.groupBox_price.setTitle(_translate("Form_hero", "费用:")) 131 | self.groupBox_job.setTitle(_translate("Form_hero", "职业:")) 132 | self.groupBox_race.setTitle(_translate("Form_hero", "种族:")) 133 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging, sys 2 | from TFTDB import TFTDB 3 | from gui import Form_hero 4 | import settings 5 | import simulator 6 | 7 | from PyQt5.QtGui import QCursor 8 | from PyQt5.QtWidgets import QApplication, QDialog, QAbstractItemView, QRadioButton, QLineEdit, QPushButton 9 | from PyQt5 import QtCore 10 | from PyQt5.QtCore import Qt 11 | 12 | 13 | class FormHero(QDialog): 14 | def __init__(self, parent=None): 15 | super(QDialog, self).__init__(parent) # ui对象初始化的时候,须将自身指针传给ui类,以供其中控件对象初始化 16 | self.ui = Form_hero.Ui_Form_hero() 17 | self.ui.setupUi(self) 18 | self.filter = { 19 | "name": '', 20 | "raceIds": '', 21 | "jobIds": '', 22 | "price": 0 23 | } # 与TFTDB的searchTFTDB函数签名一一对应 24 | 25 | # 槽函数关联 26 | # self.hero_filter_ui.groupBox_race.clicked.connect(self.on_groupBox_race_clicked) 27 | 28 | # 设置一些基本的样式 29 | self.resize(566, 500) 30 | self.setAttribute(Qt.WA_TranslucentBackground) 31 | self.setCursor(QCursor(Qt.PointingHandCursor)) 32 | # 去掉标题和置顶 33 | self.setWindowFlags(Qt.Tool | Qt.WindowMaximizeButtonHint | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) 34 | # 窗口透明 35 | self.setAttribute(Qt.WA_TranslucentBackground) 36 | self.ui.verticalLayout.setContentsMargins(1, 0, 1, 0) 37 | 38 | # 禁止编辑 39 | self.ui.tabHero.setEditTriggers(QAbstractItemView.NoEditTriggers) 40 | # 水平表格头显示和隐藏 41 | self.ui.tabHero.horizontalHeader().setVisible(False) 42 | # 垂直表格头显示和隐藏 43 | self.ui.tabHero.verticalHeader().setVisible(False) 44 | # 隐藏分割线 45 | self.ui.tabHero.setShowGrid(False) 46 | # 绑定双击事件 47 | # self.hero_filter_ui.tabHero.doubleClicked.connect(double_click_Hero_Add) 48 | # 动态加载筛选条件控件 49 | # 设置一些边框距离. 50 | self.ui.hbox_price.setContentsMargins(20, 20, 0, 0) 51 | self.ui.hbox_job.setContentsMargins(20, 20, 0, 0) 52 | self.ui.hbox_race.setContentsMargins(20, 20, 0, 0) 53 | 54 | # -----------------费用 55 | prices = ['全部', '1金币', '2金币', '3金币', '4金币', '5金币'] 56 | for price in prices: 57 | rb_price = QRadioButton(price) 58 | # 绑定事件槽 并且传递一个参数,用来标记,是什么类型的条件 59 | rb_price.pressed.connect(lambda: self.rb_click("price")) 60 | self.ui.hbox_price.addWidget(rb_price) 61 | 62 | # -----------------职业 63 | jobs = ['全部'] 64 | jobs.extend(tftdb.getJobNameList()) 65 | for job_name in jobs: 66 | rb_job = QRadioButton(job_name) 67 | rb_job.pressed.connect(lambda: self.rb_click("job")) 68 | self.ui.hbox_job.addWidget(rb_job) 69 | 70 | # -----------------种族 71 | races = ['全部'] 72 | races.extend(tftdb.getRaceNameList()) 73 | for race_name in races: 74 | rb_race = QRadioButton(race_name) 75 | rb_race.pressed.connect(lambda: self.rb_click("race")) 76 | self.ui.hbox_race.addWidget(rb_race) 77 | 78 | # -----------------按称号或名字搜索 79 | led_keyword = QLineEdit() 80 | self.ui.hbox_price.addWidget(led_keyword) 81 | pb_search = QPushButton('搜索') 82 | pb_search.pressed.connect(lambda: self.pb_click()) 83 | self.ui.hbox_price.addWidget(pb_search) 84 | # 尾部增加一个弹簧占位置 85 | self.ui.hbox_price.addStretch() 86 | 87 | # 显示 88 | self.show() 89 | self.setVisible(False) 90 | 91 | # 圆形选择点的槽函数,注意filter的键值应该和 @TFTDB.py:searchTFTDB() 函数签名一致 92 | @QtCore.pyqtSlot() 93 | def rb_click(self, tag) -> None: 94 | text = self.sender().text() # 从发送信号的QRadioButton控件中获取值 95 | if tag == "price": 96 | if text == "全部": 97 | self.filter["price"] = 0 98 | else: 99 | self.filter["price"] = int(text.replace('金币', '')) 100 | if tag == "race": 101 | if text == "全部": 102 | self.filter["raceIds"] = '' 103 | else: 104 | self.filter["raceIds"] = tftdb.getRaceIdByName(text) 105 | if tag == "job": 106 | if text == "全部": 107 | self.filter["jobIds"] = '' 108 | else: 109 | self.filter["jobIds"] = tftdb.getJobIdByName(text) 110 | 111 | result = tftdb.searchTFTDB(raceIds=self.filter["raceIds"], 112 | jobIds=self.filter["jobIds"], 113 | price=self.filter["price"]) 114 | logging.info(result) 115 | 116 | # 搜索按钮按下的槽函数 117 | @QtCore.pyqtSlot() 118 | def pb_click(self) -> None: 119 | self.filter["name"] = self.sender().text() 120 | result = tftdb.searchTFTDB(name=self.filter["name"]) 121 | logging.info(result) 122 | 123 | 124 | class KeyboardMonitor: 125 | def __init__(self): 126 | self.keyboardListener = simulator.keyboard.Listener(on_press=self.keyPress) 127 | self.keyboardListener.start() 128 | 129 | def __del__(self): 130 | if self.keyboardListener: 131 | self.keyboardListener.stop() 132 | 133 | # D键检测:d牌后自动判断并拿牌 134 | def keyPress(self, key) -> None: 135 | print(key) 136 | try: 137 | # 对于pynput包,只有普通按键有char属性域 138 | if key.char == 'd': 139 | fiveCards = simulator.getFiveTitles() 140 | logging.info(fiveCards) 141 | for index, card in enumerate(fiveCards): 142 | if card in settings.myBuild: 143 | simulator.pag.moveTo(index * 200 + 550, 1000, 0.1, simulator.pag.easeOutQuad) 144 | simulator.pag.click() 145 | 146 | except AttributeError: 147 | # 特殊按键按下时不做反应 148 | pass 149 | 150 | 151 | if __name__ == '__main__': 152 | # 加载初始配置 153 | settings.settings_init() 154 | # 加载数据库类 155 | tftdb = TFTDB() 156 | # 创建PyQt项目 157 | app = QApplication(sys.argv) 158 | # 创建英雄筛选界面 159 | form_hero = FormHero() 160 | # 前面对象都实例化了再创建键盘监听器 161 | key_monitor = KeyboardMonitor() 162 | 163 | sys.exit(app.exec()) 164 | -------------------------------------------------------------------------------- /assets/api.js: -------------------------------------------------------------------------------- 1 | // 当前赛季 2 | window.CurrentSet = 's6'; 3 | // 阵容渠道id 4 | window.TFTChannelId = '6'; 5 | /** 6 | * 资料接口管理 7 | * 为了因对不同赛季的目录地址不一样,导致目录混乱,因此手动维护一个目录,并提供接口地址 8 | * 注意 9 | * 1 为了提高数据的命中率,添加地址数据时,越新的数据,key的位置应该越靠前 10 | * 2 赛季字段,数据是2019.S2这种格式,应该把只保留S2,并转为小写s. 11 | */ 12 | window.DataUrlManager = { 13 | hero_data: { 14 | 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/chess.js?v=' + (Date.now() / 600000 >> 0), 15 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/12.4-2022.S6/chess.js?v=' + (Date.now() / 600000 >> 0), 16 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/11.22-2021.S6/chess.js?v=' + (Date.now() / 600000 >> 0), 17 | 's5': '//game.gtimg.cn/images/lol/act/img/tft/js/11.15-2021.S5/chess.js?v=' + (Date.now() / 600000 >> 0), 18 | 's4': '//game.gtimg.cn/images/lol/act/img/tft/js/11.8-2021.S4/chess.js', 19 | 's3': '//game.gtimg.cn/images/lol/act/img/tft/js/10.18-2020.S3/chess.js', 20 | 's2': '//game.gtimg.cn/images/lol/act/img/tft/js/10.5-2020.S2/chess.js', 21 | 's1': '//game.gtimg.cn/images/lol/act/img/tft/js/9.21-2019.S1/chess.js' 22 | }, 23 | race_data: { 24 | 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/race.js?v=' + (Date.now() / 600000 >> 0), 25 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/12.4-2022.S6/race.js?v=' + (Date.now() / 600000 >> 0), 26 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/11.22-2021.S6/race.js?v=' + (Date.now() / 600000 >> 0), 27 | 's5': '//game.gtimg.cn/images/lol/act/img/tft/js/11.15-2021.S5/race.js?v=' + (Date.now() / 600000 >> 0), 28 | 's4': '//game.gtimg.cn/images/lol/act/img/tft/js/11.8-2021.S4/race.js', 29 | 's3': '//game.gtimg.cn/images/lol/act/img/tft/js/10.18-2020.S3/race.js', 30 | 's2': '//game.gtimg.cn/images/lol/act/img/tft/js/10.5-2020.S2/race.js', 31 | 's1': '//game.gtimg.cn/images/lol/act/img/tft/js/9.21-2019.S1/race.js' 32 | }, 33 | job_data: { 34 | 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/job.js?v=' + (Date.now() / 600000 >> 0), 35 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/12.4-2022.S6/job.js?v=' + (Date.now() / 600000 >> 0), 36 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/11.22-2021.S6/job.js?v=' + (Date.now() / 600000 >> 0), 37 | 's5': '//game.gtimg.cn/images/lol/act/img/tft/js/11.15-2021.S5/job.js?v=' + (Date.now() / 600000 >> 0), 38 | 's4': '//game.gtimg.cn/images/lol/act/img/tft/js/11.8-2021.S4/job.js', 39 | 's3': '//game.gtimg.cn/images/lol/act/img/tft/js/10.18-2020.S3/job.js', 40 | 's2': '//game.gtimg.cn/images/lol/act/img/tft/js/10.5-2020.S2/job.js', 41 | 's1': '//game.gtimg.cn/images/lol/act/img/tft/js/9.21-2019.S1/job.js' 42 | }, 43 | equipment_data: { 44 | 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/equip.js?v=' + (Date.now() / 600000 >> 0), 45 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/12.4-2022.S6/equip.js?v=' + (Date.now() / 600000 >> 0), 46 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/11.22-2021.S6/equip.js?v=' + (Date.now() / 600000 >> 0), 47 | 's5': '//game.gtimg.cn/images/lol/act/img/tft/js/11.15-2021.S5/equip.js?v=' + (Date.now() / 600000 >> 0), 48 | 's4': '//game.gtimg.cn/images/lol/act/img/tft/js/11.8-2021.S4/equip.js', 49 | 's3': '//game.gtimg.cn/images/lol/act/img/tft/js/10.18-2020.S3/equip.js', 50 | 's2': '//game.gtimg.cn/images/lol/act/img/tft/js/10.5-2020.S2/equip.js', 51 | 's1': '//game.gtimg.cn/images/lol/act/img/tft/js/9.21-2019.S1/equip.js' 52 | }, 53 | little_hero_data: { 54 | 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/hero.js?v=' + (Date.now() / 600000 >> 0), 55 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/12.4-2022.S6/hero.js?v=' + (Date.now() / 600000 >> 0), 56 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/11.22-2021.S6/hero.js?v=' + (Date.now() / 600000 >> 0), 57 | 's5': '//game.gtimg.cn/images/lol/act/img/tft/js/11.15-2021.S5/hero.js?v=' + (Date.now() / 600000 >> 0), 58 | 's4': '//game.gtimg.cn/images/lol/act/img/tft/js/11.8-2021.S4/hero.js', 59 | 's3': '//game.gtimg.cn/images/lol/act/img/tft/js/10.18-2020.S3/hero.js', 60 | }, 61 | linelist: { 62 | 's6': '//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupJson/s6/' + window.TFTChannelId + '/lineup_detail_total.json?v=' + (Date.now() / 180000 >> 0), 63 | 's5': '//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupJson/s5/' + window.TFTChannelId + '/lineup_detail_total.json?v=' + (Date.now() / 180000 >> 0), 64 | 's4': '//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupJson/s4/6/lineup_detail_total.json', 65 | 's3': '//lol.qq.com/act/AutoCMS/publish/LOLAct/TFTlinelist_new_set3/TFTlinelist_new_set3.js', 66 | 's2': '//lol.qq.com/act/AutoCMS/publish/LOLAct/TFTLineup_V3/TFTLineup_V3.js', 67 | 's1': '' 68 | }, 69 | double_linelist: { 70 | 's6': '//game.gtimg.cn/images/lol/act/tftzlkauto/json/doubleLineupJson/s6/' + window.TFTChannelId + '/doubleLineup_detail_total.json?v=' + (Date.now() / 180000 >> 0), 71 | }, 72 | authorList: '//game.gtimg.cn/images/lol/act/tftzlkauto/json/authorJson/author.json?v=' + (Date.now() / 100000 >> 0), 73 | // equipment_strength: { 74 | // 's3': '//lol.qq.com/act/AutoCMS/publish/LOLAct/TFTequipment_set3/TFTequipment_set3.js' 75 | // }, 76 | hex_data: { 77 | 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/hex.js?v=' + (Date.now() / 180000 >> 0), 78 | // 's6': '//game.gtimg.cn/images/lol/act/img/tft/js/12.4-2022.S6/hex.js?v=' + (Date.now() / 180000 >> 0), 79 | // 's6': '//game.gtimg.cn/images/lol/act/tftzlkauto/json/hexJson/hex.json?v=' + (Date.now() / 180000 >> 0), 80 | }, 81 | /**获取某个数据类型,全部的数据地址的key数组 */ 82 | getUrlAllKey: function (data_type) { 83 | var rs = []; 84 | var temp_data = this[data_type]; 85 | if (temp_data) { 86 | for (var key in temp_data) { 87 | rs.push(key); 88 | } 89 | } 90 | return rs; 91 | } 92 | }; 93 | /** 94 | * 图片目录管理 95 | * 为了因对不同赛季的目录地址不一样,导致目录混乱,因此手动维护一个目录,并提供图片拼接地址 96 | */ 97 | window.PicUrlManager = { 98 | /**使用分析logo作为默认图片,获取图片地址发生错误时使用 */ 99 | defaultPic: '//game.gtimg.cn/images/lol/act/a20190704tft/share.png', 100 | /**英雄头像*/ 101 | hero_avatar: { 102 | origin: '//game.gtimg.cn/images/lol/act/img/tft/hero-icon/icon_{{pic_name}}.png', 103 | s1: '//game.gtimg.cn/images/lol/act/img/tft/champions/s1icon/{{pic_name}}.png', 104 | s2: '//game.gtimg.cn/images/lol/act/img/tft/champions/s2icon/{{pic_name}}.png', 105 | s3: '//game.gtimg.cn/images/lol/act/img/tft/champions/{{pic_name}}.png', 106 | s4: '//game.gtimg.cn/images/lol/act/img/tft/champions/{{pic_name}}.png', 107 | s5: '//game.gtimg.cn/images/lol/act/img/tft/champions/{{pic_name}}.png', 108 | s6: '//game.gtimg.cn/images/lol/act/img/tft/champions/{{pic_name}}.png', 109 | }, 110 | /**英雄大图1125x443*/ 111 | hero_pic_1: { 112 | s1: '//game.gtimg.cn/images/lol/tft/champions/950x375/{{pic_name}}.png', 113 | s2: '//game.gtimg.cn/images/lol/tft/champions/950x375/{{pic_name}}.jpg', 114 | s3: '//game.gtimg.cn/images/lol/tft/champions/950x375/{{pic_name}}.png', 115 | s4: '//game.gtimg.cn/images/lol/tft/champions/950x375/{{pic_name}}.jpg', 116 | s5: '//game.gtimg.cn/images/lol/tft/champions/950x375/{{pic_name}}.jpg', 117 | s6: '//game.gtimg.cn/images/lol/tft/champions/950x375/{{pic_name}}.jpg', 118 | }, 119 | /**英雄大图624x318 */ 120 | hero_pic_2: { 121 | s2: '//game.gtimg.cn/images/lol/tft/cham-icons/624x318/{{pic_name}}.jpg', 122 | s3: '//game.gtimg.cn/images/lol/tft/cham-icons/624x318/{{pic_name}}.png', 123 | s4: '//game.gtimg.cn/images/lol/tft/cham-icons/624x318/{{pic_name}}.jpg', 124 | s5: '//game.gtimg.cn/images/lol/tft/cham-icons/624x318/{{pic_name}}.jpg', 125 | s6: '//game.gtimg.cn/images/lol/tft/cham-icons/624x318/{{pic_name}}.jpg', 126 | }, 127 | // 控制整个页面切换"皮肤头像" 128 | // SkinVersion: milo.cookie.get('guide_tft_skin_version') ? milo.cookie.get('guide_tft_skin_version') : CurrentSet, 129 | // 20200422隐藏皮肤头像切换功能 130 | SkinVersion: CurrentSet, 131 | /**获取图片地址 132 | * @param pic_type 图片类型:race_job_icon,skill...... 133 | * @param season_id 赛季id:1,2....... 134 | * @param pic_name 图片名称 135 | */ 136 | getPicUrl: function (pic_type, season_id, pic_name) { 137 | if (!season_id || !pic_type || pic_name === undefined) return this.defaultPic; 138 | var basePicString = _.get(this, pic_type + '.' + season_id); 139 | if (!basePicString) return this.defaultPic; 140 | return basePicString.replace('{{pic_name}}', pic_name); 141 | } 142 | }; 143 | 144 | /** 145 | * 本类在浏览器自带的Fetch基础上,封装的一个具有出错重复请求的类, 146 | * 如果接口需要轮询,请直接使用fetch,减少内存开销 147 | * @param url 请求地址 148 | * @param catchData 请求附带的设置对象 149 | */ 150 | var FetchRequest = /** @class */ (function () { 151 | function FetchRequest(url, catch_data) { 152 | var _this = this; 153 | //重复尝试次数 154 | this.try_times = 1; 155 | //重复尝试间隔时间 156 | this.interval_time = 1000; 157 | this.try_timeout = undefined; 158 | //传递结果的Promise 159 | this.res_promise = undefined; 160 | this.res_promise_resolve = undefined; 161 | this.res_promise_reject = undefined; 162 | //请求地址 163 | this.url = undefined; 164 | //附带发送的数据 165 | this.catch_data = undefined; 166 | this.url = url; 167 | this.catch_data = catch_data; 168 | //实例化一个传递结果的promise,并代理其resolve和reject 169 | this.res_promise = new Promise(function (resolve, reject) { 170 | _this.res_promise_resolve = resolve; 171 | _this.res_promise_reject = reject; 172 | _this.try_request(); 173 | }); 174 | } 175 | FetchRequest.prototype.try_request = function () { 176 | //还有尝试次数 177 | if (this.try_times > 0) { 178 | --this.try_times; 179 | this.request(); 180 | } 181 | //没有尝试次数了,退出 182 | else { 183 | this.request_fail(); 184 | } 185 | }; 186 | FetchRequest.prototype.request = function () { 187 | var _this = this; 188 | fetch(this.url, this.catch_data).then(function (res) { 189 | if (res.ok) { 190 | _this.request_success(res); 191 | } 192 | else { 193 | throw new Error("服务器连通,但未正常响应请求"); 194 | } 195 | })["catch"](function (error) { 196 | console.warn("请求", _this.url, "发生错误,继续尝试:", error); 197 | clearTimeout(_this.try_timeout); 198 | _this.try_timeout = setTimeout(function () { 199 | _this.try_request(); 200 | }, _this.interval_time); 201 | }); 202 | }; 203 | /**接口请求成功 */ 204 | FetchRequest.prototype.request_success = function (res) { 205 | this.res_promise_resolve(res); 206 | this.clear_memory(); 207 | }; 208 | /**接口请求失败 */ 209 | FetchRequest.prototype.request_fail = function () { 210 | console.error("请求失败,重试次数耗尽", this.url); 211 | this.res_promise_reject("请求失败,重试次数耗尽 " + this.url); 212 | this.clear_memory(); 213 | }; 214 | /**清理内存*/ 215 | FetchRequest.prototype.clear_memory = function () { 216 | clearTimeout(this.try_timeout); 217 | this.res_promise_resolve = undefined; 218 | this.res_promise_reject = undefined; 219 | }; 220 | return FetchRequest; 221 | }()); 222 | function fetchRequest(url, catchData) { 223 | return new FetchRequest(url, catchData).res_promise; 224 | }; 225 | /** 226 | * 用于请求js文件,带错误重试 227 | * 放于全局作用域下加载 228 | * @param url 请求地址 229 | * @param datakey 该js加载后,会生成的全局变量名称,用来检查js是否正确加载,如js里有多个,写其中一个 230 | * */ 231 | function FetchDataScript(url, datakey, charset) { 232 | /**重复发起几次请求,直到次数耗尽或请求成功,默认4次 */ 233 | this.tryTimes = 4; 234 | this.intervalTime = 200; 235 | this.url = url; 236 | this.datakey = datakey; 237 | /**借位的promise对象,代替原生fetch返回的promise响应 */ 238 | this.promise = new Promise(function (resolve, reject) { 239 | if (window[datakey]) { 240 | resolve(window[datakey]); 241 | return; 242 | } 243 | var tryFunction = function () { 244 | --this.tryTimes; 245 | var jsonpScript = document.createElement('script'); 246 | charset && jsonpScript.setAttribute('charset', charset); 247 | jsonpScript.setAttribute('src', this.url); 248 | jsonpScript.onerror = function (error) { 249 | document.getElementsByTagName('head')[0].removeChild(jsonpScript); 250 | if (this.tryTimes > 0) { 251 | console.warn('请求' + this.url + '失败,继续尝试:', error); 252 | setTimeout(function () { 253 | tryFunction(); 254 | }.bind(this), this.intervalTime); 255 | } else { 256 | reject('请求' + this.url + '次数耗尽,请检查服务情况'); 257 | console.error('请求' + this.url + '次数耗尽,请检查服务情况', error); 258 | } 259 | }.bind(this); 260 | jsonpScript.onload = function () { 261 | document.getElementsByTagName('head')[0].removeChild(jsonpScript); 262 | var data = window[datakey]; 263 | if (!data) { 264 | jsonpScript.onerror(); 265 | return; 266 | } 267 | resolve(data); 268 | }.bind(this); 269 | document.getElementsByTagName('head')[0].appendChild(jsonpScript); 270 | }.bind(this); 271 | tryFunction(); 272 | }.bind(this)); 273 | }; 274 | function fetchDataScript(url, datakey, charset) { 275 | return new FetchDataScript(url, datakey, charset).promise; 276 | }; 277 | 278 | /** 279 | * jsonp 280 | * */ 281 | function FetchJsonpScript(url, callbackName) { 282 | /**重复发起几次请求,直到次数耗尽或请求成功,默认2次 */ 283 | this.tryTimes = 2; 284 | this.intervalTime = 500; 285 | this.url = url; 286 | /**借位的promise对象,代替原生fetch返回的promise响应 */ 287 | this.promise = new Promise(function (resolve, reject) { 288 | var tryFunction = function () { 289 | --this.tryTimes; 290 | // 接收jsonp的函数 291 | window[callbackName] = function (res) { 292 | resolve(res); 293 | delete window[callbackName]; 294 | document.getElementsByTagName('head')[0].removeChild(jsonpScript); 295 | }; 296 | var jsonpScript = document.createElement('script'); 297 | jsonpScript.setAttribute('src', this.url); 298 | jsonpScript.onerror = function (error) { 299 | document.getElementsByTagName('head')[0].removeChild(jsonpScript); 300 | if (this.tryTimes > 0) { 301 | console.warn('请求' + this.url + '失败,继续尝试:', error); 302 | setTimeout(function () { 303 | tryFunction(); 304 | }.bind(this), this.intervalTime); 305 | } else { 306 | reject('请求' + this.url + '次数耗尽,请检查服务情况'); 307 | console.error('请求' + this.url + '次数耗尽,请检查服务情况:', error); 308 | } 309 | }.bind(this); 310 | document.getElementsByTagName('head')[0].appendChild(jsonpScript); 311 | }.bind(this); 312 | tryFunction(); 313 | }.bind(this)); 314 | }; 315 | function fetchJsonpScript(url, callbackName) { 316 | return new FetchJsonpScript(url, callbackName).promise; 317 | }; 318 | 319 | /** 320 | * 用于请求返回结果如"var LWDFramework_Swoole = {}"的接口,返回相同的变量名,不做数据缓存,带错误重试 321 | * 放于全局作用域下加载 322 | * @param url 请求地址 323 | * @param datakey 该js加载后,会生成的全局变量名称,用来检查js是否正确加载,如js里有多个,写其中一个 324 | * */ 325 | function FetchVariableDataScript(url, datakey, charset) { 326 | /**重复发起几次请求,直到次数耗尽或请求成功,默认4次 */ 327 | this.tryTimes = 4; 328 | this.intervalTime = 200; 329 | this.url = url; 330 | this.datakey = datakey; 331 | /**借位的promise对象,代替原生fetch返回的promise响应 */ 332 | this.promise = new Promise(function (resolve, reject) { 333 | var tryFunction = function () { 334 | --this.tryTimes; 335 | var jsonpScript = document.createElement('script'); 336 | charset && jsonpScript.setAttribute('charset', charset); 337 | jsonpScript.setAttribute('src', this.url); 338 | jsonpScript.onerror = function (error) { 339 | document.getElementsByTagName('head')[0].removeChild(jsonpScript); 340 | if (this.tryTimes > 0) { 341 | console.warn('请求' + this.url + '失败,继续尝试:', error); 342 | setTimeout(function () { 343 | tryFunction(); 344 | }.bind(this), this.intervalTime); 345 | } else { 346 | reject('请求' + this.url + '次数耗尽,请检查服务情况'); 347 | console.error('请求' + this.url + '次数耗尽,请检查服务情况', error); 348 | } 349 | }.bind(this); 350 | jsonpScript.onload = function () { 351 | var data = window[datakey]; 352 | delete window[datakey]; 353 | if (!data) { 354 | jsonpScript.onerror(); 355 | return; 356 | } 357 | document.getElementsByTagName('head')[0].removeChild(jsonpScript); 358 | resolve(data); 359 | }.bind(this); 360 | document.getElementsByTagName('head')[0].appendChild(jsonpScript); 361 | }.bind(this); 362 | tryFunction(); 363 | }.bind(this)); 364 | }; 365 | function fetchVariableDataScript(url, datakey, charset) { 366 | return new FetchVariableDataScript(url, datakey, charset).promise; 367 | }; 368 | 369 | /** 370 | * 用于请求几天前的数据,如果前一天没有数据,则请求再往前一天的数据,最多请求到5天前 371 | * @param interface 请求接口 372 | * @param callbackName jsonp调用方法名称 373 | * @param masterId 大神puuid 374 | * @param area 大区id 375 | */ 376 | async function requestDataBeforeDays(interface, callbackName, area, masterId) { 377 | var date = window.TFTFuncLib.pushDay(new Date(), -1).split(' ')[0]; 378 | backDate = date.replace(new RegExp("/", "g"), ''); 379 | // console.warn(backDate) 380 | var resData = await window.fetchJsonpScript(masterId ? interface(backDate, callbackName, masterId, area) : interface(backDate, callbackName, area), callbackName);//'LWDFramework_Swoole' 381 | // console.log(resData) 382 | if(_.get(resData, 'code') === 0) { 383 | var tryRequestData = async function (resData, pushDays) { 384 | var rData = _.get(resData, 'data.result'); 385 | if(!rData || !rData.length) { 386 | date = window.TFTFuncLib.pushDay(new Date(), pushDays).split(' ')[0]; 387 | backDate = date.replace(new RegExp("/", "g"), ''); 388 | // console.warn(backDate) 389 | var data = await window.fetchJsonpScript(masterId ? interface(backDate, callbackName, masterId, area) : interface(backDate, callbackName, area), callbackName); 390 | // console.log(data) 391 | return data; 392 | } 393 | return resData; 394 | }.bind(this); 395 | resData = await tryRequestData(resData, -2); 396 | resData = await tryRequestData(resData, -3); 397 | resData = await tryRequestData(resData, -4); 398 | resData = await tryRequestData(resData, -5); 399 | } 400 | if(resData && _.get(resData, 'data.result')) { 401 | resData.updateDate = date; 402 | masterId && (resData.masterId = masterId); 403 | area && (resData.area = area); 404 | return resData; 405 | } else { 406 | return Promise.reject('数据为空'); 407 | } 408 | }; 409 | /** 410 | * 接口请求成功则缓存,输出一个返回请求结果副本的函数 411 | * 请求失败则清空缓存可发起一个新的请求 412 | * @param interfaceType 请求接口类型 413 | * @param callbackName jsonp调用方法名称 414 | * @param masterId 大神puuid 415 | * @param area 大区id 416 | */ 417 | function requestDataByDays(interfaceType, callbackName, masterId, area) { 418 | if(!interfaceType) return Promise.reject('请求接口类型为空'); 419 | if(!masterId || !area) return Promise.reject('参数为空'); 420 | window.requestDataByDays[interfaceType] || (window.requestDataByDays[interfaceType] = {}); 421 | 422 | var key = masterId + '_' + area; 423 | if(window.requestDataByDays[interfaceType][key]) return window.requestDataByDays[interfaceType][key]; 424 | return window.requestDataByDays[interfaceType][key] = window.requestDataBeforeDays(window.ApiManager[interfaceType], callbackName, area, masterId).then(function(res) { 425 | if(!res) return Promise.reject('接口请求失败'); 426 | if(_.get(res, 'code') !== 0) return Promise.reject(_.get(res, 'msg') + ' code:' + _.get(res, 'code')); 427 | var rData = _.get(res, 'data.result'); 428 | if(!rData || !rData.length) return Promise.reject('暂无数据'); 429 | return function() { 430 | return _.cloneDeep(res); 431 | }; 432 | }).catch(function(err) { 433 | window.requestDataByDays[interfaceType][key] = null; 434 | console.error(err); 435 | return Promise.reject(err); 436 | }); 437 | }; 438 | 439 | /** 440 | * 接口请求成功则缓存 441 | * 请求失败则清空缓存可发起一个新的请求 442 | * @param interfaceType 请求接口类型 443 | * @param area 大区id 444 | */ 445 | function requestDataByDaysForRanking(interfaceType, callbackName, area) { 446 | if(!interfaceType) return Promise.reject('请求接口类型为空'); 447 | if(!area) return Promise.reject('参数为空'); 448 | window.requestDataByDaysForRanking[interfaceType] || (window.requestDataByDaysForRanking[interfaceType] = {}); 449 | 450 | if(window.requestDataByDaysForRanking[interfaceType][area]) return window.requestDataByDaysForRanking[interfaceType][area]; 451 | return window.requestDataByDaysForRanking[interfaceType][area] = window.requestDataBeforeDays(window.ApiManager[interfaceType], callbackName, area).then(function (res) { 452 | if(!res) return Promise.reject('接口请求失败'); 453 | if(_.get(res, 'code') !== 0) return Promise.reject(_.get(res, 'msg') + ' code:' + _.get(res, 'code')); 454 | var rData = _.get(res, 'data.result'); 455 | if(!rData || !rData.length) return Promise.reject('暂无数据'); 456 | return res; 457 | }).catch(function (err) { 458 | window.requestDataByDaysForRanking[interfaceType][area] = null; 459 | console.error(err); 460 | return Promise.reject(err); 461 | }); 462 | }; 463 | 464 | // 登录 465 | var TFTLogin = { 466 | areaCookieKey: '', 467 | init: function () { 468 | milo.ready(function () { 469 | need("biz.login", function (LoginManager) { 470 | LoginManager.checkLogin(function () { 471 | // LoginManager.getUserFace(function (data) { 472 | // window.vuex.commit('setPlayerUserFace', data.userFace); 473 | // }); 474 | var areaCookieKey = 'area' + LoginManager.getUserUin(); 475 | var cookieValue = milo.cookie.get(areaCookieKey); 476 | //登录且绑定大区 477 | if (cookieValue) { 478 | var areaInfo = cookieValue.split('-'); 479 | window.vuex.commit('setPlayerNickname', areaInfo[1]); 480 | window.vuex.commit('setPlayerArea', areaInfo[0]); 481 | this.getPlayerInfo(Number(areaInfo[2])); 482 | } else if (LoginManager.getUserUin() && !cookieValue) { 483 | //登录未绑定大区 484 | // this.changeArea(); 485 | } 486 | 487 | }.bind(this), function () { 488 | //未登录 489 | }.bind(this)); 490 | 491 | }.bind(this)); 492 | 493 | }.bind(this)); 494 | }, 495 | login: function () { 496 | need("biz.login", function (LoginManager) { 497 | LoginManager.init({ 498 | needReloadPage: true 499 | }); 500 | LoginManager.login(); 501 | }); 502 | return false; 503 | }, 504 | logout: function () { 505 | need("biz.login", function (LoginManager) { 506 | LoginManager.logout(function () { 507 | window.vuex.commit('setPlayerNickname', null); 508 | window.vuex.commit('setPlayerArea', null); 509 | window.vuex.commit('setWhiteAuthorData', null); 510 | window.vuex.commit('setPlayerUserFace', null); 511 | milo.cookie.set(this.areaCookieKey, ''); 512 | }.bind(this)); 513 | }.bind(this)); 514 | 515 | return false; 516 | }, 517 | changeArea: function () { 518 | need(["biz.roleselector"], function (RoleSelector) { 519 | RoleSelector.init({ 520 | 'gameId': 'lol', 521 | 'submitEvent': function (roleObject) { 522 | var iArea = roleObject.submitData['areaid']; 523 | var sRoleName = roleObject.submitData['rolename']; 524 | areaCookieKey = "area" + roleObject.submitData['roleid']; 525 | // console.log(roleObject); 526 | milo.cookie.set(areaCookieKey, LOLServerSelect.zoneToName(iArea) + '-' + sRoleName + '-' + iArea, false); 527 | alert('大区绑定成功!当前绑定大区【' + LOLServerSelect.zoneToName(iArea) + '】'); 528 | window.vuex.commit('setPlayerNickname', sRoleName); 529 | window.vuex.commit('setPlayerArea', LOLServerSelect.zoneToName(iArea)); 530 | 531 | this.getPlayerInfo(Number(iArea)); //获取玩家信息 532 | }.bind(this) 533 | // 'cancelEvent': function () { 534 | // window.location.href = '//lol.qq.com/guides/index.shtml'; 535 | // } 536 | }); 537 | RoleSelector.show(); 538 | }.bind(this)); 539 | }, 540 | // 请求玩家信息接口次数 541 | getPlayerInfoTimes: 0, 542 | // 获取玩家信息 543 | getPlayerInfo: function (area) { 544 | ApiManager.requestPlayerInfo(area).then(function (resp) { 545 | if (resp.MobilePlayerInfo.status === 0) { 546 | var profile = _.head(resp.MobilePlayerInfo.msg.res.uuid_prifle_list); 547 | var logoUrl = profile.logo_url.replace('http://', '//'); 548 | logoUrl = this.parseLogoUrl(logoUrl); 549 | profile && window.vuex.commit('setPlayerUserFace', logoUrl); 550 | this.getPlayerInfoTimes = 0; 551 | } else { 552 | this.getPlayerInfoTimes++; 553 | if (this.getPlayerInfoTimes < 5) this.getPlayerInfo(area); 554 | else this.logout(); 555 | } 556 | }.bind(this)); 557 | // 判断是否是白名单 558 | ApiManager.requestWhiteAuthorData().then(function (authorResp) { 559 | if (_.get(authorResp, 'status') === 0) { 560 | if (_.get(authorResp, 'data.allow') === 1) { 561 | window.vuex.commit('setWhiteAuthorData', true); 562 | } 563 | } 564 | }.bind(this)); 565 | }, 566 | //判断掌盟头像是否需要加尺寸参数 567 | parseLogoUrl: function (o) { 568 | var logoSizeParam = '/0'; 569 | if (typeof (o) === 'string') { 570 | if (!this.judgeEndStr(o, logoSizeParam)) { 571 | if (o.indexOf('qtl_user') !== -1 || o.indexOf('//p.qpic.cn/qtlinfo') !== -1) { 572 | o += logoSizeParam; 573 | } 574 | } 575 | return o; 576 | } 577 | if (typeof (o) === 'object') { 578 | for (var i = 0, j = o.length; i < j; ++i) { 579 | var obj = o[i]; 580 | var logoUrl = obj.logo_url; 581 | if (!this.judgeEndStr(logoUrl, logoSizeParam)) { 582 | if (logoUrl.indexOf('qtl_user') !== -1 || logoUrl.indexOf('//p.qpic.cn/qtlinfo') !== -1) { 583 | logoUrl += logoSizeParam; 584 | } 585 | obj.logo_url = logoUrl; 586 | } 587 | } 588 | return o; 589 | } 590 | }, 591 | //判断a字符串结尾是否有b字符串 592 | judgeEndStr: function (a, b) { 593 | var d = a.length - b.length; 594 | return (d >= 0 && a.lastIndexOf(b) === d); 595 | } 596 | }; 597 | /** 598 | * API管理, 所有API都封装在此 599 | */ 600 | window.ApiManager = { 601 | /** 602 | * 基于FetchRequest封装的Promise 603 | * @param url 请求地址 604 | * @param catchData 请求附带的设置对象 605 | * @param type 不填: 默认请求, 'script': 获取js脚本 606 | */ 607 | baseRequestPromise: function (url, catchData, type) { 608 | return new Promise(function (resolve, reject) { 609 | fetchRequest(url, catchData).then(function (res) { 610 | resolve(type !== 'script' ? res.json() : res.text()) 611 | }).catch(function (error) { 612 | console.log(error); 613 | reject(error); 614 | }) 615 | }) 616 | }, 617 | baseScriptRequestPromise: function (url, datakey, charset) { 618 | return fetchDataScript(url, datakey, charset) 619 | }, 620 | // 请求英雄, 特质, 职业, 装备, 海克斯5大基础数据 621 | requestBaseData: function () { 622 | return Promise.all([ 623 | this.baseRequestPromise(DataUrlManager['hero_data'][CurrentSet]), 624 | this.baseRequestPromise(DataUrlManager['race_data'][CurrentSet]), 625 | this.baseRequestPromise(DataUrlManager['job_data'][CurrentSet]), 626 | this.baseRequestPromise(DataUrlManager['equipment_data'][CurrentSet]), 627 | this.baseRequestPromise(DataUrlManager['hex_data'][CurrentSet]) 628 | ]) 629 | }, 630 | // 请求阵容数据和作者信息(20220307阵容优化不使用此接口了) 631 | requestLineupData: function () { 632 | var urlParamTest = window.vueRouter.currentRoute.query.test; 633 | if (urlParamTest === '1') { 634 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/t20210517/lineup_detail_total.json'); 635 | } 636 | return this.baseRequestPromise(DataUrlManager['linelist'][CurrentSet]); 637 | }, 638 | // 请求双人模式阵容数据 639 | requestDoubleLineupData: function () { 640 | return this.baseRequestPromise(DataUrlManager['double_linelist'][CurrentSet]); 641 | }, 642 | // 请求所有阵容列表(包含已审核、已上架和已推荐三种状态,20220307阵容优化新增) 643 | requestListedLineupData: function () { 644 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/totalLineupJson/lineup_total.json?v=' + (Date.now() / 600000 >> 0)); 645 | }, 646 | // 获取作者信息 647 | requestAuthorList: function () { 648 | return this.baseRequestPromise(DataUrlManager['authorList']); 649 | }, 650 | // 获取作者详情页阵容列表 651 | requestAuthorDetailLineup: function (authorId) { 652 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupJson/' + window.CurrentSet + '/author/' + authorId + '.json'); 653 | }, 654 | // 获取渠道列表 655 | requestPlatformList: function () { 656 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/platJson/plat.json'); 657 | }, 658 | // 获取小小英雄 659 | requestLittleHero: function () { 660 | return this.baseRequestPromise(DataUrlManager['little_hero_data'][CurrentSet]) 661 | }, 662 | // 按需加载html2canvas组件 663 | requestHtml2canvasJs: function () { 664 | return this.baseScriptRequestPromise('js/lib/html2canvas.min.js', 'html2canvas'); 665 | }, 666 | // 按需加载腾讯视频组件 667 | requestTxplayerJs: function () { 668 | return this.baseScriptRequestPromise('//vm.gtimg.cn/tencentvideo/txp/js/txplayer.js', 'Txplayer'); 669 | }, 670 | // 版本-kv(运营推荐位接口) 671 | requestOperateJson: function () { 672 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/operateJson/operate.json'); 673 | }, 674 | // 最新攻略-类别列表 675 | requestTFTGuideAlbum: function () { 676 | return this.baseScriptRequestPromise('//lol.qq.com/act/AutoCMS/publish/LOLAct/tftGuideAlbum/tftGuideAlbum.js', 'tftGuideAlbum'); 677 | }, 678 | // 最新攻略-类别-'最新' 679 | requestLatestNews: function () { 680 | // return this.baseRequestPromise('//apps.game.qq.com/cmc/cross?serviceId=3&tagids=1934&limit=6&source=glzx&typeids=1,2'); 681 | return this.baseRequestPromise('https://apps.game.qq.com/cmc/cross?serviceId=245&limit=6&source=zm&tagids=78387&typeids=1,2'); 682 | }, 683 | // 获取最新攻略-类别-'最新'之外的类别 684 | requestCollectionContentList: function (collectionId) { 685 | return this.baseRequestPromise('//apps.game.qq.com/cmc/zmMcnCollectionContentList?collectionid=' + collectionId + '&page=1&num=10&source=glzx'); 686 | }, 687 | // 获取游戏当前版本号 688 | requestTFTVersion: function () { 689 | return this.baseScriptRequestPromise('//lol.qq.com/act/AutoCMS/publish/LOLWeb/OfficialWebsite/website_cfg.js', 'OfficialWebsiteCfg'); 690 | }, 691 | // 获取游戏当前版本号链接 692 | requestTFTVersionLink: function () { 693 | return this.baseRequestPromise('https://apps.game.qq.com/cmc/cross?serviceId=3&tagids=1983&limit=1&source=glzx&typeids=1'); 694 | }, 695 | // 获取单个阵容详情 696 | requestTFTLineupByLineId: function (lineId, channelId) { 697 | !channelId && (channel_id = window.TFTChannelId); 698 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupJson/' + window.CurrentSet + '/' + channelId + '/' + lineId + '.json'); 699 | }, 700 | // 获取单个双人阵容详情 701 | requestTFTDoubleLineupByLineId: function (lineId, channelId) { 702 | !channelId && (channel_id = window.TFTChannelId); 703 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/doubleLineupJson/' + window.CurrentSet + '/' + channelId + '/' + lineId + '.json'); 704 | }, 705 | // 获取单个阵容详情(不需要渠道参数) 706 | requestLineupByLineId: function (lineId) { 707 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupJson/total/' + lineId + '.json'); 708 | }, 709 | // 获取玩家账号信息 710 | requestPlayerInfo: function (area) { 711 | return this.baseRequestPromise('//lol.ams.game.qq.com/lol/autocms/v1/transit/LOL/LOLWeb/Official/MobilePlayerInfo,PlayerBattleSummary?use=zm,uid,acc&area=' + area, { credentials: 'include', mode: 'cors' }); 712 | }, 713 | // 判断是否是白名单 714 | requestWhiteAuthorData: function () { 715 | var urlParamTest = window.vueRouter.currentRoute.query.test; 716 | var goUrl = '//lol.sw.game.qq.com/lol/lwdcommact/a20201106tft/a20201106tftLineup/verify'; 717 | urlParamTest === '1' && (goUrl += '?test=1'); 718 | return this.baseRequestPromise(goUrl, { 719 | credentials: 'include' 720 | }); 721 | }, 722 | // 上传阵容 723 | postUploadLineUp: function (data) { 724 | var urlParamTest = window.vueRouter.currentRoute.query.test; 725 | var goUrl = '//lol.sw.game.qq.com/lol/lwdcommact/a20201106tft/a20201106tftLineup/save'; 726 | urlParamTest === '1' && (goUrl += '?test=1'); 727 | // body数据 728 | var formData = new FormData(); 729 | formData.append('lineupInfo', JSON.stringify(data.lineupInfo)); 730 | return this.baseRequestPromise(goUrl, { 731 | credentials: 'include', 732 | method: 'POST', 733 | // headers:{ 734 | // 'Content-Type':'multipart/form-data; charset=utf-8' 735 | // // 'Content-Type':'application/x-www-form-urlencoded; charset=utf-8' 736 | // }, 737 | body: formData 738 | }); 739 | }, 740 | /** 主羁绊排行列表 741 | * @param period 时间周期 1, 7, 30 742 | * @param tier 段位 0: 大师以上 1: 黄金至钻石 255: 全部 743 | * @param raceId 特质id 255: 全部 744 | * @param jobId 职业id 255: 全部 745 | */ 746 | getMainEffectRank: function (period, tier, raceId, jobId) { 747 | var time_type = period || '1'; 748 | var tier_part = tier ? '&tier_part=' + tier : '&tier_part=255'; 749 | var raceId = raceId ? '&raceid=' + raceId : '&raceid=255'; 750 | var jobId = jobId ? '&jobid=' + jobId : '&jobid=255'; 751 | return fetchJsonpScript('//lol.sw.game.qq.com/lol/lwdcommact/a20200629api/A20200629api/mbrank?time_type=' + time_type + tier_part + raceId + jobId + '&callback=TFTBigDataMainEffectRank', 'TFTBigDataMainEffectRank') 752 | }, 753 | /** 英雄排行列表 754 | * @param period 时间周期 1, 7, 30 755 | * @param tier 段位 0: 大师以上 1: 黄金至钻石 255: 全部 756 | * @param raceId 特质id 255: 全部 757 | * @param jobId 职业id 255: 全部 758 | */ 759 | getHeroesRank: function (period, tier, raceId, jobId) { 760 | var time_type = period || '1'; 761 | var tier_part = tier ? '&tier_part=' + tier : '&tier_part=255'; 762 | var raceId = raceId ? '&raceid=' + raceId : '&raceid=255'; 763 | var jobId = jobId ? '&jobid=' + jobId : '&jobid=255'; 764 | return fetchJsonpScript('//lol.sw.game.qq.com/lol/lwdcommact/a20200629api/A20200629api/herorank?time_type=' + time_type + tier_part + raceId + jobId + '&callback=TFTBigDataHeroesRank', 'TFTBigDataHeroesRank') 765 | }, 766 | /** 子羁绊组合详情列表 767 | * @param period 时间周期 1, 7, 30 768 | * @param effects 羁绊信息 结构: id,等级;id,等级... 769 | */ 770 | getEffectDetailRank: function (period, effects) { 771 | var time_type = period || '1'; 772 | return fetchJsonpScript('//lol.sw.game.qq.com/lol/lwdcommact/a20200629api/A20200629api/sbc?time_type=' + time_type + '&main_traits_id=' + effects + '&tier_part=255&callback=TFTBigDataEffectDetailRank', 'TFTBigDataEffectDetailRank') 773 | }, 774 | /** 主羁绊克制列表 775 | * @param period 时间周期 1, 7, 30 776 | * @param effects 羁绊信息 结构: id,等级;id,等级... 777 | */ 778 | getMainEffectCounterRank: function (period, effects) { 779 | var time_type = period || '1'; 780 | return fetchJsonpScript('//lol.sw.game.qq.com/lol/lwdcommact/a20200629api/A20200629api/mbrl?time_type=' + time_type + '&main_traits_id=' + effects + '&tier_part=255&callback=TFTBigDataMainEffectCounterRank', 'TFTBigDataMainEffectCounterRank') 781 | }, 782 | /** 子羁绊克制列表 783 | * @param period 时间周期 1, 7, 30 784 | * @param main_effects 羁绊信息 结构: id,等级;id,等级... 785 | * @param sub_effects 羁绊信息 结构: id,等级|id,等级... 786 | * @param hero_ids 英雄id列表 结构: id,id... 787 | */ 788 | getSubEffectCounterRank: function (period, main_effects, sub_effects, hero_ids) { 789 | var time_type = period || '1'; 790 | return fetchJsonpScript('//lol.sw.game.qq.com/lol/lwdcommact/a20200629api/A20200629api/sbrl?time_type=' + time_type + '&main_traits_id=' + main_effects + '&minor_traits_id=' + sub_effects + '&minor_champion_content_id=' + hero_ids + '&tier_part=255&callback=TFTBigDataSubEffectCounterRank', 'TFTBigDataSubEffectCounterRank') 791 | }, 792 | // 装备排行 793 | getEquipRank: function (period, tier) { 794 | var time_type = period || '1'; 795 | var tier_part = tier ? '&tier_part=' + tier : '&tier_part=255'; 796 | return Promise.all([ 797 | // this.baseRequestPromise('//lol.qq.com/act/a20200224tft/staticJS-vue3/TFTEquipMap.json'), 798 | this.baseRequestPromise('//lol.qq.com/act/a20200224tft/staticJS-vue3/TFTEquipMap.json?v=' + (Date.now() / 600000 >> 0)), 799 | fetchJsonpScript('//lol.sw.game.qq.com/lol/lwdcommact/a20210420api/a20210420api/equiprank?callback=TFTBigDataEquipRank&time_type=' + time_type + tier_part, 'TFTBigDataEquipRank') 800 | ]) 801 | }, 802 | // 获取大区列表 803 | getLOLArea: function () { 804 | return this.baseScriptRequestPromise('//lol.qq.com/comm-htdocs/js/game_area/lol_server_select.js', 'LOLServerSelect', 'gbk'); 805 | }, 806 | // 获取大区段位排行 807 | getAreaTierRank: function (area_id, offset) { 808 | // 获取sign参数 809 | var params = "area_id=" + area_id + "&offset=" + offset; 810 | var key = "qtld^xibt#a*"; 811 | var sign = hex_md5(params + key); 812 | // body数据 813 | var withdata = { 814 | next_offset: "", 815 | player_list: [] 816 | }; 817 | return this.baseRequestPromise('//qt.qq.com/lua/mlol_battle_info/get_total_tier_rank_list?area_id=' + area_id + '&offset=' + offset + '&sign=' + sign, { 818 | credentials: 'include', 819 | method: 'POST', 820 | body: JSON.stringify(withdata) 821 | }); 822 | }, 823 | // 获取阵容标签列表 824 | getLineupTagList: function () { 825 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/tagJson/tag.json?v=' + (Date.now() / 600000 >> 0)); 826 | }, 827 | // 获取阵容特性列表 828 | getLineupFeatureList: function () { 829 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/specialityJson/speciality.json?v=' + (Date.now() / 600000 >> 0)); 830 | }, 831 | // 获取阵容模式列表 832 | getLineupTypeList: function () { 833 | return this.baseRequestPromise('//game.gtimg.cn/images/lol/act/tftzlkauto/json/lineupTypeJson/lineupType.json?v=' + (Date.now() / 600000 >> 0)); 834 | }, 835 | // 获取羁绊数据 836 | getBuffData: function() { 837 | if(window.ApiManager.getBuffData.cache) return window.ApiManager.getBuffData.cache; 838 | return window.ApiManager.getBuffData.cache = fetchRequest('//game.gtimg.cn/images/lol/act/20190722tftxcx/data/buffData.json?v=' + (Date.now() / 600000 >> 0)).then(function(res) { 839 | return res.json(); 840 | }).catch(function(err) { 841 | window.ApiManager.getBuffData.cache = null; 842 | console.error(err); 843 | return Promise.reject(err); 844 | }); 845 | }, 846 | // 获取棋子数据 847 | getChampionData: function() { 848 | if(window.ApiManager.getChampionData.cache) return window.ApiManager.getChampionData.cache; 849 | return window.ApiManager.getChampionData.cache = fetchRequest('//game.gtimg.cn/images/lol/act/20190722tftxcx/data/heroid_chaid_pieceid.json?v=' + (Date.now() / 600000 >> 0)).then(function(res) { 850 | return res.json(); 851 | }).catch(function(err) { 852 | window.ApiManager.getChampionData.cache = null; 853 | console.error(err); 854 | return Promise.reject(err); 855 | }); 856 | }, 857 | // 获取装备映射 858 | getEquipMap: function() { 859 | if(window.ApiManager.getEquipMap.cache) return window.ApiManager.getEquipMap.cache; 860 | return window.ApiManager.getEquipMap.cache = fetchRequest('//lol.qq.com/act/a20200224tft/staticJS-vue3/TFTEquipMap.json?v=' + (Date.now() / 600000 >> 0)).then(function(res) { 861 | return res.json(); 862 | }).catch(function(err) { 863 | window.ApiManager.getEquipMap.cache = null; 864 | console.error(err); 865 | return Promise.reject(err); 866 | }); 867 | }, 868 | // 获取小小英雄信息 869 | getLittleHeroInfo: function () { 870 | if(window.ApiManager.getLittleHeroInfo.cache) return window.ApiManager.getLittleHeroInfo.cache; 871 | return window.ApiManager.getLittleHeroInfo.cache = fetchRequest('https://mlol.qt.qq.com/go/exploit/get_tiny_hero_list?v=' + (Date.now() / 600000 >> 0)).then(function (res) { 872 | return res.json(); 873 | }).catch(function (err) { 874 | window.ApiManager.getLittleHeroInfo.cache = null; 875 | console.error(err); 876 | return Promise.reject(err); 877 | }); 878 | }, 879 | // 获取大神羁绊棋子数据 880 | getMasterCom: function(date, callbackName, masterId, area) { 881 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20210906api/a20210906api/mastercom?callback=' + callbackName + '&date=' + date + '&puuid=' + masterId + '&area=' + area + '&v=' + (Date.now() / 600000 >> 0); 882 | }, 883 | // 获取大神羁绊使用数据 884 | getMasterRaceJob: function(date, callbackName, masterId, area) { 885 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20210906api/a20210906api/racejob?callback=' + callbackName + '&date=' + date + '&puuid=' + masterId + '&area=' + area + '&v=' + (Date.now() / 600000 >> 0); 886 | }, 887 | // 获取大神棋子使用数据 888 | getMasterHero: function(date, callbackName, masterId, area) { 889 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20210906api/a20210906api/hero?callback=' + callbackName + '&date=' + date + '&puuid=' + masterId + '&area=' + area + '&v=' + (Date.now() / 600000 >> 0); 890 | }, 891 | // 获取大神装备使用数据 892 | getMasterEquip: function(date, callbackName, masterId, area) { 893 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20210906api/a20210906api/equip?callback=' + callbackName + '&date=' + date + '&puuid=' + masterId + '&area=' + area + '&v=' + (Date.now() / 600000 >> 0); 894 | }, 895 | // 获取大神生涯数据 896 | getMasterCareer: function(date, callbackName, masterId, area) { 897 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20210906api/a20210906api/master?callback=' + callbackName + '&date=' + date + '&puuid=' + masterId + '&area=' + area + '&v=' + (Date.now() / 600000 >> 0); 898 | }, 899 | // TFT段位排行榜 900 | getTFTTierRanking: function(date, callbackName, worldid) { 901 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20211021tftSet6/a20211021api/ranking?callback=' + callbackName + '&dtstatdate=' + date + '&worldid=' + worldid + '&v=' + (Date.now() / 600000 >> 0); 902 | }, 903 | // TFT大神信息 904 | getMasterInfo: function(date, callbackName, puuid, worldid) { 905 | return '//lol.sw.game.qq.com/lol/lwdcommact/a20211021tftSet6/a20211021api/info?callback=' + callbackName + '&dtstatdate=' + date + '&worldid=' + worldid + '&puuid=' + puuid + '&v=' + (Date.now() / 600000 >> 0); 906 | }, 907 | // TFT大神战绩列表 908 | getMasterFightList: function(puuid, areaid, filter, start, limit) { 909 | if(!puuid || !areaid) return Promise.reject('参数为空'); 910 | filter = filter || 'all'; 911 | start = start || 0; 912 | limit = limit || 10; 913 | 914 | var link = '//lol.sw.game.qq.com/lol/lwdcommact/a20211021tftSet6/a20211021api/fightlist?puuid=' + puuid + '&areaid=' + areaid + '&filter=' + filter + '&start=' + start + '&limit=' + limit + '&v=' + (Date.now() / 600000 >> 0); 915 | return fetchVariableDataScript(link, 'LWDFramework_Swoole').then(function (res) { 916 | if(_.get(res, 'code') !== 0) return Promise.reject(_.get(res, 'msg') + ' code:' + _.get(res, 'code')); 917 | var rData = _.get(res, 'data.result'); 918 | if(!rData) return Promise.reject('暂无数据'); 919 | rData.masterId = puuid; 920 | rData.area = areaid; 921 | return rData; 922 | }).catch(function (err) { 923 | console.error(err); 924 | return Promise.reject(err); 925 | }); 926 | }, 927 | // TFT大神战绩详情 928 | getMasterFightDetail: function(gameid, areaid) { 929 | if(!gameid || !areaid) return Promise.reject('参数为空'); 930 | var link = '//lol.sw.game.qq.com/lol/lwdcommact/a20211021tftSet6/a20211021api/fightdetail?areaid=' + areaid + '&gameid=' + gameid + '&v=' + (Date.now() / 600000 >> 0); 931 | return fetchVariableDataScript(link, 'LWDFramework_Swoole').then(function (res) { 932 | if(_.get(res, 'code') !== 0) return Promise.reject(_.get(res, 'msg') + ' code:' + _.get(res, 'code')); 933 | var rData = _.get(res, 'data.result'); 934 | if(!rData) return Promise.reject('暂无数据'); 935 | rData.gameid = gameid; 936 | rData.area = areaid; 937 | return rData; 938 | }).catch(function (err) { 939 | console.error(err); 940 | return Promise.reject(err); 941 | }); 942 | }, 943 | /** 944 | * 游戏说(gicp)交叉搜索 945 | * @param source 请求来源 946 | * @param tagids 标签id eg:121986,120921 947 | * @param typeids 类型id,1:图文; 2:视频; 1,2:图文+视频 948 | * @param start 起始位置 949 | * @param limit 搜索条目数 950 | * @param logic 多个标签拉取关系 or:并集(包含其中之一); and:交集(同时包含) 951 | * @returns {Promise|Promise} 952 | */ 953 | requestCMCCross: function (source, tagids, typeids, start, limit, logic) { 954 | if (!source) return Promise.reject('需要参数source'); 955 | if (!tagids) return Promise.reject('需要参数tagids'); 956 | !typeids && (typeids = '1,2'); 957 | !logic && (logic = 'and'); 958 | return this.baseRequestPromise('//apps.game.qq.com/cmc/cross?serviceId=245&source=' + source + '&tagids=' + tagids + '&typeids=' + typeids + '&logic=' + logic + '&start=' + start + '&limit=' + limit); 959 | }, 960 | // getAreaTierRank: function (area_id, offset) { 961 | // return this.baseRequestPromise('https://faas-4880.odp.qq.com/faas/4880/995/tft_rank_list', { 962 | // mode: 'cors', 963 | // credentials: 'include', 964 | // method: 'POST', 965 | // headers: { 966 | // 'Content-Type': 'application/x-www-form-urlencoded' 967 | // }, 968 | // // body: JSON.stringify(withdata) 969 | // body: 'areaid=' + area_id + '&page=' + offset 970 | // }); 971 | // } 972 | }/* #t6Hl8#CD9D8BAB292D36880E21BAC9910C9AC9 */ --------------------------------------------------------------------------------