mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ed1c20feb | |||
| e5abc99590 | |||
| d6449ddf8c | |||
| dcbab47833 | |||
|
|
e65c50e107 | ||
| 4525b84fc4 | |||
| 6207cdb3d9 | |||
| f1291a9923 | |||
|
|
0d2b37e78a | ||
|
|
a8998b01a7 | ||
| df6cb71e3f | |||
| 2a19b9fe4c | |||
| a77e190cf7 | |||
| c9e7923243 | |||
| 1569e71da9 | |||
| da4c3c01a6 | |||
| 9786a1325b |
72
.github/workflows/codeql-analysis.yml
vendored
Normal file
72
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '17 6 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
dist
|
||||
test*
|
||||
Untitled*
|
||||
*copy*
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 scdhh
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
10
README.md
10
README.md
@@ -12,10 +12,17 @@ TETRIS Stats
|
||||
安装
|
||||
----
|
||||
|
||||
* 使用 pip
|
||||
* 使用 nb-cli(推荐)
|
||||
|
||||
```
|
||||
nb plugin install nonebot-plugin-tetris-stats
|
||||
```
|
||||
|
||||
* 使用 pip(不推荐)
|
||||
|
||||
```
|
||||
pip install nonebot-plugin-tetris-stats
|
||||
# 修改bot.py
|
||||
```
|
||||
|
||||
使用
|
||||
@@ -33,6 +40,7 @@ pip install nonebot-plugin-tetris-stats
|
||||
|
||||
* [NoneBot2](https://v2.nonebot.dev/)
|
||||
* [OneBot](https://onebot.dev/)
|
||||
* [go-cqhttp](https://github.com/Mrs4s/go-cqhttp/)
|
||||
|
||||
开源
|
||||
----
|
||||
|
||||
211
nonebot_plugin_tetris_stats/GameDataProcessor/IODataProcessor.py
Normal file
211
nonebot_plugin_tetris_stats/GameDataProcessor/IODataProcessor.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from nonebot import on_regex
|
||||
from nonebot.adapters.onebot.v11 import GROUP, MessageEvent
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.log import logger
|
||||
|
||||
from typing import Any, Mapping
|
||||
from asyncio import gather
|
||||
from re import I
|
||||
|
||||
from ..Utils.Request import request
|
||||
|
||||
from ..Utils.MessageAnalyzer import handleBindMessage, handleStatsQueryMessage
|
||||
from ..Utils.SQL import queryBindInfo, writeBindInfo
|
||||
|
||||
ioBind = on_regex(pattern=r'^io绑定|^iobind', flags=I, permission=GROUP)
|
||||
ioStats = on_regex(pattern=r'^io查|^iostats', flags=I, permission=GROUP)
|
||||
|
||||
|
||||
@ioBind.handle()
|
||||
async def _(event: MessageEvent, matcher: Matcher):
|
||||
decodedMessage = await handleBindMessage(message=event.raw_message, gameType='IO')
|
||||
if decodedMessage[0] is None:
|
||||
await matcher.finish(decodedMessage[1][0])
|
||||
if decodedMessage[0] == 'ID':
|
||||
userIDStats = await checkUserID(userID=decodedMessage[1][1])
|
||||
if userIDStats[0] is False:
|
||||
await matcher.finish(userIDStats[1])
|
||||
else:
|
||||
userID = decodedMessage[1][1]
|
||||
elif decodedMessage[0] == 'Name':
|
||||
userData = await getUserData(userName=decodedMessage[1][1])
|
||||
if userData[0] is False:
|
||||
await matcher.finish('用户信息请求失败')
|
||||
elif userData[1] is False:
|
||||
await matcher.finish(f'用户信息请求错误:\n{userData[2]["error"]}')
|
||||
else:
|
||||
userID = await getUserID(userData=userData[2])
|
||||
if event.sender.user_id is None: # 理论上是不会有None出现的,ide快乐行属于是(
|
||||
logger.error('获取QQ号失败')
|
||||
await matcher.finish('获取QQ号失败')
|
||||
await matcher.finish(await writeBindInfo(QQNumber=event.sender.user_id, user=userID, gameType='IO'))
|
||||
|
||||
|
||||
@ioStats.handle()
|
||||
async def _(event: MessageEvent, matcher: Matcher):
|
||||
decodedMessage = await handleStatsQueryMessage(message=event.raw_message, gameType='IO')
|
||||
if decodedMessage[0] is None:
|
||||
await matcher.finish(decodedMessage[1][0])
|
||||
elif decodedMessage[0] == 'AT':
|
||||
if event.is_tome() is True:
|
||||
await matcher.finish(message='不能查询bot的信息')
|
||||
bindInfo = await queryBindInfo(QQNumber=decodedMessage[1][1], gameType='IO')
|
||||
if bindInfo is None:
|
||||
message = '未查询到绑定信息'
|
||||
else:
|
||||
message = (f'* 由于无法验证绑定信息,不能保证查询到的用户为本人\n{await generateMessage(userID=bindInfo)}')
|
||||
elif decodedMessage[0] == 'ME':
|
||||
if event.sender.user_id is None:
|
||||
logger.error('获取QQ号失败')
|
||||
await matcher.finish('获取QQ号失败,请联系bot主人')
|
||||
bindInfo = await queryBindInfo(QQNumber=event.sender.user_id, gameType='IO')
|
||||
if bindInfo is None:
|
||||
message = '未查询到绑定信息'
|
||||
else:
|
||||
message = (f'* 由于无法验证绑定信息,不能保证查询到的用户为本人\n{await generateMessage(userID=bindInfo)}')
|
||||
elif decodedMessage[0] == 'ID':
|
||||
message = await generateMessage(userID=decodedMessage[1][1])
|
||||
elif decodedMessage[0] == 'Name':
|
||||
message = await generateMessage(userName=decodedMessage[1][1])
|
||||
await matcher.finish(message=message)
|
||||
|
||||
|
||||
async def getUserData(userName: str = None, userID: str = None) -> tuple[bool, bool, dict[str, Any]]:
|
||||
# 获取用户数据
|
||||
if userName is not None and userID is None:
|
||||
userDataUrl = f'https://ch.tetr.io/api/users/{userName}'
|
||||
elif userName is None and userID is not None:
|
||||
userDataUrl = f'https://ch.tetr.io/api/users/{userID}'
|
||||
else:
|
||||
raise ValueError(
|
||||
'[TETRIS STATS] IODataProcessing.getUserData: 预期外行为,请上报GitHub')
|
||||
return await request(Url=userDataUrl)
|
||||
|
||||
|
||||
async def getSoloData(userName: str = None, userID: str = None) -> tuple[bool, bool, dict[str, Any]]:
|
||||
# 获取Solo数据
|
||||
if userName is not None and userID is None:
|
||||
userSoloUrl = f'https://ch.tetr.io/api/users/{userName}/records'
|
||||
elif userName is None and userID is not None:
|
||||
userSoloUrl = f'https://ch.tetr.io/api/users/{userID}/records'
|
||||
else:
|
||||
raise ValueError(
|
||||
'[TETRIS STATS] IODataProcessing.getSoloData: 预期外行为,请上报GitHub')
|
||||
return await request(Url=userSoloUrl)
|
||||
|
||||
|
||||
async def getUserID(userData: dict) -> str:
|
||||
return userData['data']['user']['_id']
|
||||
|
||||
|
||||
async def checkUserID(userID: str) -> tuple[bool, str]:
|
||||
userData = await getUserData(userID=userID)
|
||||
if userData[0] is False:
|
||||
return (False, '用户信息请求失败')
|
||||
elif userData[1] is False:
|
||||
return (False, f'用户信息请求错误:\n{userData[2]["error"]}')
|
||||
elif userID == userData[2]['data']['user']['_id']:
|
||||
return (True, '')
|
||||
else:
|
||||
raise ValueError(
|
||||
'[TETRIS STATS] IODataProcessing.checkUserID: 服务器返回的userID和用户提供的不一致,这种情况理论上不应该发生,以防万一还是写一下(x')
|
||||
|
||||
|
||||
async def getLeagueStats(userData: dict) -> dict[str, Any]:
|
||||
# 获取排位统计数据
|
||||
league = userData['data']['user']['league']
|
||||
leagueStats: dict[str, Any] = {}
|
||||
if league['gamesplayed'] == 0:
|
||||
leagueStats['Played'] = False
|
||||
else:
|
||||
leagueStats['Played'] = True
|
||||
leagueStats['PPS'] = league['pps']
|
||||
leagueStats['APM'] = league['apm']
|
||||
leagueStats['VS'] = 0 if league['vs'] is None else league['vs']
|
||||
leagueStats['Rank'] = False if league['rank'] == 'z' else league['rank'].upper()
|
||||
if league['rating'] != -1:
|
||||
leagueStats['Ranked'] = True
|
||||
leagueStats['Rating'] = round(league['rating'], 2)
|
||||
leagueStats['Glicko'] = round(league['glicko'], 2)
|
||||
leagueStats['RD'] = round(league['rd'], 2)
|
||||
else:
|
||||
leagueStats['Ranked'] = False
|
||||
leagueStats['Standing'] = league['standing']
|
||||
leagueStats['LPM'] = round((league['pps'] * 24), 2)
|
||||
leagueStats['APL'] = round(
|
||||
(leagueStats['APM'] / leagueStats['LPM']), 2)
|
||||
leagueStats['ADPM'] = round((leagueStats['VS'] * 0.6), 2)
|
||||
leagueStats['ADPL'] = round(
|
||||
(leagueStats['ADPM'] / leagueStats['LPM']), 2)
|
||||
return leagueStats
|
||||
|
||||
|
||||
async def getSprintStats(soloData: dict) -> Mapping[str, bool | int | float]:
|
||||
# 获取40L统计数据
|
||||
sprintStats = {}
|
||||
if soloData['data']['records']['40l']['record'] is None:
|
||||
sprintStats['Played'] = False
|
||||
else:
|
||||
sprintStats['Played'] = True
|
||||
if soloData['data']['records']['40l']['rank'] is None:
|
||||
sprintStats['Rank'] = False
|
||||
else:
|
||||
sprintStats['Rank'] = soloData['data']['records']['40l']['rank']
|
||||
sprintStats['Time'] = round(
|
||||
soloData['data']['records']['40l']['record']['endcontext']['finalTime'] / 1000, 2)
|
||||
return sprintStats
|
||||
|
||||
|
||||
async def getBlitzStats(soloData: dict) -> dict[str, Any]:
|
||||
# 获取Blitz统计数据
|
||||
blitzStats = {}
|
||||
if soloData['data']['records']['blitz']['record'] is None:
|
||||
blitzStats['Played'] = False
|
||||
else:
|
||||
blitzStats['Played'] = True
|
||||
if soloData['data']['records']['blitz']['rank'] is None:
|
||||
blitzStats['Rank'] = False
|
||||
else:
|
||||
blitzStats['Rank'] = soloData['data']['records']['blitz']['rank']
|
||||
blitzStats['Score'] = soloData['data']['records']['blitz']['record']['endcontext']['score']
|
||||
return blitzStats
|
||||
|
||||
|
||||
async def generateMessage(userName: str = None, userID: str = None) -> str:
|
||||
# 生成消息
|
||||
userData, soloData = await gather(getUserData(userName=userName, userID=userID), getSoloData(userName=userName, userID=userID))
|
||||
if userData[0] is False:
|
||||
return '用户信息请求失败'
|
||||
elif userData[1] is False:
|
||||
return f'用户信息请求错误:\n{userData[2]["error"]}'
|
||||
userName = userData[2]['data']['user']['username'].upper()
|
||||
leagueStats = await getLeagueStats(userData[2])
|
||||
message = ''
|
||||
if leagueStats['Played'] is False:
|
||||
message += f'用户 {userName} 没有排位统计数据'
|
||||
else:
|
||||
if leagueStats['Rank'] is False and leagueStats['Ranked'] is False:
|
||||
message += f'用户 {userName} 暂未完成定级赛'
|
||||
elif leagueStats['Rank'] is False and leagueStats['Ranked'] is True:
|
||||
message += f'用户 {userName} 暂无段位, {leagueStats["Rating"]} TR'
|
||||
else:
|
||||
message += f'{leagueStats["Rank"]} 段用户 {userName} {leagueStats["Rating"]} TR (#{leagueStats["Standing"]})'
|
||||
message += f', 段位分 {leagueStats["Glicko"]}±{leagueStats["RD"]}, 最近十场的数据:' if leagueStats['Ranked'] is True else ', 最近十场的数据:'
|
||||
message += f'\nL\'PM: {leagueStats["LPM"]} ( {leagueStats["PPS"]} pps )'
|
||||
message += f'\nAPM: {leagueStats["APM"]} ( x{leagueStats["APL"]} )'
|
||||
if leagueStats["VS"] != 0:
|
||||
message += f'\nADPM: {leagueStats["ADPM"]} ( x{leagueStats["ADPL"]} ) ( {leagueStats["VS"]}vs )'
|
||||
if soloData[0] is False:
|
||||
return f'{message}\nSolo统计数据请求失败'
|
||||
elif soloData[1] is False:
|
||||
return f'{message}\nSolo统计数据请求错误:\n{soloData[2]["error"]}'
|
||||
sprintStats, blitzStats = await gather(getSprintStats(soloData[2]), getBlitzStats(soloData[2]))
|
||||
if sprintStats['Played'] is True:
|
||||
message += f'\n40L: {sprintStats["Time"]}s'
|
||||
if sprintStats['Rank'] is not False:
|
||||
message += f' ( #{sprintStats["Rank"]} )'
|
||||
if blitzStats['Played'] is True:
|
||||
message += f'\nBlitz: {blitzStats["Score"]}'
|
||||
if blitzStats['Rank'] is not False:
|
||||
message += f' ( #{blitzStats["Rank"]} )'
|
||||
return message
|
||||
@@ -0,0 +1,164 @@
|
||||
from nonebot import on_regex
|
||||
from nonebot.adapters.onebot.v11 import GROUP, MessageEvent
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
from typing import Any
|
||||
from asyncio import gather
|
||||
from re import I
|
||||
|
||||
from ..Utils.Request import request
|
||||
|
||||
from ..Utils.MessageAnalyzer import handleStatsQueryMessage
|
||||
|
||||
tosStats = on_regex(pattern=r'^tos查|^tostats|^tosstats|^茶服查|^茶服stats',
|
||||
flags=I, permission=GROUP)
|
||||
|
||||
|
||||
@tosStats.handle()
|
||||
async def _(event: MessageEvent, matcher: Matcher):
|
||||
decodedMessage = await handleStatsQueryMessage(message=event.raw_message, gameType='TOS')
|
||||
if decodedMessage[0] is None:
|
||||
await matcher.finish(decodedMessage[1][0])
|
||||
elif decodedMessage[0] == 'AT' or decodedMessage[0] == 'QQ':
|
||||
if decodedMessage[1][1] == event.self_id:
|
||||
await matcher.finish(message='不能查询bot的信息')
|
||||
message = await generateMessage(teaID=decodedMessage[1][1])
|
||||
elif decodedMessage[0] == 'ME':
|
||||
message = await generateMessage(teaID=event.sender.user_id)
|
||||
elif decodedMessage[0] == 'Name':
|
||||
message = await generateMessage(userName=decodedMessage[1][1])
|
||||
await matcher.finish(message=message)
|
||||
|
||||
|
||||
async def getUserInfo(userName: str = None, teaID: int = None) -> tuple[bool, bool, dict[str, Any]]:
|
||||
# 获取用户信息
|
||||
if userName is not None and teaID is None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getUsernameInfo?username={userName}'
|
||||
elif userName is None and teaID is not None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getTeaIdInfo?teaId={teaID}'
|
||||
else:
|
||||
raise ValueError(
|
||||
'[TETRIS STATS] TOSDataProcessing.getUserInfo: 预期外行为,请上报GitHub')
|
||||
return await request(Url=userDataUrl)
|
||||
|
||||
|
||||
async def getUserData(userName: str = None, teaID: int = None, otherParameter: str = '') -> tuple[bool, bool, dict[str, Any]]:
|
||||
# 获取用户数据
|
||||
if userName is not None and teaID is None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getProfile?id={userName}{otherParameter}'
|
||||
elif userName is None and teaID is not None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getProfile?id={teaID}{otherParameter}'
|
||||
else:
|
||||
raise ValueError(
|
||||
'[TETRIS STATS] TOSDataProcessing.getUserData: 预期外行为,请上报GitHub')
|
||||
return await request(Url=userDataUrl)
|
||||
|
||||
|
||||
async def getRankStats(userInfo: dict) -> dict[str, bool | float]:
|
||||
# 获取Rank数据
|
||||
rankStats: dict[str, bool | float] = {}
|
||||
if int(userInfo['data']['rankedGames']) == 0:
|
||||
rankStats['Played'] = False
|
||||
else:
|
||||
rankStats['Played'] = True
|
||||
rankStats['Rating'] = round(
|
||||
float(userInfo['data']['ratingNow']), 2)
|
||||
rankStats['RD'] = round(float(userInfo['data']['rdNow']), 2)
|
||||
rankStats['Vol'] = round(float(userInfo['data']['volNow']), 3)
|
||||
return rankStats
|
||||
|
||||
|
||||
async def getGameData(userData: dict) -> dict[str, bool | int | float]:
|
||||
# 获取游戏数据
|
||||
gameData: dict[str, bool | int | float] = {}
|
||||
if userData['data'] == []:
|
||||
gameData['Played'] = False
|
||||
else:
|
||||
gameData['Played'] = True
|
||||
weightedTotalLpm = weightedTotalApm = weightedTotalAdpm = weightedTotalTime = num = 0
|
||||
for i in userData['data']:
|
||||
# 排除单人局和时间为0的游戏
|
||||
if i['num_players'] == 1 or i['time'] == 0:
|
||||
continue
|
||||
# 茶:不计算没挖掘的局,即使apm和lpm也如此
|
||||
if i['dig'] is None:
|
||||
continue
|
||||
# 加权计算
|
||||
time = i['time'] / 1000
|
||||
lpm = 24 * (i['pieces'] / time)
|
||||
apm = (i['attack'] / time) * 60
|
||||
adpm = ((i['attack'] + i['dig']) / time) * 60
|
||||
weightedTotalLpm += lpm * time
|
||||
weightedTotalApm += apm * time
|
||||
weightedTotalAdpm += adpm * time
|
||||
weightedTotalTime += time
|
||||
num += 1
|
||||
if num == 50:
|
||||
break
|
||||
if num > 0:
|
||||
gameData['NUM'] = num
|
||||
gameData['LPM'] = round((weightedTotalLpm / weightedTotalTime), 2)
|
||||
gameData['APM'] = round((weightedTotalApm / weightedTotalTime), 2)
|
||||
gameData['ADPM'] = round((weightedTotalAdpm / weightedTotalTime), 2)
|
||||
gameData['PPS'] = round((gameData['LPM'] / 24), 2)
|
||||
gameData['APL'] = round((gameData['APM'] / gameData['LPM']), 2)
|
||||
gameData['ADPL'] = round((gameData['ADPM'] / gameData['LPM']), 2)
|
||||
gameData['VS'] = round((gameData['ADPM'] / 60 * 100), 2)
|
||||
else:
|
||||
gameData['Played'] = False
|
||||
# TODO: 如果有效局数不满50, 没有无dig信息的局, 且userData['data']内有50个局, 则继续往前获取信息
|
||||
return gameData
|
||||
|
||||
|
||||
async def getPBData(userInfo: dict) -> dict[str, bool | float | str]:
|
||||
# 获取PB数据
|
||||
PBData: dict[str, bool | float | str] = {}
|
||||
if int(userInfo['data']['PBSprint']) == 2147483647:
|
||||
PBData['Sprint'] = False
|
||||
else:
|
||||
PBData['Sprint'] = round(
|
||||
float(userInfo['data']['PBSprint']) / 1000, 2)
|
||||
if int(userInfo['data']['PBMarathon']) == 0:
|
||||
PBData['Marathon'] = False
|
||||
else:
|
||||
PBData['Marathon'] = userInfo['data']['PBMarathon']
|
||||
if int(userInfo['data']['PBChallenge']) == 0:
|
||||
PBData['Challenge'] = False
|
||||
else:
|
||||
PBData['Challenge'] = userInfo['data']['PBChallenge']
|
||||
return PBData
|
||||
|
||||
|
||||
async def generateMessage(userName: str = None, teaID: int = None) -> str:
|
||||
# 生成消息
|
||||
userInfo, userData = await gather(getUserInfo(userName=userName, teaID=teaID), getUserData(userName=userName, teaID=teaID))
|
||||
if userInfo[0] is False:
|
||||
return f'用户信息请求失败'
|
||||
elif userInfo[1] is False:
|
||||
return f'用户信息请求错误:\n{userInfo[2]["error"]}'
|
||||
rankStats, PBData = await gather(getRankStats(userInfo[2]), getPBData(userInfo[2]))
|
||||
message = ''
|
||||
if rankStats['Played'] is False:
|
||||
message += f'用户 {userInfo[2]["data"]["name"]}({userInfo[2]["data"]["teaId"]})暂无段位统计数据'
|
||||
elif rankStats['Played'] is True:
|
||||
message += f'用户 {userInfo[2]["data"]["name"]} ({userInfo[2]["data"]["teaId"]}) , 段位分 {rankStats["Rating"]}±{rankStats["RD"]} ({rankStats["Vol"]}) '
|
||||
if userData[0] is False:
|
||||
message = f'{message.rstrip()}\n游戏数据请求失败'
|
||||
elif userData[1] is False:
|
||||
message = f'{message.rstrip()}\n游戏数据请求错误:\n{userData[2]["error"]}'
|
||||
else:
|
||||
gameData = await getGameData(userData[2])
|
||||
if gameData['Played'] is False:
|
||||
message += ', 暂无游戏数据'
|
||||
elif gameData['Played'] is True:
|
||||
message += f', 最近 {gameData["NUM"]} 局数据'
|
||||
message += f'\nL\'PM: {gameData["LPM"]} ( {gameData["PPS"]} pps )'
|
||||
message += f'\nAPM:{gameData["APM"]} ( x{gameData["APL"]} )'
|
||||
message += f'\nADPM:{gameData["ADPM"]} ( x{gameData["ADPL"]} ) ( {gameData["VS"]}vs )'
|
||||
if PBData['Sprint'] is not False:
|
||||
message += f'\n40L: {PBData["Sprint"]}s'
|
||||
if PBData['Marathon'] is not False:
|
||||
message += f'\nMarathon: {PBData["Marathon"]}'
|
||||
if PBData['Challenge'] is not False:
|
||||
message += f'\nChallenge: {PBData["Challenge"]}'
|
||||
return message
|
||||
@@ -0,0 +1 @@
|
||||
from . import IODataProcessor, TOSDataProcessor
|
||||
@@ -1,137 +0,0 @@
|
||||
from nonebot.log import logger
|
||||
|
||||
from asyncio import gather
|
||||
import aiohttp
|
||||
|
||||
# 封装请求函数
|
||||
async def request(Url: str) -> dict[str, bool|dict[str, any]]:
|
||||
data = {}
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(Url) as resp:
|
||||
data['Status'] = True
|
||||
data['Data'] = await resp.json()
|
||||
data['Success'] = data['Data']['success']
|
||||
except aiohttp.client_exceptions.ClientConnectorError as e:
|
||||
logger.error(f'[TETRIS STATS] IODataProcessing.request: 请求错误\n{e}')
|
||||
data['Status'] = False
|
||||
finally:
|
||||
return data
|
||||
|
||||
# 获取用户数据
|
||||
async def getUserData(userName: str = None, userID: str = None) -> dict[str, dict[str, any]]:
|
||||
if userName is not None and userID is None:
|
||||
userDataUrl = f'https://ch.tetr.io/api/users/{userName}'
|
||||
userData = await request(Url=userDataUrl)
|
||||
elif userName is None and userID is not None:
|
||||
userDataUrl = f'https://ch.tetr.io/api/users/{userID}'
|
||||
userData = await request(Url=userDataUrl)
|
||||
else:
|
||||
raise ValueError('[TETRIS STATS] IODataProcessing.getUserData: 参数错误')
|
||||
return userData
|
||||
|
||||
# 获取Solo数据
|
||||
async def getSoloData(userName: str = None, userID: str = None) -> dict[str, dict[str, any]]:
|
||||
if userName is not None and userID is None:
|
||||
userSoloUrl = f'https://ch.tetr.io/api/users/{userName}/records'
|
||||
soloData = await request(Url = userSoloUrl)
|
||||
elif userName is None and userID is not None:
|
||||
userSoloUrl = f'https://ch.tetr.io/api/users/{userID}/records'
|
||||
soloData = await request(Url = userSoloUrl)
|
||||
else:
|
||||
raise ValueError('[TETRIS STATS] IODataProcessing.getSoloData: 参数错误')
|
||||
return soloData
|
||||
|
||||
# 获取用户ID
|
||||
async def getUserID(userData: dict = None, userName: str = None) -> str:
|
||||
if userName is not None and userData is None:
|
||||
userData = await getUserData(userName=userName)
|
||||
elif userData is None and userName is None:
|
||||
raise ValueError('[TETRIS STATS] IODataProcessing.getUserID: 参数错误')
|
||||
return userData['Data']['data']['user']['_id']
|
||||
|
||||
# 获取排位统计数据
|
||||
async def getLeagueStats(userData: dict) -> dict[str, bool|int|str|float]:
|
||||
league = userData['Data']['data']['user']['league']
|
||||
leagueStats = {}
|
||||
if league['gamesplayed'] == 0:
|
||||
leagueStats['Played'] = False
|
||||
else:
|
||||
leagueStats['Played'] = True
|
||||
leagueStats['PPS'] = league['pps']
|
||||
leagueStats['APM'] = league['apm']
|
||||
leagueStats['VS'] = 0 if league['vs'] is None else league['vs']
|
||||
leagueStats['Rank'] = False if league['rank'] == 'z' else league['rank'].upper()
|
||||
if league['rating'] != -1:
|
||||
leagueStats['Ranked'] = True
|
||||
leagueStats['Rating'] = round(league['rating'], 2)
|
||||
leagueStats['Glicko'] = round(league['glicko'], 2)
|
||||
leagueStats['RD'] = round(league['rd'], 2)
|
||||
else:
|
||||
leagueStats['Ranked'] = False
|
||||
leagueStats['Standing'] = league['standing']
|
||||
leagueStats['LPM'] = round((league['pps'] * 24), 2)
|
||||
leagueStats['APL'] = round((leagueStats['APM'] / leagueStats['LPM']), 2)
|
||||
leagueStats['ADPM'] = round((leagueStats['VS'] * 0.6), 2)
|
||||
leagueStats['ADPL'] = round((leagueStats['ADPM'] / leagueStats['LPM']), 2)
|
||||
return leagueStats
|
||||
|
||||
# 获取40L统计数据
|
||||
async def getSprintStats(soloData: dict) -> dict[str, bool|int|float]:
|
||||
sprintStats = {}
|
||||
if soloData['Data']['data']['records']['40l']['record'] is None:
|
||||
sprintStats['Played'] = False
|
||||
else:
|
||||
sprintStats['Played'] = True
|
||||
sprintStats['Rank'] = False if soloData['Data']['data']['records']['40l']['rank'] is None else soloData['Data']['data']['records']['40l']['rank']
|
||||
sprintStats['Time'] = round(soloData['Data']['data']['records']['40l']['record']['endcontext']['finalTime'] / 1000, 2)
|
||||
return sprintStats
|
||||
|
||||
# 获取Blitz统计数据
|
||||
async def getBlitzStats(soloData: dict) -> dict[str, bool|int]:
|
||||
blitzStats = {}
|
||||
if soloData['Data']['data']['records']['blitz']['record'] is None:
|
||||
blitzStats['Played'] = False
|
||||
else:
|
||||
blitzStats['Played'] = True
|
||||
blitzStats['Rank'] = False if soloData['Data']['data']['records']['blitz']['rank'] is None else soloData['Data']['data']['records']['blitz']['rank']
|
||||
blitzStats['Score'] = soloData['Data']['data']['records']['blitz']['record']['endcontext']['score']
|
||||
return blitzStats
|
||||
|
||||
# 生成消息
|
||||
async def generateMessage(userName: str = None, userID: str = None) -> str:
|
||||
userData, soloData = await gather(getUserData(userName = userName, userID = userID), getSoloData(userName = userName, userID = userID))
|
||||
if userData['Status'] is False:
|
||||
return '用户信息请求失败'
|
||||
if userData['Success'] is False:
|
||||
return f'用户信息请求错误:\n{userData["Data"]["error"]}'
|
||||
userName = userData['Data']['data']['user']['username'].upper()
|
||||
leagueStats = await getLeagueStats(userData)
|
||||
message = ''
|
||||
if leagueStats['Played'] is False:
|
||||
message += f'用户 {userName} 没有排位统计数据'
|
||||
else:
|
||||
if leagueStats['Rank'] is False and leagueStats['Ranked'] is False:
|
||||
message += f'用户 {userName} 暂未完成定级赛'
|
||||
elif leagueStats['Rank'] is False and leagueStats['Ranked'] is True:
|
||||
message += f'用户 {userName} 暂无段位, {leagueStats["Rating"]} TR'
|
||||
else:
|
||||
message += f'{leagueStats["Rank"]} 段用户 {userName} {leagueStats["Rating"]} TR (#{leagueStats["Standing"]})'
|
||||
message += f', 段位分 {leagueStats["Glicko"]}±{leagueStats["RD"]}, 最近十场的数据:' if leagueStats['Ranked'] is True else ', 最近十场的数据:'
|
||||
message += f'\nL\'PM: {leagueStats["LPM"]} ( {leagueStats["PPS"]} pps )'
|
||||
message += f'\nAPM: {leagueStats["APM"]} ( x{leagueStats["APL"]} )'
|
||||
if leagueStats["VS"] != 0:
|
||||
message += f'\nADPM: {leagueStats["ADPM"]} ( x{leagueStats["ADPL"]} ) ( {leagueStats["VS"]}vs )'
|
||||
if soloData['Status'] is False:
|
||||
return f'{message}\nSolo统计数据请求失败'
|
||||
sprintStats = await getSprintStats(soloData)
|
||||
blitzStats = await getBlitzStats(soloData)
|
||||
if sprintStats['Played'] is True:
|
||||
message += f'\n40L: {sprintStats["Time"]}s'
|
||||
if sprintStats['Rank'] is not False:
|
||||
message += f' ( #{sprintStats["Rank"]} )'
|
||||
if blitzStats['Played'] is True:
|
||||
message += f'\nBlitz: {blitzStats["Score"]}'
|
||||
if blitzStats['Rank'] is not False:
|
||||
message += f' ( #{blitzStats["Rank"]} )'
|
||||
return message
|
||||
@@ -1,69 +0,0 @@
|
||||
from re import match
|
||||
|
||||
# userBind
|
||||
async def handleBindMessage(message: str, gameType: str) -> dict[str, bool | str]:
|
||||
_CMD_ALIASES = {'IO': ['io绑定', 'iobind'],
|
||||
'TOP': ['top绑定', 'topbind']}
|
||||
# 剔除命令前缀
|
||||
for i in _CMD_ALIASES[gameType]:
|
||||
if message.startswith(i):
|
||||
message = message.replace(i, '')
|
||||
message = message.strip()
|
||||
break
|
||||
if message == '' or message.isspace():
|
||||
return {'Success': False, 'Type': None, 'Message': '用户名为空'}
|
||||
else:
|
||||
return await checkName(message, gameType)
|
||||
|
||||
# statsQuery
|
||||
async def handleStatsQueryMessage(message: str, gameType: str) -> dict[str, bool | str]:
|
||||
_CMD_ALIASES = {'IO': ['io查', 'iostats'],
|
||||
'TOS': ['tos查', 'tostats', '茶服查', '茶服stats'],
|
||||
'TOP': ['top查', 'topstats']}
|
||||
_ME = ['我', '自己', '私', '俺', 'me']
|
||||
message = (message.strip()).lower()
|
||||
# 剔除命令前缀
|
||||
for i in _CMD_ALIASES[gameType]:
|
||||
if message.startswith(i):
|
||||
message = message.replace(i, '')
|
||||
message = message.strip()
|
||||
break
|
||||
if message == '' or message.isspace():
|
||||
return {'Success': False, 'Type': None, 'Message': '用户名为空'}
|
||||
else:
|
||||
if message.startswith('[cq:at,qq='):
|
||||
try:
|
||||
QQNumber = int((str(message)).split(
|
||||
'[cq:at,qq=')[1].split(']')[0])
|
||||
except ValueError:
|
||||
return {'Success': False, 'Type': None, 'Message': 'QQ号码不合法'}
|
||||
else:
|
||||
return {'Success': True, 'Type': 'AT', 'Message': None, 'QQNumber': QQNumber}
|
||||
elif message in _ME:
|
||||
return {'Success': True, 'Type': 'ME', 'Message': None}
|
||||
else:
|
||||
return await checkName(message, gameType)
|
||||
|
||||
async def checkName(name: str, gameType: str) -> dict[str, bool | str]:
|
||||
if gameType == 'IO':
|
||||
if match(r'^[a-f0-9]{24}$', name):
|
||||
return {'Success': True, 'Type': 'ID', 'Message': None, 'User': name}
|
||||
elif match(r'^[a-zA-Z0-9_-]{3,16}$', name):
|
||||
return {'Success': True, 'Type': 'Name', 'Message': None, 'User': name}
|
||||
else:
|
||||
return {'Success': False, 'Type': None, 'Message': '用户名不合法'}
|
||||
elif gameType == 'TOP':
|
||||
if match(r'^[a-zA-Z0-9_]{1,16}$', name):
|
||||
return {'Success': True, 'Type': 'Name', 'Message': None, 'User': name}
|
||||
else:
|
||||
return {'Success': False, 'Type': None, 'Message': '用户名不合法'}
|
||||
elif gameType == 'TOS':
|
||||
if (match(r'^(?!\.)(?!com[0-9]$)(?!con$)(?!lpt[0-9]$)(?!nul$)(?!prn$)[^\-][^\+][^\|\*\?\\\s\!:<>/$"]*[^\.\|\*\?\\\s\!:<>/$"]+$', name)
|
||||
and name.isdigit() is False
|
||||
and 2 <= len(name) <= 18):
|
||||
# 虽然我也不想这么长 但是似乎确实得这么长
|
||||
return {'Success': True, 'Type': 'Name', 'Message': None, 'User': name}
|
||||
elif name.isdigit() is True:
|
||||
return {'Success': True, 'Type': 'QQ', 'Message': None, 'QQNumber': name}
|
||||
else:
|
||||
return {'Success': False, 'Type': None, 'Message': '用户名不合法'}
|
||||
@@ -1,143 +0,0 @@
|
||||
from nonebot.log import logger
|
||||
|
||||
from asyncio import gather
|
||||
import aiohttp
|
||||
|
||||
# 封装请求函数
|
||||
async def request(Url: str) -> dict[str, bool|dict[str, any]]:
|
||||
data = {}
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(Url) as resp:
|
||||
data['Status'] = True
|
||||
data['Data'] = await resp.json()
|
||||
data['Success'] = data['Data']['success']
|
||||
except aiohttp.client_exceptions.ClientConnectorError as e:
|
||||
logger.error(f'[TETRIS STATS] TOSDataProcessing.request: 请求错误\n{e}')
|
||||
data['Status'] = False
|
||||
finally:
|
||||
return data
|
||||
|
||||
# 获取用户信息
|
||||
async def getUserInfo(userName: str = None, teaID: int = None) -> dict[str, bool|dict[str, any]]:
|
||||
if userName is not None and teaID is None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getUsernameInfo?username={userName}'
|
||||
userInfo = await request(Url=userDataUrl)
|
||||
elif teaID is not None and userName is None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getTeaIdInfo?teaId={teaID}'
|
||||
userInfo = await request(Url=userDataUrl)
|
||||
else:
|
||||
raise ValueError('[TETRIS STATS] TOSDataProcessing.getUserInfo: 参数错误')
|
||||
return userInfo
|
||||
|
||||
# 获取用户数据
|
||||
async def getUserData(userName: str = None, teaID: int = None, otherParameter: str = '') -> dict[str, bool|dict[str, any]]:
|
||||
if userName is not None and teaID is None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getProfile?id={userName}{otherParameter}'
|
||||
userData = await request(Url=userDataUrl)
|
||||
elif teaID is not None and userName is None:
|
||||
userDataUrl = f'https://teatube.cn:8888/getProfile?id={teaID}{otherParameter}'
|
||||
userData = await request(Url=userDataUrl)
|
||||
else:
|
||||
raise ValueError('[TETRIS STATS] TOSDataProcessing.getUserData: 参数错误')
|
||||
return userData
|
||||
|
||||
# 获取Rank数据
|
||||
async def getRankStats(userInfo: dict) -> dict[str, bool|float]:
|
||||
rankStats = {}
|
||||
if int(userInfo['Data']['data']['rankedGames']) == 0:
|
||||
rankStats['Played'] = False
|
||||
else:
|
||||
rankStats['Played'] = True
|
||||
rankStats['Rating'] = round(float(userInfo['Data']['data']['ratingNow']), 2)
|
||||
rankStats['RD'] = round(float(userInfo['Data']['data']['rdNow']), 2)
|
||||
rankStats['Vol'] = round(float(userInfo['Data']['data']['volNow']), 3)
|
||||
return rankStats
|
||||
|
||||
# 获取游戏数据
|
||||
async def getGameData(userData: dict) -> dict[str, bool|int|float]:
|
||||
gameData = {}
|
||||
if userData['Data']['data'] == []:
|
||||
gameData['Played'] = False
|
||||
else:
|
||||
gameData['Played'] = True
|
||||
weightedTotalLpm = weightedTotalApm = weightedTotalAdpm = weightedTotalTime = num = 0
|
||||
for i in userData['Data']['data']:
|
||||
# 排除单人局和时间为0的游戏
|
||||
if i['num_players'] == 1 or i['time'] == 0:
|
||||
continue
|
||||
# 茶:不计算没挖掘的局,即使apm和lpm也如此
|
||||
if i['dig'] is None:
|
||||
break
|
||||
# 加权计算
|
||||
time = i['time'] / 1000
|
||||
lpm = 24 * (i['pieces'] / time)
|
||||
apm = (i['attack'] / time) * 60
|
||||
adpm = ((i['attack'] + i['dig']) / time) * 60
|
||||
weightedTotalLpm += lpm * time
|
||||
weightedTotalApm += apm * time
|
||||
weightedTotalAdpm += adpm * time
|
||||
weightedTotalTime += time
|
||||
num += 1
|
||||
if num == 50:
|
||||
break
|
||||
gameData['NUM'] = num
|
||||
gameData['LPM'] = round((weightedTotalLpm / weightedTotalTime), 2)
|
||||
gameData['APM'] = round((weightedTotalApm / weightedTotalTime), 2)
|
||||
gameData['ADPM'] = round((weightedTotalAdpm / weightedTotalTime), 2)
|
||||
gameData['PPS'] = round((gameData['LPM'] / 24), 2)
|
||||
gameData['APL'] = round((gameData['APM'] / gameData['LPM']), 2)
|
||||
gameData['ADPL'] = round((gameData['ADPM'] / gameData['LPM']), 2)
|
||||
gameData['VS'] = round((gameData['ADPM'] / 60 * 100), 2)
|
||||
# TODO: 如果有效局数不满50, 没有无dig信息的局, 且userData['Data']['data']内有50个局, 则继续往前获取信息
|
||||
return gameData
|
||||
|
||||
# 获取PB数据
|
||||
async def getPBData(userInfo: dict) -> dict[str, bool|float|str]:
|
||||
PBData = {}
|
||||
if int(userInfo['Data']['data']['PBSprint']) == 2147483647:
|
||||
PBData['Sprint'] = False
|
||||
else:
|
||||
PBData['Sprint'] = round(float(userInfo['Data']['data']['PBSprint']) / 1000, 2)
|
||||
if int(userInfo['Data']['data']['PBMarathon']) == 0:
|
||||
PBData['Marathon'] = False
|
||||
else:
|
||||
PBData['Marathon'] = userInfo['Data']['data']['PBMarathon']
|
||||
if int(userInfo['Data']['data']['PBChallenge']) == 0:
|
||||
PBData['Challenge'] = False
|
||||
else:
|
||||
PBData['Challenge'] = userInfo['Data']['data']['PBChallenge']
|
||||
return PBData
|
||||
|
||||
# 生成消息
|
||||
async def generateMessage(userName: str = None, teaID: int = None) -> str:
|
||||
userInfo, userData = await gather(getUserInfo(userName=userName, teaID=teaID), getUserData(userName=userName, teaID=teaID))
|
||||
if userInfo['Status'] is False:
|
||||
return f'用户信息请求失败'
|
||||
if userInfo['Success'] is False:
|
||||
return f'用户信息请求错误:\n{userInfo["Data"]["error"]}'
|
||||
rankStats = await getRankStats(userInfo)
|
||||
PBData = await getPBData(userInfo)
|
||||
message = ''
|
||||
if rankStats['Played'] is False:
|
||||
message += f'用户 {userInfo["Data"]["data"]["name"]}({userInfo["Data"]["data"]["teaId"]})暂无段位统计数据'
|
||||
elif rankStats['Played'] is True:
|
||||
message += f'用户 {userInfo["Data"]["data"]["name"]} ({userInfo["Data"]["data"]["teaId"]}) , 段位分 {rankStats["Rating"]}±{rankStats["RD"]} ({rankStats["Vol"]}) '
|
||||
if userData['Status'] is False:
|
||||
message = f'{message.rstrip()}\n游戏数据请求失败'
|
||||
elif userData['Status'] is True:
|
||||
gameData = await getGameData(userData)
|
||||
if gameData['Played'] is False:
|
||||
message += ', 暂无游戏数据'
|
||||
elif gameData['Played'] is True:
|
||||
message += f', 最近 {gameData["NUM"]} 局数据'
|
||||
message += f'\nL\'PM: {gameData["LPM"]} ( {gameData["PPS"]} pps )'
|
||||
message += f'\nAPM:{gameData["APM"]} ( x{gameData["APL"]} )'
|
||||
message += f'\nADPM:{gameData["ADPM"]} ( x{gameData["ADPL"]} ) ( {gameData["VS"]}vs )'
|
||||
if PBData['Sprint'] is not False:
|
||||
message += f'\n40L: {PBData["Sprint"]}s'
|
||||
if PBData['Marathon']is not False:
|
||||
message += f'\nMarathon: {PBData["Marathon"]}'
|
||||
if PBData['Challenge'] is not False:
|
||||
message += f'\nChallenge: {PBData["Challenge"]}'
|
||||
return message
|
||||
79
nonebot_plugin_tetris_stats/Utils/MessageAnalyzer.py
Normal file
79
nonebot_plugin_tetris_stats/Utils/MessageAnalyzer.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from re import match, sub
|
||||
|
||||
|
||||
async def handleBindMessage(message: str, gameType: str) -> tuple[str | None, tuple]:
|
||||
'''返回值为tuple[gameType, tuple[message, user]]'''
|
||||
_CMD_ALIASES = {'IO': ['io绑定', 'iobind'],
|
||||
'TOP': ['top绑定', 'topbind']}
|
||||
# 剔除命令前缀
|
||||
for i in _CMD_ALIASES[gameType]:
|
||||
if match(rf'(?i){i}', message):
|
||||
message = sub(rf'(?i){i}', '', message)
|
||||
message = message.strip()
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
'[TETRIS STATS] MessageAnalyzer.handleBindMessage: 预期外行为,请上报GitHub')
|
||||
if message == '' or message.isspace():
|
||||
return (None, ('用户名为空', None))
|
||||
else:
|
||||
return await checkName(message, gameType)
|
||||
|
||||
|
||||
async def handleStatsQueryMessage(message: str, gameType: str) -> tuple[str | None, tuple]:
|
||||
'''返回值为tuple[gameType, tuple[message, user]]'''
|
||||
_CMD_ALIASES = {'IO': ['io查', 'iostats'],
|
||||
'TOS': ['tos查', 'tostats', 'tosstats', '茶服查', '茶服stats'],
|
||||
'TOP': ['top查', 'topstats']}
|
||||
_ME = ['我', '自己', '我等', '卑人', '愚', '老身', '爷', '老娘', '本姑娘', '本大爷',
|
||||
'鄙人', '寡人', '小生', '贫僧', '本人', '孤', '吾', '俺', '咱', '私', 'me']
|
||||
# 剔除命令前缀
|
||||
for i in _CMD_ALIASES[gameType]:
|
||||
if match(rf'(?i){i}', message):
|
||||
message = sub(rf'(?i){i}', '', message)
|
||||
message = message.strip()
|
||||
break
|
||||
if message == '' or message.isspace():
|
||||
return (None, ('用户名为空', None))
|
||||
else:
|
||||
if message.startswith('[CQ:at,qq='):
|
||||
try:
|
||||
user = int(str(message).split('[CQ:at,qq=')[1].split(']')[0])
|
||||
except ValueError:
|
||||
return (None, ('QQ号码不合法', None))
|
||||
else:
|
||||
return ('AT', (None, user))
|
||||
elif message in _ME:
|
||||
# 会不会有人叫本姑娘 本大爷这种或许可以成为id的名字呢
|
||||
# TODO: 在判断是否可能是查自己的情况的时候 也去判断是否能成立为一个UserName
|
||||
return ('ME', (None, None))
|
||||
else:
|
||||
return await checkName(message, gameType)
|
||||
|
||||
|
||||
async def checkName(name: str, gameType: str) -> tuple[str | None, tuple]:
|
||||
'''返回值为tuple[gameType, tuple[message, user]]'''
|
||||
if gameType == 'IO':
|
||||
if match(r'^[a-f0-9]{24}$', name):
|
||||
return ('ID', (None, name))
|
||||
elif match(r'^[a-zA-Z0-9_-]{3,16}$', name):
|
||||
return ('Name', (None, name.lower()))
|
||||
else:
|
||||
return (None, ('用户名不合法', None))
|
||||
elif gameType == 'TOP':
|
||||
if match(r'^[a-zA-Z0-9_]{1,16}$', name):
|
||||
return ('Name', (None, name))
|
||||
else:
|
||||
return (None, ('用户名不合法', None))
|
||||
elif gameType == 'TOS':
|
||||
if (match(r'^(?!\.)(?!com[0-9]$)(?!con$)(?!lpt[0-9]$)(?!nul$)(?!prn$)[^\-][^\+][^\|\*\?\\\s\!:<>/$"]*[^\.\|\*\?\\\s\!:<>/$"]+$', name)
|
||||
and name.isdigit() is False
|
||||
and 2 <= len(name) <= 18):
|
||||
# 虽然我也不想这么长 但是似乎确实得这么长
|
||||
return ('Name', (None, name))
|
||||
elif name.isdigit() is True:
|
||||
return ('QQ', (None, name))
|
||||
else:
|
||||
return (None, ('用户名不合法', None))
|
||||
else:
|
||||
return (None, ('游戏类型错误', None))
|
||||
17
nonebot_plugin_tetris_stats/Utils/Request.py
Normal file
17
nonebot_plugin_tetris_stats/Utils/Request.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from nonebot.log import logger
|
||||
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
async def request(Url: str) -> tuple[bool, bool, dict[str, Any]]:
|
||||
# 封装请求函数
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(Url) as resp:
|
||||
data = await resp.json()
|
||||
return (True, data['success'], data)
|
||||
except aiohttp.client_exceptions.ClientConnectorError as e:
|
||||
logger.error(f'[TETRIS STATS] request.request: 请求错误\n{e}')
|
||||
return (False, False, {})
|
||||
@@ -3,12 +3,17 @@ from nonebot.log import logger
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
_DB_FILE = 'data/nonebot-plugin-tetris-stats/data.db'
|
||||
_DB_FILE = 'data/nonebot_plugin_tetris_stats/data.db'
|
||||
|
||||
|
||||
# 初始化数据库
|
||||
async def initDB():
|
||||
# 初始化数据库
|
||||
if not os.path.exists(os.path.dirname(_DB_FILE)):
|
||||
os.makedirs(os.path.dirname(_DB_FILE))
|
||||
if os.path.exists('data/nonebot-plugin-tetris-stats'): # 重命名旧的数据库路径
|
||||
os.rename('data/nonebot-plugin-tetris-stats',
|
||||
os.path.dirname(_DB_FILE))
|
||||
else:
|
||||
os.makedirs(os.path.dirname(_DB_FILE))
|
||||
db = sqlite3.connect(_DB_FILE)
|
||||
cursor = db.cursor()
|
||||
cursor.execute('''CREATE TABLE IF NOT EXISTS IOBIND
|
||||
@@ -21,8 +26,9 @@ async def initDB():
|
||||
db.close()
|
||||
logger.info('数据库初始化完成')
|
||||
|
||||
# 查询绑定信息
|
||||
async def queryBindInfo(QQNumber: int, gameType: str) -> dict:
|
||||
|
||||
async def queryBindInfo(QQNumber: str | int, gameType: str) -> str | None:
|
||||
# 查询绑定信息
|
||||
db = sqlite3.connect(_DB_FILE)
|
||||
cursor = db.cursor()
|
||||
cursor.execute(f'SELECT USER FROM {gameType}BIND WHERE QQ = {QQNumber}')
|
||||
@@ -30,20 +36,21 @@ async def queryBindInfo(QQNumber: int, gameType: str) -> dict:
|
||||
db.commit()
|
||||
db.close()
|
||||
if user is None:
|
||||
return {'Hit': False, 'User': None}
|
||||
return None
|
||||
else:
|
||||
return {'Hit': True, 'User': user[0]}
|
||||
return user[0]
|
||||
|
||||
# 写入绑定信息
|
||||
async def writeBindInfo(QQNumber: int, user: str, gameType: str) -> str:
|
||||
info = await queryBindInfo(QQNumber, gameType)
|
||||
|
||||
async def writeBindInfo(QQNumber: str | int, user: str, gameType: str) -> str:
|
||||
# 写入绑定信息
|
||||
bindInfo = await queryBindInfo(QQNumber, gameType)
|
||||
db = sqlite3.connect(_DB_FILE)
|
||||
cursor = db.cursor()
|
||||
if info['Hit'] is True:
|
||||
if bindInfo is not None:
|
||||
cursor.execute(
|
||||
f'UPDATE {gameType}BIND SET USER = ? WHERE QQ = ?', (user, QQNumber))
|
||||
message = '更新成功'
|
||||
elif info['Hit'] is False:
|
||||
elif bindInfo is None:
|
||||
cursor.execute(
|
||||
f'INSERT INTO {gameType}BIND (QQ, USER) VALUES (?, ?)', (QQNumber, user))
|
||||
message = '绑定成功'
|
||||
1
nonebot_plugin_tetris_stats/Utils/__init__.py
Normal file
1
nonebot_plugin_tetris_stats/Utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import MessageAnalyzer, Request, SQL
|
||||
@@ -1,86 +1,13 @@
|
||||
from re import I
|
||||
from nonebot import get_driver
|
||||
|
||||
from nonebot import get_driver, on_regex
|
||||
from nonebot.adapters.onebot.v11 import GROUP, MessageEvent
|
||||
from nonebot.matcher import Matcher
|
||||
from .Utils.SQL import initDB
|
||||
|
||||
from .MessageAnalyzer import *
|
||||
from .SQL import *
|
||||
from . import GameDataProcessor
|
||||
|
||||
from .IODataProcessing import generateMessage as IOgenerateMessage
|
||||
from .IODataProcessing import getUserID as IOgetUserID
|
||||
from .TOSDataProcessing import generateMessage as TOSgenerateMessage
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def startUP():
|
||||
await initDB()
|
||||
|
||||
ioBind = on_regex(pattern=r'^io绑定|^iobind', flags=I, permission=GROUP)
|
||||
ioStats = on_regex(pattern=r'^io查|^iostats', flags=I, permission=GROUP)
|
||||
|
||||
tosStats = on_regex(pattern=r'^tos查|^tostats|^茶服查|^茶服stats',
|
||||
flags=I, permission=GROUP)
|
||||
|
||||
topBind = on_regex(pattern=r'^top绑定|^topbind', flags=I, permission=GROUP)
|
||||
topStats = on_regex(pattern=r'^top查|^topstats', flags=I, permission=GROUP)
|
||||
|
||||
@ioBind.handle()
|
||||
async def bindIOUser(event: MessageEvent, matcher: Matcher):
|
||||
decodedMessage = await handleBindMessage(message=str(event.get_message()), gameType='IO')
|
||||
if decodedMessage['Success'] is True:
|
||||
if decodedMessage['Type'] == 'ID':
|
||||
user = decodedMessage['User']
|
||||
elif decodedMessage['Type'] == 'Name':
|
||||
user = await IOgetUserID(userName=decodedMessage['User'])
|
||||
message = await writeBindInfo(QQNumber=event.sender.user_id, user=user, gameType='IO')
|
||||
elif decodedMessage['Success'] is False:
|
||||
message = decodedMessage['Message']
|
||||
await matcher.send(message=message)
|
||||
|
||||
@ioStats.handle()
|
||||
async def handleIOStatsQuery(event: MessageEvent, matcher: Matcher):
|
||||
decodedMessage = await handleStatsQueryMessage(message=str(event.get_message()), gameType='IO')
|
||||
if decodedMessage['Success'] is True:
|
||||
if decodedMessage['Type'] == 'AT':
|
||||
bindInfo = await queryBindInfo(QQNumber=decodedMessage['QQNumber'], gameType='IO')
|
||||
if bindInfo['Hit'] is True:
|
||||
message = (f'* 由于无法验证绑定信息,不能保证查询到的用户为本人\n{await IOgenerateMessage(userID=bindInfo["User"])}')
|
||||
elif bindInfo['Hit'] is False:
|
||||
message = '未查询到绑定信息'
|
||||
elif decodedMessage['Type'] == 'ME':
|
||||
bindInfo = await queryBindInfo(QQNumber=event.sender.user_id, gameType='IO')
|
||||
if bindInfo['Hit'] is True:
|
||||
message = (f'* 由于无法验证绑定信息,不能保证查询到的用户为本人\n{await IOgenerateMessage(userID=bindInfo["User"])}')
|
||||
elif bindInfo['Hit'] is False:
|
||||
message = '您还没有绑定账号'
|
||||
elif decodedMessage['Type'] == 'ID':
|
||||
message = await IOgenerateMessage(userID=decodedMessage['User'])
|
||||
elif decodedMessage['Type'] == 'Name':
|
||||
message = await IOgenerateMessage(userName=decodedMessage['User'])
|
||||
elif decodedMessage['Success'] is False:
|
||||
message = decodedMessage['Message']
|
||||
await matcher.finish(message=message)
|
||||
|
||||
@tosStats.handle()
|
||||
async def handleTOSStatsQuery(event: MessageEvent, matcher: Matcher):
|
||||
decodedMessage = await handleStatsQueryMessage(message=str(event.get_message()), gameType='TOS')
|
||||
if decodedMessage['Success'] is True:
|
||||
if decodedMessage['Type'] == 'AT' or decodedMessage['Type'] == 'QQ':
|
||||
message = await TOSgenerateMessage(teaID=decodedMessage['QQNumber'])
|
||||
elif decodedMessage['Type'] == 'ME':
|
||||
message = await TOSgenerateMessage(teaID=event.sender.user_id)
|
||||
elif decodedMessage['Type'] == 'Name':
|
||||
message = await TOSgenerateMessage(userName=decodedMessage['User'])
|
||||
elif decodedMessage['Success'] is False:
|
||||
message = decodedMessage['Message']
|
||||
await matcher.finish(message=message)
|
||||
|
||||
@topBind.handle()
|
||||
async def bindTOPUser(event: MessageEvent, matcher: Matcher):
|
||||
await matcher.send(message='TODO')
|
||||
|
||||
@topStats.handle()
|
||||
async def handleTOPStatsQuery(event: MessageEvent, matcher: Matcher):
|
||||
await matcher.send(message='TODO')
|
||||
|
||||
405
poetry.lock
generated
405
poetry.lock
generated
@@ -46,17 +46,6 @@ doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
|
||||
[[package]]
|
||||
name = "arrow"
|
||||
version = "1.2.2"
|
||||
description = "Better dates & times for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.5.2"
|
||||
@@ -99,31 +88,16 @@ tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)"
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "binaryornot"
|
||||
version = "0.4.4"
|
||||
description = "Ultra-lightweight pure Python package to check if a file is binary or text."
|
||||
category = "main"
|
||||
name = "autopep8"
|
||||
version = "1.6.0"
|
||||
description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
chardet = ">=3.0.2"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.5.18.1"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "chardet"
|
||||
version = "4.0.0"
|
||||
description = "Universal encoding detector for Python 2 and 3"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
pycodestyle = ">=2.8.0"
|
||||
toml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
@@ -155,24 +129,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "cookiecutter"
|
||||
version = "1.7.3"
|
||||
description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.dependencies]
|
||||
binaryornot = ">=0.4.4"
|
||||
click = ">=7.0"
|
||||
Jinja2 = ">=2.7,<4.0.0"
|
||||
jinja2-time = ">=0.2.0"
|
||||
poyo = ">=0.5.0"
|
||||
python-slugify = ">=4.0.0"
|
||||
requests = ">=2.23.0"
|
||||
six = ">=1.10"
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.78.0"
|
||||
@@ -207,21 +163,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "0.13.2"
|
||||
description = "A minimal low-level HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
h11 = "<1.0.0"
|
||||
sniffio = ">=1.0.0,<2.0.0"
|
||||
|
||||
[package.extras]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.4.0"
|
||||
@@ -233,24 +174,6 @@ python-versions = ">=3.5.0"
|
||||
[package.extras]
|
||||
test = ["Cython (>=0.29.24,<0.30.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.18.1"
|
||||
description = "The next generation HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
httpcore = ">=0.13.0,<0.14.0"
|
||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi (>=1.0.0,<2.0.0)"]
|
||||
http2 = ["h2 (>=3.0.0,<4.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
@@ -259,32 +182,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2-time"
|
||||
version = "0.2.0"
|
||||
description = "Jinja2 Extension for Dates and Times"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
arrow = "*"
|
||||
jinja2 = "*"
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.6.0"
|
||||
@@ -300,14 +197,6 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||
[package.extras]
|
||||
dev = ["colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "black (>=19.10b0)", "isort (>=5.1.1)", "Sphinx (>=4.1.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.0.2"
|
||||
@@ -342,29 +231,6 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "nb-cli"
|
||||
version = "0.6.7"
|
||||
description = "CLI for nonebot2"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.3,<4.0.0"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0,<9.0.0"
|
||||
colorama = ">=0.4.3,<0.5.0"
|
||||
cookiecutter = ">=1.7.2,<2.0.0"
|
||||
httpx = ">=0.18.0,<1.0.0"
|
||||
nonebot2 = ">=2.0.0-beta.1,<3.0.0"
|
||||
prompt-toolkit = ">=3.0.19,<4.0.0"
|
||||
pyfiglet = ">=0.8.post1,<0.9"
|
||||
tomlkit = ">=0.10.0,<0.11.0"
|
||||
wcwidth = ">=0.2.5,<0.3.0"
|
||||
|
||||
[package.extras]
|
||||
deploy = ["docker-compose (>=1.29.2,<1.30.0)"]
|
||||
docker = ["docker-compose (>=1.29.2,<1.30.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-adapter-onebot"
|
||||
version = "2.0.0b1"
|
||||
@@ -402,23 +268,12 @@ aiohttp = ["aiohttp[speedups] (>=3.7.4,<4.0.0)"]
|
||||
httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "poyo"
|
||||
version = "0.5.0"
|
||||
description = "A lightweight YAML Parser for Python. 🐓"
|
||||
category = "main"
|
||||
name = "pycodestyle"
|
||||
version = "2.8.0"
|
||||
description = "Python style guide checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.29"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
@@ -436,14 +291,6 @@ typing-extensions = ">=3.7.4.3"
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyfiglet"
|
||||
version = "0.8.post1"
|
||||
description = "Pure-python FIGlet implementation"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pygtrie"
|
||||
version = "2.4.2"
|
||||
@@ -452,17 +299,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "0.20.0"
|
||||
@@ -474,20 +310,6 @@ python-versions = ">=3.5"
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-slugify"
|
||||
version = "6.1.2"
|
||||
description = "A Python slugify application that also handles Unicode"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[package.dependencies]
|
||||
text-unidecode = ">=1.3"
|
||||
|
||||
[package.extras]
|
||||
unidecode = ["Unidecode (>=1.1.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
@@ -496,46 +318,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.27.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
|
||||
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
version = "1.5.0"
|
||||
description = "Validating URI References per RFC 3986"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
||||
|
||||
[package.extras]
|
||||
idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.2.0"
|
||||
@@ -559,12 +341,12 @@ anyio = ">=3.4.0,<5"
|
||||
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
|
||||
|
||||
[[package]]
|
||||
name = "text-unidecode"
|
||||
version = "1.3"
|
||||
description = "The most basic Text::Unidecode port"
|
||||
category = "main"
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
@@ -590,19 +372,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.9"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.17.6"
|
||||
@@ -650,14 +419,6 @@ python-versions = ">=3.7"
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0,<4"
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.5"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "10.3"
|
||||
@@ -692,7 +453,7 @@ multidict = ">=4.0"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "75ef2d677c56aead3d7a7f1d1f8570120b56d8642832183a280062c54f7f5cd3"
|
||||
content-hash = "a4d410151de3d540ca69d31936bedf6f731720abc4467821224b07bb34048cd1"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = [
|
||||
@@ -777,10 +538,6 @@ anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
]
|
||||
arrow = [
|
||||
{file = "arrow-1.2.2-py3-none-any.whl", hash = "sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177"},
|
||||
{file = "arrow-1.2.2.tar.gz", hash = "sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b"},
|
||||
]
|
||||
asgiref = [
|
||||
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
|
||||
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
|
||||
@@ -799,17 +556,9 @@ attrs = [
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
binaryornot = [
|
||||
{file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"},
|
||||
{file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
|
||||
{file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
||||
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
||||
autopep8 = [
|
||||
{file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"},
|
||||
{file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
@@ -823,10 +572,6 @@ colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
cookiecutter = [
|
||||
{file = "cookiecutter-1.7.3-py2.py3-none-any.whl", hash = "sha256:f8671531fa96ab14339d0c59b4f662a4f12a2ecacd94a0f70a3500843da588e2"},
|
||||
{file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"},
|
||||
]
|
||||
fastapi = [
|
||||
{file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
|
||||
{file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
|
||||
@@ -896,10 +641,6 @@ h11 = [
|
||||
{file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"},
|
||||
{file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"},
|
||||
]
|
||||
httpcore = [
|
||||
{file = "httpcore-0.13.2-py3-none-any.whl", hash = "sha256:52b7d9413f6f5592a667de9209d70d4d41aba3fb0540dd7c93475c78b85941e9"},
|
||||
{file = "httpcore-0.13.2.tar.gz", hash = "sha256:c16efbdf643e1b57bde0adc12c53b08645d7d92d6d345a3f71adfc2a083e7fd2"},
|
||||
]
|
||||
httptools = [
|
||||
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1ee0b459257e222b878a6c09ccf233957d3a4dcb883b0847640af98d2d9aac23"},
|
||||
@@ -936,68 +677,14 @@ httptools = [
|
||||
{file = "httptools-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:34d2903dd2a3dd85d33705b6fde40bf91fc44411661283763fd0746723963c83"},
|
||||
{file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"},
|
||||
]
|
||||
httpx = [
|
||||
{file = "httpx-0.18.1-py3-none-any.whl", hash = "sha256:ad2e3db847be736edc4b272c4d5788790a7e5789ef132fc6b5fef8aeb9e9f6e0"},
|
||||
{file = "httpx-0.18.1.tar.gz", hash = "sha256:0a2651dd2b9d7662c70d12ada5c290abcf57373b9633515fe4baa9f62566086f"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||
]
|
||||
jinja2-time = [
|
||||
{file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"},
|
||||
{file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"},
|
||||
]
|
||||
loguru = [
|
||||
{file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
|
||||
{file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||
]
|
||||
multidict = [
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
|
||||
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
|
||||
@@ -1088,10 +775,6 @@ mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
nb-cli = [
|
||||
{file = "nb-cli-0.6.7.tar.gz", hash = "sha256:394bf65eabbda6aa7c410961b901e9f7320fae12143818b1c078fc43f060fa0e"},
|
||||
{file = "nb_cli-0.6.7-py3-none-any.whl", hash = "sha256:693342ebcf4dce14fd7be4555c65e5da41707a67e5de73d7d7fe67a716a1cad9"},
|
||||
]
|
||||
nonebot-adapter-onebot = [
|
||||
{file = "nonebot-adapter-onebot-2.0.0b1.tar.gz", hash = "sha256:9dad770371e577fead096ceacacc43b3ef304a8e238e8fff1163eefc4e947a75"},
|
||||
{file = "nonebot_adapter_onebot-2.0.0b1-py3-none-any.whl", hash = "sha256:ca1375de1dd503a5ab20440445026195b587e05a2b18ae8df9b6ab17c9e857b5"},
|
||||
@@ -1100,13 +783,9 @@ nonebot2 = [
|
||||
{file = "nonebot2-2.0.0b3-py3-none-any.whl", hash = "sha256:00f2ea63d5f2c665428bec4b7a33301f6b1b483d5635d2a3241f0a1ab3b5b2ea"},
|
||||
{file = "nonebot2-2.0.0b3.tar.gz", hash = "sha256:b7ee6ddf387af268e36f4276c2d94b4f717c1a29078400738ca275c3fb266dd4"},
|
||||
]
|
||||
poyo = [
|
||||
{file = "poyo-0.5.0-py2.py3-none-any.whl", hash = "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a"},
|
||||
{file = "poyo-0.5.0.tar.gz", hash = "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd"},
|
||||
]
|
||||
prompt-toolkit = [
|
||||
{file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"},
|
||||
{file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"},
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"},
|
||||
@@ -1145,25 +824,13 @@ pydantic = [
|
||||
{file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"},
|
||||
{file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"},
|
||||
]
|
||||
pyfiglet = [
|
||||
{file = "pyfiglet-0.8.post1-py2.py3-none-any.whl", hash = "sha256:d555bcea17fbeaf70eaefa48bb119352487e629c9b56f30f383e2c62dd67a01c"},
|
||||
{file = "pyfiglet-0.8.post1.tar.gz", hash = "sha256:c6c2321755d09267b438ec7b936825a4910fec696292139e664ca8670e103639"},
|
||||
]
|
||||
pygtrie = [
|
||||
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
]
|
||||
python-dotenv = [
|
||||
{file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"},
|
||||
{file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"},
|
||||
]
|
||||
python-slugify = [
|
||||
{file = "python-slugify-6.1.2.tar.gz", hash = "sha256:272d106cb31ab99b3496ba085e3fea0e9e76dcde967b5e9992500d1f785ce4e1"},
|
||||
{file = "python_slugify-6.1.2-py2.py3-none-any.whl", hash = "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
@@ -1199,18 +866,6 @@ pyyaml = [
|
||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
||||
]
|
||||
rfc3986 = [
|
||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
@@ -1219,9 +874,9 @@ starlette = [
|
||||
{file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
|
||||
{file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
|
||||
]
|
||||
text-unidecode = [
|
||||
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
|
||||
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
@@ -1235,10 +890,6 @@ typing-extensions = [
|
||||
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
|
||||
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
||||
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
|
||||
]
|
||||
uvicorn = [
|
||||
{file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"},
|
||||
{file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"},
|
||||
@@ -1265,10 +916,6 @@ watchgod = [
|
||||
{file = "watchgod-0.8.2-py3-none-any.whl", hash = "sha256:2f3e8137d98f493ff58af54ea00f4d1433a6afe2ed08ab331a657df468c6bfce"},
|
||||
{file = "watchgod-0.8.2.tar.gz", hash = "sha256:cb11ff66657befba94d828e3b622d5fb76f22fbda1376f355f3e6e51e97d9450"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
websockets = [
|
||||
{file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"},
|
||||
{file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot-plugin-tetris-stats"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "一个基于nonebot2的用于查询TETRIS相关游戏玩家数据的插件"
|
||||
authors = ["scdhh <wallfjjd@gmail.com>"]
|
||||
readme = "README.md"
|
||||
@@ -10,13 +10,14 @@ license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
nb-cli = "^0.6.7"
|
||||
nonebot-adapter-onebot = "^2.0.0-beta.1"
|
||||
aiohttp = "^3.8.1"
|
||||
asyncio = "^3.4.3"
|
||||
nonebot2 = "^2.0.0-beta.3"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
mypy = "^0.950"
|
||||
autopep8 = "^1.6.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
Reference in New Issue
Block a user