├── .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 */
--------------------------------------------------------------------------------