├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── osu.py ├── requirements.txt ├── src ├── DataBase.py ├── Model │ ├── BeatmapModel.py │ ├── PPModel.py │ ├── SayomapModel.py │ ├── ScoreModel.py │ ├── UserModel.py │ └── __init__.py ├── Picture │ ├── Image.py │ ├── Info.py │ ├── Map.py │ ├── Performance.py │ ├── Score.py │ └── __init__.py ├── __init__.py ├── api.py ├── error.py ├── file.py ├── mods.py └── pp.py └── static ├── Best Performance.png ├── OAuth.json ├── beatmapinfo.png ├── flags ├── AD.png ├── AE.png ├── AF.png ├── AG.png ├── AI.png ├── AL.png ├── AM.png ├── AO.png ├── AQ.png ├── AR.png ├── AS.png ├── AT.png ├── AU.png ├── AW.png ├── AX.png ├── AZ.png ├── BA.png ├── BB.png ├── BD.png ├── BE.png ├── BF.png ├── BG.png ├── BH.png ├── BI.png ├── BJ.png ├── BL.png ├── BM.png ├── BN.png ├── BO.png ├── BQ (1).png ├── BQ (2).png ├── BR.png ├── BS.png ├── BT.png ├── BV.png ├── BW.png ├── BY.png ├── BZ.png ├── CA.png ├── CD.png ├── CF.png ├── CG.png ├── CH.png ├── CI.png ├── CK.png ├── CL.png ├── CM.png ├── CN.png ├── CO.png ├── CR.png ├── CU.png ├── CV.png ├── CX.png ├── CY.png ├── CZ.png ├── DE.png ├── DJ.png ├── DK.png ├── DM.png ├── DO.png ├── DZ.png ├── EC.png ├── EE.png ├── EG.png ├── EH.png ├── ER.png ├── ES.png ├── ET.png ├── FI.png ├── FJ.png ├── FK.png ├── FO.png ├── GA.png ├── GB.png ├── GD.png ├── GE.png ├── GF.png ├── GG.png ├── GH.png ├── GI.png ├── GL.png ├── GM.png ├── GN.png ├── GP.png ├── GQ.png ├── GR.png ├── GS.png ├── GT.png ├── GU.png ├── GW.png ├── GY.png ├── HK.png ├── HM.png ├── HN.png ├── HR.png ├── HT.png ├── HU.png ├── ID.png ├── IE.png ├── IL.png ├── IM.png ├── IN.png ├── IO.png ├── IQ.png ├── IR.png ├── IS.png ├── IT.png ├── JE.png ├── JM.png ├── JO.png ├── JP.png ├── KE.png ├── KG.png ├── KH.png ├── KI.png ├── KM.png ├── KN.png ├── KP.png ├── KR.png ├── KW.png ├── KY.png ├── KZ.png ├── LA.png ├── LB.png ├── LC.png ├── LI.png ├── LK.png ├── LR.png ├── LS.png ├── LT.png ├── LU.png ├── LV.png ├── LY.png ├── MA.png ├── MC.png ├── MD.png ├── ME.png ├── MF.png ├── MG.png ├── MH.png ├── MK.png ├── ML.png ├── MM.png ├── MN.png ├── MO.png ├── MP.png ├── MQ.png ├── MR.png ├── MS.png ├── MT.png ├── MU.png ├── MV.png ├── MW.png ├── MX.png ├── MY.png ├── MZ.png ├── NA.png ├── NC.png ├── NE.png ├── NF.png ├── NG.png ├── NI.png ├── NL.png ├── NO.png ├── NP.png ├── NR.png ├── NU.png ├── NZ.png ├── OM.png ├── PA.png ├── PE.png ├── PF.png ├── PG.png ├── PH.png ├── PK.png ├── PL.png ├── PM.png ├── PN.png ├── PR.png ├── PS.png ├── PT.png ├── PW.png ├── PY.png ├── QA.png ├── RE.png ├── RO.png ├── RS.png ├── RU.png ├── RW.png ├── SA.png ├── SB.png ├── SC.png ├── SD.png ├── SE.png ├── SG.png ├── SH.png ├── SI.png ├── SJ.png ├── SK.png ├── SL.png ├── SM.png ├── SN.png ├── SO.png ├── SR.png ├── SS.png ├── ST.png ├── SV.png ├── SY.png ├── SZ.png ├── TC.png ├── TD.png ├── TF.png ├── TG.png ├── TH.png ├── TJ.png ├── TK.png ├── TL.png ├── TM.png ├── TN.png ├── TO.png ├── TR.png ├── TT.png ├── TV.png ├── TW.png ├── TZ.png ├── UA.png ├── UG.png ├── UM.png ├── US.png ├── UY.png ├── UZ.png ├── VA.png ├── VC.png ├── VE.png ├── VG.png ├── VI.png ├── VN.png ├── VU.png ├── WF.png ├── WS.png ├── YE.png ├── YT.png ├── ZA.png ├── ZM.png └── ZW.png ├── fonts ├── Meiryo Regular.ttf ├── Meiryo SemiBold.ttf ├── Torus Regular.otf ├── Torus SemiBold.otf └── Venera.otf ├── help.png ├── info.png ├── map └── s ├── mods ├── 4K.png ├── 5K.png ├── 6K.png ├── 9K.png ├── DT.png ├── EZ.png ├── FI.png ├── FL.png ├── HD.png ├── HR.png ├── HT.png ├── MR.png ├── NC.png ├── NF.png ├── PF.png ├── SD.png ├── SO.png └── TD.png ├── pfm_ctb.png ├── pfm_mania.png ├── pfm_std.png ├── pfm_taiko.png ├── ranking ├── ranking-A.png ├── ranking-B.png ├── ranking-C.png ├── ranking-D.png ├── ranking-F.png ├── ranking-S.png ├── ranking-SH.png ├── ranking-X.png └── ranking-XH.png └── work ├── bmap.png ├── center.png ├── color.png ├── ctb.png ├── ctb_expertplus.png ├── left.png ├── mania.png ├── mania_expertplus.png ├── mapbg.png ├── off-line.png ├── on-line.png ├── right.png ├── stars.png ├── stars_expertplus.png ├── std.png ├── std_expertplus.png ├── suppoter.png ├── taiko.png └── taiko_expertplus.png /.gitignore: -------------------------------------------------------------------------------- 1 | static -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yuri-YuzuChaN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osuv2 2 | 3 | 基于HoshinoBot v2的osu api v2版本的查询模块 4 | 5 | 项目地址:https://github.com/Yuri-YuzuChaN/osuv2 6 | 7 | # 重做中,尚未进行完整测试,暂时无法使用 8 | 9 | 10 | ## 使用方法 11 | 12 | 1. 将该项目放在HoshinoBot插件目录 `modules` 下,或者clone本项目 `git clone https://github.com/Yuri-YuzuChaN/osuv2` 13 | 2. 在 `static/OAuth.json` 文件填入申请的 `client_id`,`client_secret`,[如何申请 OAuth 客户端](#如何申请OAuth客户端) 14 | 3. 安装第三方依赖:`pip install -r requirements.txt` 15 | 4. 在`config/__bot__.py`模块列表中添加`osuv2` 16 | 5. 重启HoshinoBot 17 | 18 | **注:`pillow`需要高于等于8.0.0版本** 19 | 20 | ## 如何申请OAuth客户端 21 | 22 | 1. 打开osu个人设置页面:https://osu.ppy.sh/home/account/edit ,拉到最下面开放授权页面。 23 | 24 | 2. 点击`新的 OAuth 应用`,在弹出的窗口填入`应用名称`(随意),`应用回调链接`(第三方网站或自己的网站),点击`注册应用程序`,此时你已经拥有了 `OAuth客户端` 25 | 26 | 3. [进行使用方法第二步](#使用方法) 27 | 28 | ## 指令说明 29 | ### 如果看不懂可以可以查看 [使用说明](https://sakura.yuzuai.xyz/plugins/rhythmgame/osu!.html) 30 | 31 | ![指令](./static/help.png) 32 | 33 | ## 更新说明 34 | 35 | **2023-09-13** 36 | 37 | 1. 推倒重做所有模块 38 | 39 | ## 历史更新 40 | 41 |
42 | 说明 43 | 44 | **2022-10-12** 45 | 46 | 1. 重写画图,指令 `score` 和 `bp` 的逻辑 47 | 2. 修复acc画图内存泄漏的问题 48 | 3. 新增指令 `pr`,查询最近的游玩pass记录 49 | 4. 新增指令 `tr`,查询当天游玩Pass的谱面 50 | 5. 查询成绩不再下载整个地图谱面,只下载单一谱面且不保存,节省时间和空间 51 | 6. 再次修改PP计算器api地址及PP请求源码,现已提供api使用说明:[PP计算器API](https://sakura.yuzuai.xyz/API/osu!.html) 52 | 53 | **2022-01-07** 54 | 55 | 1. 修改PP计算器api地址及PP请求源码,现已提供api使用说明:[PP计算器API](https://sakura.yuzuai.xyz/API/osu!.html) 56 | 57 | **2021-11-22** 58 | 59 | 1. 修复更新token失败时一直重复更新导致死循环的问题 60 | 61 | **2021-11-19** 62 | 63 | 1. 修复`update`指令无法更改查询模式的问题 64 | 2. 修复`pfm`指令查询个别玩家bp不足100个的问题 65 | 3. 修复新版本pp计算器返回结果不一致的问题 66 | 67 | **2021-11-11** 68 | 69 | 1. 新增`tbp`指令,查询当天新增的BP成绩,时间范围为当天的00:00到23:59 70 | 2. 修复`pfm`指令无法查询TA的问题 71 | 3. `token`的更新方式已修改为检测到`token`过期时自动更新,不在定时更新,手动更新的指令保留 72 | 4. 优化不存在的用户的返回结果 73 | 74 | **2021-10-17** 75 | 76 | 1. 修复`info`指令无法查询未游玩过的模式 77 | 78 | **2021-10-16** 79 | 80 | 1. 修改数据库结构 81 | 3. 修改`token`请求,不再重复读取文件 82 | 4. 修改图片发送方式为`base64`编码,不再保存输出图片,以节省空间 83 | 5. 修改`bp`指令的参数 84 | 6. 修改std成绩图片,新增`aim`,`acc`,`speed`,`if fc`,`if ss` pp的计算 85 | 7. 移除`bp`指令查询指定范围成绩的功能,修改为单独的指令`pfm` 86 | 8. 移除`smap`指令,不再支持搜索地图 87 | 9. 不再保存头像和头图,.osu文件,仅保留背景图片 88 | 10. 新增全模式PP计算,提供api接口,无文档 89 | 11. 新增指令`pfm`指令,查询指定范围成绩 90 | 91 | **2021-08-06** 92 | 93 | 1. 修复图版本获取失败的问题 94 | 95 | **2021-07-29** 96 | 97 | 1. 更新新版渐变色难度模式图标 98 | 99 | **2021-07-09** 100 | 101 | 1. 修复ctb和taiko模式`pp`变量错误 102 | 103 | **2021-07-08** 104 | 105 | 1. 修复无法自动更新个人信息的问题 106 | 107 | **2021-07-06** 108 | 109 | 1. 数据处理移动到`data.py` 110 | 2. 修改部分函数名称 111 | 3. 不再保存cover及badges图片 112 | 4. 修改所有成绩图 113 | 114 | **2021-06-11** 115 | 116 | 1. 为区分图组与单图,已将图组`bmapid`修改为`setid` 117 | 2. 修复地图在没有背景制图错误的问题 118 | 3. 修复`get_token.py`无法申请token的问题 119 | 4. 修改报错提示 120 | 5. 将`draw.py`文件的`FILEHTTP`字符串移至`http.py` 121 | 122 | **2021-05-29** 123 | 124 | 1. 新增查询图组功能,指令:`bmap [bmapid]`,`bmapid`为图组id,或者使用地图id`mapid`查询图组,在指令加`-b`,例:`bmap -b [mapid]` 125 | 2. 修改`map`指令,并增加音乐分享,音乐分享需使用`http`服务,请自行修改`draw.py`文件中的`FILEHTTP`字符串,将地址改为自己的服务器IP或域名,`:{PORT}/map`请勿删除 126 | 3. 完善api请求,准确返回无法查询的错误 127 | 4. 修复所有指令无法查询TA人的问题 128 | 5. 修复指令@人无法查询的问题 129 | 130 | **2021-05-23** 131 | 132 | 1. 修改`bp`指令,改用图片的形式发送 133 | 2. `info`,`recent`,`score`指令可使用@方式查询群友的成绩或信息 134 | 135 | **2021-05-16** 136 | 137 | 1. 修改`map`指令,改用图片的形式发送 138 | 2. 修复`smap`指令搜索个别ranked地图没有背景的问题 139 | 140 | **2021-05-06** 141 | 142 | 1. 新增地图搜索功能,指令:`[smap keyword]`,`keyword`为关键词,可多个,默认搜索std模式ranked状态 143 | 2. 新增地图下载功能,指令:`[osudl bmapid]`,`bmapid`为地图组id,非单图id 144 | 145 | **2021-05-04** 146 | 147 | 1. 支持`mania`模式pp计算 148 | 149 | **2021-05-02** 150 | 151 | 1. 修复`map`指令查询非ranked图时出错的问题 152 | 2. 修复`map`指令查询mania图时无max combo的问题 153 | 154 | **2021-04-27** 155 | 156 | 1. 修复撒泼特错位 157 | 2. 修复info的游玩时间 158 | 3. 对比信息无法更新 159 | 160 | **2021-04-23** 161 | 162 | 1. 船新版本的osu插件 163 | 164 |
165 | 166 | ## License 167 | 168 | MIT -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from hoshino.service import Service, priv 5 | 6 | # 路径 7 | Root = Path(__file__).parent 8 | static = Root / 'static' 9 | OAuthJson = static / 'OAuth.json' 10 | OsuDataBase = static / 'osu.db' 11 | MeiryoR = static / 'fonts' / 'Meiryo Regular.ttf' 12 | MeiryoS = static / 'fonts' / 'Meiryo SemiBold.ttf' 13 | TrFont = static / 'fonts' / 'Torus Regular.otf' 14 | TsFont = static / 'fonts' / 'Torus SemiBold.otf' 15 | Venera = static / 'fonts' / 'Venera.otf' 16 | RankDir = static / 'ranking' 17 | MapDir = static / 'map' 18 | WorkDir = static / 'work' 19 | FlagsDir = static / 'flags' 20 | ModsDir = static / 'mods' 21 | bgImg = [ static / f'pfm_{mode}.png' for mode in ['std', 'taiko', 'ctb', 'mania'] ] 22 | pfmbg = static / 'Best Performance.png' 23 | 24 | 25 | # OAuth 26 | OAuth = json.load(open(OAuthJson, 'r', encoding='utf-8')) 27 | client_id = OAuth['client_id'] 28 | client_secret = OAuth['client_secret'] 29 | 30 | 31 | # 常用变量 32 | GameModeNum = {0 : 'Std', 1 : 'Taiko', 2 : 'Ctb', 3 : 'Mania'} 33 | GameMode = {0 : 'osu', 1 : 'taiko', 2 : 'fruits', 3 : 'mania'} 34 | GameModeName = {'osu' : 'Std', 'taiko' : 'Taiko', 'fruits' : 'Ctb', 'mania' : 'Mania'} 35 | FGameMode = {value: key for key, value in GameMode.items()} 36 | Mod = { 37 | '0' : 'NO', 38 | '1' : 'NF', 39 | '2' : 'EZ', 40 | '4' : 'TD', 41 | '8' : 'HD', 42 | '16' : 'HR', 43 | '32' : 'SD', 44 | '64' : 'DT', 45 | '128' : 'RX', 46 | '256' : 'HT', 47 | '576' : 'NC', 48 | '1024' : 'FL', 49 | '2048' : 'AT', 50 | '4096' : 'SO', 51 | '8192' : 'RX2', 52 | '16384' : 'PF', 53 | '32768' : '4K', 54 | '65536' : '5K', 55 | '131072' : '6K', 56 | '262144' : '7K', 57 | '524288' : '8K', 58 | '1048576' : 'FI', 59 | '2097152' : 'RD', 60 | '4194304' : 'CI', 61 | '8388608' : 'TG', 62 | '16777216' : '9K', 63 | '33554432' : 'KC', 64 | '67108864' : '1K', 65 | '134217728' : '3K', 66 | '268435456' : '2K', 67 | '536870912' : 'V2', 68 | '1073741824': 'MR', 69 | } 70 | NewMod = {value: key for key, value in Mod.items()} 71 | CropSize = { 72 | 'BG': [1500, 360], 73 | 'Header': [1000, 400], 74 | 'MapBG': [1200, 600], 75 | 'BMapBG': [1200, 300] 76 | } 77 | 78 | sv = Service('osuv2', manage_priv=priv.ADMIN, enable_on_default=True) -------------------------------------------------------------------------------- /osu.py: -------------------------------------------------------------------------------- 1 | import re 2 | import traceback 3 | from typing import List 4 | 5 | from hoshino.typing import CQEvent 6 | from nonebot import NoneBot 7 | 8 | from . import * 9 | from .src import update_daily_data 10 | from .src.DataBase import delete_user, get_user_data, insert_user, update_user_mode 11 | from .src.Error import * 12 | from .src.Picture import * 13 | 14 | info_ali = ['info', 'Info', 'INFO'] 15 | recent_ali = ['recent', 're', 'RECENT', 'RE', 'Recent', 'Re'] 16 | pass_recent_ali = ['passrecent', 'pr', 'Pr', 'PR'] 17 | tbp_ali = ['tbp', 'Tbp', 'TBP', 'todaybp', 'Todaybp', 'TODAYBP'] 18 | score_ali = ['score', 'SCORE', 'Score'] 19 | bp_ali = ['bp', 'Bp', 'BP'] 20 | tr_ali = ['tr', 'Tr', 'TR', 'todayre', 'Todayre', 'TODAYRE', 'todayrecent', 'Todayrecent', 'TODAYRECENT'] 21 | 22 | 23 | @sv.on_prefix(info_ali) 24 | @sv.on_prefix(recent_ali) 25 | @sv.on_prefix(pass_recent_ali) 26 | @sv.on_prefix(tbp_ali) 27 | @sv.on_prefix(tr_ali) 28 | async def _(bot: NoneBot, ev: CQEvent): 29 | try: 30 | qqid = ev.user_id 31 | cmd: str = ev.prefix.lower() 32 | text: str = ev.message.extract_plain_text().strip() 33 | for message in ev.message: 34 | if message.type == 'at': 35 | qqid = int(message.data['qq']) 36 | 37 | mode = 0 38 | pattern = re.compile(r'^([A-Za-z0-9\s_-]+)?\s?[::]?([0-3]+)?$') 39 | match = pattern.search(text) 40 | if match.group(1): 41 | user_id = match.group(1).strip() 42 | else: 43 | user = get_user_data(qqid) 44 | user_id = user.Osuid 45 | mode = user.Osumode 46 | if match.group(2): 47 | mode = int(match.group(2).strip()) 48 | 49 | if cmd in info_ali: 50 | data = await draw_info(user_id, GameMode[mode]) 51 | elif cmd in recent_ali: 52 | data = await draw_score('recent', user_id, GameMode[mode]) 53 | elif cmd in pass_recent_ali: 54 | data = await draw_score('passrecent', user_id, GameMode[mode]) 55 | elif cmd in tbp_ali: 56 | data = await best_pfm('tbp', user_id, GameMode[mode]) 57 | else: 58 | data = await best_pfm('tr', user_id, GameMode[mode]) 59 | except UserNotBindError as e: 60 | data = str(e) 61 | except Exception as e: 62 | sv.logger.error(traceback.format_exc()) 63 | data = type(e) 64 | 65 | await bot.send(ev, data) 66 | 67 | 68 | def mods2list(args: str) -> List[str]: 69 | tempmods = '' 70 | mods = [] 71 | for n, v in enumerate(args): 72 | if n % 2 == 1: 73 | if (m := tempmods + v.upper()) not in NewMod: 74 | raise ModsError(f'无法识别Mods -> [{m}]') 75 | mods.append(m) 76 | else: 77 | tempmods = v.upper() 78 | return mods 79 | 80 | 81 | @sv.on_prefix(score_ali) 82 | @sv.on_prefix(bp_ali) 83 | async def _(bot: NoneBot, ev: CQEvent): 84 | try: 85 | qqid = ev.user_id 86 | cmd: str = ev.prefix.lower() 87 | text = ev.message.extract_plain_text().strip().split() 88 | if not text: 89 | if cmd is score_ali: 90 | await bot.send(ev, '请输入正确的地图ID') 91 | else: 92 | await bot.send(ev, '请输入正确的bp') 93 | return 94 | for message in ev.message: 95 | if message.type == 'at': 96 | qqid = int(message.data['qq']) 97 | 98 | mode = 0 99 | mods = None 100 | pattern = re.compile( 101 | r'^([A-Za-z0-9\s_-]+)?\s([0-9]+)\s?[::]?([0-9])?\s?\+?([A-Za-z]+)?') 102 | match = pattern.search(ev.message.extract_plain_text().strip()) 103 | if match: 104 | if _u := match.group(1): 105 | user_id = _u 106 | else: 107 | user = get_user_data(qqid) 108 | user_id = user.Osuid 109 | 110 | map_best = int(match.group(2)) 111 | if _m := match.group(3): 112 | mode = int(_m) 113 | if _ms := match.group(4): 114 | mods = mods2list(_ms) 115 | elif match2 := pattern.search(' ' + ev.message.extract_plain_text()): 116 | user_id = get_user_data(qqid).Osuid 117 | map_best = int(match2.group(2)) 118 | 119 | if _m := match2.group(3): 120 | mode = int(_m) 121 | if _ms := match2.group(4): 122 | mods = mods2list(_ms) 123 | else: 124 | raise UserEnterError 125 | 126 | if cmd is score_ali: 127 | data = await draw_score('score', user_id, GameMode[mode], mapid=map_best, mods=mods) 128 | if cmd is bp_ali: 129 | if map_best <= 0 or map_best > 100: 130 | await bot.send(ev, '只允许查询 1-100 的成绩') 131 | return 132 | data = await draw_score('bp', user_id, GameMode[mode], best=map_best, mods=mods) 133 | except UserNotBindError as e: 134 | data = str(e) 135 | except UserEnterError as e: 136 | data = str(e) 137 | except ModsError as e: 138 | data = e.value 139 | except Exception as e: 140 | sv.logger.error(traceback.format_exc()) 141 | data = type(e) 142 | await bot.send(ev, data) 143 | 144 | 145 | @sv.on_prefix(['pfm', 'Pfm', 'PFM']) 146 | async def _(bot: NoneBot, ev: CQEvent): 147 | try: 148 | qqid = ev.user_id 149 | args = ev.message.extract_plain_text().strip() 150 | if not args: 151 | await bot.finish(ev, '请输入正确的参数') 152 | for message in args: 153 | if message.type == 'at': 154 | qqid = int(message.data['qq']) 155 | 156 | pattern = re.compile( 157 | r'^([A-Za-z0-9]+)?\s?([0-9]+)-([0-9]+)\s?([::][0-9])?\s?(\+[A-Za-z]+)?') 158 | match = pattern.search(ev.message.extract_plain_text()) 159 | 160 | user_id = None 161 | mode = 0 162 | mods = None 163 | 164 | min = match.group(2) 165 | max = match.group(3) 166 | if not min and not max: 167 | await bot.finish(ev, '请输入正确的bp范围') 168 | else: 169 | min = int(min) 170 | max = int(max) 171 | if min > 100 or max > 100: 172 | await bot.finish(ev, '只允许查询bp 1-100 的成绩') 173 | if min >= max: 174 | await bot.finish(ev, '请输入正确的bp范围') 175 | 176 | if _u := match.group(1): 177 | user_id = _u 178 | mode = 0 179 | else: 180 | user = get_user_data(qqid) 181 | user_id = user.Osuid 182 | mode = user.Osumode 183 | if _m := match.group(4): 184 | mode = int(_m[1:]) 185 | if mode < 0 and mode > 3: 186 | await bot.finish(ev, '请输入正确的模式') 187 | if _ms := match.group(5): 188 | mods = mods2list(_ms) 189 | 190 | data = await best_pfm('pfm', user_id, GameMode[mode], min, max, mods) 191 | except UserNotBindError as e: 192 | data = str(e) 193 | except UserEnterError as e: 194 | data = str(e) 195 | except ModsError as e: 196 | data = e.value 197 | except Exception as e: 198 | sv.logger.error(traceback.format_exc()) 199 | data = type(e) 200 | await bot.send(ev, data) 201 | 202 | 203 | @sv.on_prefix(['map', 'Map', 'MAP']) 204 | async def osumap(bot: NoneBot, ev: CQEvent): 205 | try: 206 | mapid: str = ev.message.extract_plain_text().strip() 207 | mods = [] 208 | if not mapid: 209 | await bot.finish(ev, '请输入地图ID') 210 | elif not mapid[0].isdigit(): 211 | await bot.finish(ev, '请输入正确的地图ID') 212 | if '+' in mapid[-1]: 213 | mods = mods2list(mapid[-1][1:]) 214 | del mapid[-1] 215 | data = await draw_map(mapid[0], mods) 216 | except ModsError as e: 217 | info = e.value 218 | except Exception as e: 219 | sv.logger.error(traceback.format_exc()) 220 | info = type(e) 221 | await bot.send(ev, info) 222 | 223 | 224 | @sv.on_prefix(['bmap', 'Bmap', 'BMAP']) 225 | async def osubmap(bot: NoneBot, ev: CQEvent): 226 | args: str = ev.message.extract_plain_text().strip().split() 227 | if not args: 228 | await bot.finish(ev, '请输入地图ID') 229 | op = False 230 | if len(args) == 1: 231 | if not args[0].isdigit(): 232 | await bot.finish(ev, '请输入正确的地图ID') 233 | setid = args[0] 234 | elif args[0] == '-b': 235 | if not args[1].isdigit(): 236 | await bot.finish(ev, '请输入正确的地图ID') 237 | op = True 238 | setid = args[1] 239 | else: 240 | await bot.finish(ev, '请输入正确的地图ID') 241 | info = await draw_beatmap(setid, op) 242 | await bot.send(ev, info) 243 | 244 | 245 | @sv.on_prefix(['bind', 'Bind', 'BIND']) 246 | async def osubind(bot: NoneBot, ev: CQEvent): 247 | qqid = ev.user_id 248 | osuid = ev.message.extract_plain_text().strip() 249 | if not osuid: 250 | await bot.finish(ev, '请输入您的 osuid', at_sender=True) 251 | if get_user_data(qqid): 252 | msg = '您已绑定,如需要解绑请输入 !unbind' 253 | else: 254 | data = await osuApi.user(osuid) 255 | if data: 256 | user = User(**data) 257 | _in = insert_user(qqid, user) 258 | if _in: 259 | msg = f'用户 {user.username} 已成功绑定QQ {qqid}' 260 | else: 261 | msg = '数据库错误' 262 | else: 263 | msg = '未找到玩家' 264 | await bot.send(ev, msg, at_sender=True) 265 | 266 | 267 | @sv.on_prefix(['unbind', 'Unbind', 'UNBIND']) 268 | async def osuunbind(bot: NoneBot, ev: CQEvent): 269 | try: 270 | qqid = ev.user_id 271 | get_user_data(qqid) 272 | deluser = delete_user(qqid) 273 | if deluser: 274 | data = '解绑成功' 275 | else: 276 | data = '数据库错误' 277 | except UserNotBindError: 278 | data = '玩家尚未绑定,无需解绑' 279 | await bot.send(ev, data, at_sender=True) 280 | 281 | 282 | @sv.on_prefix(['update', 'Update', 'UPDATE']) 283 | async def osuupdate(bot: NoneBot, ev: CQEvent): 284 | try: 285 | qqid = ev.user_id 286 | args: str = ev.message.extract_plain_text().strip().split() 287 | get_user_data(qqid) 288 | if not args: 289 | data = '请输入需要更新内容的参数' 290 | elif args[0] == 'mode': 291 | try: 292 | mode = int(args[1]) 293 | except: 294 | await bot.finish('请输入更改的模式!') 295 | if mode >= 0 or mode < 4: 296 | result = update_user_mode(qqid, mode) 297 | if result: 298 | data = f'已将默认模式更改为 {GameModeName[mode]}' 299 | else: 300 | data = '数据库错误' 301 | else: 302 | data = '请输入正确的模式 0-3' 303 | else: 304 | data = '参数错误,请输入正确的参数' 305 | except UserNotBindError as e: 306 | data = str(e) 307 | await bot.send(data) 308 | 309 | 310 | @sv.on_prefix(['osuhelp', 'Osuhelp', 'OSUHELP']) 311 | async def osuhelp(bot: NoneBot, ev: CQEvent): 312 | await bot.send(ev, MessageSegment.image(f'file:///{static / "help.png"}')) 313 | 314 | 315 | @sv.scheduled_job('cron', hour='0', minute='0') 316 | async def update_info(): 317 | num = await update_daily_data() 318 | sv.logger.info(f'已更新 {num} 位玩家数据') -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.8.5 2 | pillow>=10.0.0 3 | matplotlib>=3.7.3 4 | requests_oauthlib>=1.3.1 -------------------------------------------------------------------------------- /src/DataBase.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from typing import List, Optional 3 | 4 | from peewee import * 5 | 6 | from .. import OsuDataBase 7 | from .Error import * 8 | from .Model import User as _User 9 | 10 | db = SqliteDatabase(OsuDataBase) 11 | 12 | 13 | class OsuModel(Model): 14 | class Meta: 15 | database = db 16 | 17 | 18 | class User(OsuModel): 19 | ID = IntegerField(primary_key=True) 20 | Qqid = BigIntegerField() 21 | Osuid = BigIntegerField() 22 | Osuname = TextField() 23 | Osumode = IntegerField() 24 | 25 | class Meta: 26 | table_name = 'User' 27 | 28 | 29 | class ModeData(OsuModel): 30 | Osuid = BigIntegerField() 31 | CountryRanked = BigIntegerField() 32 | GlobalRanked = BigIntegerField() 33 | Pp = FloatField() 34 | Accuracy = FloatField() 35 | PlayCount = BigIntegerField() 36 | Hit = BigIntegerField() 37 | DateTime = DateTimeField(formats='%Y-%m-%d', default=datetime.date(datetime.now() + timedelta(days=1))) 38 | 39 | 40 | class StdMode(ModeData): 41 | class Meta: 42 | table_name = 'Std' 43 | 44 | 45 | class TaikoMode(ModeData): 46 | class Meta: 47 | table_name = 'Taiko' 48 | 49 | 50 | class CtbMode(ModeData): 51 | class Meta: 52 | table_name = 'Ctb' 53 | 54 | 55 | class ManiaMode(ModeData): 56 | class Meta: 57 | table_name = 'Mania' 58 | 59 | 60 | DatabaseTable = [User, StdMode, TaikoMode, CtbMode, ManiaMode] 61 | ModeTable: List[ModeData] = DatabaseTable[1:] 62 | 63 | 64 | db.create_tables(DatabaseTable) 65 | 66 | 67 | def get_user_data(qqid: int) -> Optional[User]: 68 | record: Select = User.select().where(User.Qqid == qqid) 69 | if record: 70 | return record.get() 71 | else: 72 | raise UserNotBindError 73 | 74 | 75 | def get_all_user_osuid() -> List[User]: 76 | return list(User.select(User.Osuid)) 77 | 78 | 79 | def get_user_daily_data(osuid: int, day: int = 0, mode: int = 0) -> Optional[ModeData]: 80 | time = datetime.date(datetime.now() - timedelta(days=day)) 81 | table = ModeTable[mode] 82 | record: Select = table.select().where((table.Osuid == osuid) & (table.DateTime == time)) 83 | if record: 84 | return record.get() 85 | return None 86 | 87 | 88 | def insert_user(qqid: int, user: _User) -> bool: 89 | try: 90 | User.insert(Qqid=qqid, Osuid=user.id, Osuname=user.username, Osumode=user.play_mode).execute() 91 | return True 92 | except: 93 | return False 94 | 95 | 96 | def insert_user_daily_data(mode: int, data: List[_User]) -> bool: 97 | try: 98 | table = ModeTable[mode] 99 | dbdata = [(d.id, 100 | d.statistics.country_rank if d.statistics.country_rank else 0, 101 | d.statistics.global_rank if d.statistics.global_rank else 0, 102 | d.statistics.pp if d.statistics.pp else 0, 103 | d.statistics.accuracy if d.statistics.accuracy else 0, 104 | d.statistics.play_count if d.statistics.play_count else 0, 105 | d.statistics.total_hits if d.statistics.total_hits else 0) for d in data] 106 | with db.atomic(): 107 | for i in range(0, len(data), 100): 108 | table.insert_many(dbdata[i:i + 100], ['Osuid', 'CountryRanked', 'GlobalRanked', 'Pp', 'Accuracy', 'PlayCount', 'Hit']).execute() 109 | return True 110 | except: 111 | return False 112 | 113 | 114 | def update_user_mode(qqid: int, mode: int) -> bool: 115 | try: 116 | User.update(User.Osumode == mode).where(User.Qqid == qqid) 117 | return True 118 | except: 119 | return False 120 | 121 | 122 | def delete_user(qqid: int) -> bool: 123 | try: 124 | User.delete().where(User.Qqid == qqid).execute() 125 | return True 126 | except: 127 | return False -------------------------------------------------------------------------------- /src/Model/BeatmapModel.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class Covers(BaseModel): 7 | 8 | cover: str 9 | cover2x: str = Field(alias='cover@2x') 10 | card: str 11 | card2x: str = Field(alias='card@2x') 12 | list: str 13 | list2x: str = Field(alias='list@2x') 14 | slimcover: str 15 | slimcover2x: str = Field(alias='slimcover@2x') 16 | 17 | 18 | class BeatmapSet(BaseModel): 19 | 20 | artist: str 21 | artist_unicode: Optional[str] 22 | covers: Covers 23 | creator: str 24 | favourite_count: int 25 | id: int 26 | play_count: int 27 | preview_url: str 28 | source: str 29 | title: str 30 | title_unicode: Optional[str] 31 | user_id: int 32 | ranked: Optional[int] 33 | ranked_date: Optional[str] = None 34 | status: str 35 | 36 | 37 | class Beatmap(BaseModel): 38 | 39 | beatmapset_id: int 40 | difficulty_rating: float 41 | id: int 42 | total_length: int 43 | version: str 44 | accuracy: float 45 | ar: float 46 | bpm: int 47 | count_circles: int 48 | count_sliders: int 49 | count_spinners: int 50 | cs: int 51 | drain: int 52 | hit_length: int 53 | mode_int: int 54 | ranked: int 55 | url: str 56 | beatmapset: Optional[BeatmapSet] 57 | max_combo: Optional[int] = 0 58 | -------------------------------------------------------------------------------- /src/Model/PPModel.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class PP(BaseModel): 7 | 8 | StarRating: float 9 | HP: float 10 | CS: float 11 | Aim: float 12 | Speed: float 13 | AR: Optional[float] 14 | OD: Optional[float] 15 | aim: Optional[float] 16 | speed: Optional[float] 17 | accuracy: Optional[float] 18 | pp: float 19 | ifpp: float 20 | sspp: float -------------------------------------------------------------------------------- /src/Model/SayomapModel.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class BidData(BaseModel): 7 | 8 | AR: float 9 | CS: float 10 | HP: float 11 | OD: float 12 | aim: float 13 | bid: int 14 | circles: int 15 | length: int 16 | maxcombo: int 17 | mode: int 18 | pp: int 19 | pp_acc: float 20 | pp_aim: float 21 | pp_speed: float 22 | sliders: int 23 | speed: float 24 | star: float 25 | version: str 26 | 27 | 28 | class Sayomap(BaseModel): 29 | 30 | approved_date: int 31 | artist: str 32 | artistU: Optional[str] 33 | bid_data: List[BidData] 34 | bids_amount: int 35 | bpm: float 36 | creator: str 37 | creator_id: int 38 | sid: int 39 | source: Optional[str] 40 | title: str 41 | titleU: Optional[str] 42 | -------------------------------------------------------------------------------- /src/Model/ScoreModel.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | from pydantic import BaseModel 4 | 5 | from .BeatmapModel import Beatmap, BeatmapSet 6 | from .UserModel import User 7 | 8 | 9 | class Statistics(BaseModel): 10 | 11 | count_100: int 12 | count_300: int 13 | count_50: int 14 | count_geki: int 15 | count_katu: int 16 | count_miss: int 17 | 18 | 19 | class Weight(BaseModel): 20 | 21 | percentage: float 22 | pp: float 23 | 24 | 25 | class Score(BaseModel): 26 | 27 | accuracy: float 28 | best_id: Optional[int] 29 | created_at: Optional[str] 30 | max_combo: Optional[int] 31 | mode: Optional[str] 32 | mode_int: int 33 | mods: List[Optional[str]] 34 | pp: float = -1 35 | rank: Optional[str] 36 | score: Optional[int] 37 | statistics: Statistics 38 | beatmap: Beatmap 39 | beatmapset: Optional[BeatmapSet] 40 | user: Optional[User] 41 | weight: Optional[Weight] 42 | 43 | 44 | class BestScore(BaseModel): 45 | 46 | position: Union[int, str] 47 | score: Score -------------------------------------------------------------------------------- /src/Model/UserModel.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class Level(BaseModel): 7 | 8 | current: int = 0 9 | progress: int = 0 10 | 11 | 12 | class GradeCounts(BaseModel): 13 | 14 | ss: int 15 | ssh: int 16 | s: int 17 | sh: int 18 | a: int 19 | 20 | 21 | class Variants(BaseModel): 22 | 23 | mode: str 24 | variant: str 25 | country_rank: int 26 | global_rank: int 27 | pp: float 28 | 29 | 30 | class Statistics(BaseModel): 31 | 32 | level: Level 33 | pp: Optional[float] = 0 34 | global_rank: Optional[int] = 0 35 | ranked_score: Optional[int] = 0 36 | accuracy: Optional[float] = Field(alias='hit_accuracy') 37 | play_count: Optional[int] = 0 38 | play_time: Optional[int] = 0 39 | total_score: Optional[int] = 0 40 | total_hits: Optional[int] = 0 41 | maximum_combo: Optional[int] = 0 42 | is_ranked: bool 43 | grade_counts: GradeCounts 44 | country_rank: int 45 | variants: Optional[List[Variants]] 46 | 47 | 48 | class UserAchievements(BaseModel): 49 | 50 | achieved_at: str 51 | achievement_id: int 52 | 53 | 54 | class History(BaseModel): 55 | 56 | mode: str 57 | data: List[int] 58 | 59 | 60 | class User(BaseModel): 61 | 62 | avatar_url: str 63 | country_code: str 64 | id: int 65 | is_online: bool 66 | is_supporter: bool 67 | last_visit: str 68 | username: str 69 | cover_url: Optional[str] = '' 70 | join_date: Optional[str] = '' 71 | play_mode: Optional[int] = 0 72 | statistics: Optional[Statistics] 73 | user_achievements: Optional[List[UserAchievements]] 74 | rankHistory: Optional[History] 75 | -------------------------------------------------------------------------------- /src/Model/__init__.py: -------------------------------------------------------------------------------- 1 | from .BeatmapModel import * 2 | from .PPModel import * 3 | from .SayomapModel import * 4 | from .ScoreModel import * 5 | from .UserModel import * 6 | -------------------------------------------------------------------------------- /src/Picture/Image.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import math 3 | from datetime import datetime, timedelta 4 | from io import BytesIO 5 | from pathlib import Path 6 | from time import localtime, strftime 7 | from typing import Tuple, Union 8 | 9 | from PIL import Image, ImageDraw, ImageFont 10 | 11 | from ... import CropSize, GameModeNum, WorkDir 12 | 13 | 14 | class DrawText: 15 | 16 | def __init__(self, image: ImageDraw.ImageDraw, font: Path) -> None: 17 | self._img = image 18 | self._font = str(font) 19 | 20 | def get_box(self, text: str, size: int): 21 | return ImageFont.truetype(self._font, size).getbbox(text) 22 | 23 | def draw(self, 24 | pos_x: int, 25 | pos_y: int, 26 | size: int, 27 | text: str, 28 | color: Tuple[int, int, int, int] = (255, 255, 255, 255), 29 | anchor: str = 'lt', 30 | stroke_width: int = 0, 31 | stroke_fill: Tuple[int, int, int, int] = (0, 0, 0, 0), 32 | multiline: bool = False): 33 | 34 | font = ImageFont.truetype(self._font, size) 35 | if multiline: 36 | self._img.multiline_text((pos_x, pos_y), str(text), color, font, anchor, stroke_width=stroke_width, stroke_fill=stroke_fill) 37 | else: 38 | self._img.text((pos_x, pos_y), str(text), color, font, anchor, stroke_width=stroke_width, stroke_fill=stroke_fill) 39 | 40 | def draw_partial_opacity(self, 41 | pos_x: int, 42 | pos_y: int, 43 | size: int, 44 | text: str, 45 | po: int = 2, 46 | color: Tuple[int, int, int, int] = (255, 255, 255, 255), 47 | anchor: str = 'lt', 48 | stroke_width: int = 0, 49 | stroke_fill: Tuple[int, int, int, int] = (0, 0, 0, 0)): 50 | 51 | font = ImageFont.truetype(self._font, size) 52 | self._img.text((pos_x + po, pos_y + po), str(text), (0, 0, 0, 128), font, anchor, stroke_width=stroke_width, stroke_fill=stroke_fill) 53 | self._img.text((pos_x, pos_y), str(text), color, font, anchor, stroke_width=stroke_width, stroke_fill=stroke_fill) 54 | 55 | 56 | def draw_filler(img: Image.Image, radii: int) -> Image.Image: 57 | # 画圆(用于分离4个角) 58 | circle = Image.new('L', (radii * 2, radii * 2), 0) # 创建一个黑色背景的画布 59 | draw = ImageDraw.Draw(circle) 60 | draw.ellipse((0, 0, radii * 2, radii * 2), fill=255) # 画白色圆形 61 | # 原图 62 | img = img.convert("RGBA") 63 | w, h = img.size 64 | # 画4个角(将整圆分离为4个部分) 65 | alpha = Image.new('L', img.size, 255) 66 | alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0)) # 左上角 67 | alpha.paste(circle.crop((radii, 0, radii * 2, radii)), (w - radii, 0)) # 右上角 68 | alpha.paste(circle.crop((radii, radii, radii * 2, radii * 2)), (w - radii, h - radii)) # 右下角 69 | alpha.paste(circle.crop((0, radii, radii, radii * 2)), (0, h - radii)) # 左下角 70 | # 白色区域透明可见,黑色区域不可见 71 | img.putalpha(alpha) 72 | return img 73 | 74 | 75 | def cropBG(size: str, path: Union[str, BytesIO]) -> Image.Image: 76 | bg = Image.open(path).convert('RGBA') 77 | bg_w, bg_h = bg.size[0], bg.size[1] 78 | fix_w, fix_h = CropSize[size] 79 | fix_scale = fix_h / fix_w 80 | bg_scale = bg_h / bg_w 81 | 82 | if bg_scale > fix_scale: 83 | scale_width = fix_w / bg_w 84 | width = int(scale_width * bg_w) 85 | height = int(scale_width * bg_h) 86 | sf = bg.resize((width, height)) 87 | crop_height = (height - fix_h) / 2 88 | x1, y1, x2, y2 = 0, crop_height, width, height - crop_height 89 | crop_img = sf.crop((x1, y1, x2, y2)) 90 | 91 | elif bg_scale < fix_scale: 92 | scale_height = fix_h / bg_h 93 | width = int(scale_height * bg_w) 94 | height = int(scale_height * bg_h) 95 | sf = bg.resize((width, height)) 96 | crop_width = (width - fix_w) / 2 97 | x1, y1, x2, y2 = crop_width, 0, width - crop_width, height 98 | crop_img = sf.crop((x1, y1, x2, y2)) 99 | 100 | else: 101 | crop_img = bg.resize((fix_w, fix_h)) 102 | 103 | return crop_img 104 | 105 | 106 | def starsDiff(mode: Union[int, str], stars: float) -> Image.Image: 107 | if mode in GameModeNum: 108 | mode = GameModeNum[mode].lower() 109 | else: 110 | mode = 'stars' 111 | default = 115 112 | if stars < 1: 113 | xp = 0 114 | default = 120 115 | elif stars < 2: 116 | xp = 120 117 | default = 120 118 | elif stars < 3: 119 | xp = 240 120 | elif stars < 4: 121 | xp = 355 122 | elif stars < 5: 123 | xp = 470 124 | elif stars < 6: 125 | xp = 585 126 | elif stars < 7: 127 | xp = 700 128 | elif stars < 8: 129 | xp = 815 130 | else: 131 | return Image.open(WorkDir / f'{mode}_expertplus.png').convert('RGBA') 132 | # 取色 133 | x = (stars - math.floor(stars)) * default + xp 134 | color = Image.open(WorkDir / 'color.png').load() 135 | r, g, b = color[x, 1] 136 | # 打开底图 137 | im = Image.open(WorkDir / f'{mode}.png').convert('RGBA') 138 | xx, yy = im.size 139 | # 填充背景 140 | sm = Image.new('RGBA', im.size, (r, g, b)) 141 | sm.paste(im, (0, 0, xx, yy), im) 142 | # 把白色变透明 143 | for i in range(xx): 144 | for z in range(yy): 145 | data = sm.getpixel((i, z)) 146 | if (data.count(255) == 4): 147 | sm.putpixel((i, z), (255, 255, 255, 0)) 148 | return sm 149 | 150 | 151 | def strtime(time: str) -> str: 152 | old_time = datetime.strptime(time, '%Y-%m-%dT%H:%M:%SZ') 153 | new_time = (old_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') 154 | return new_time 155 | 156 | 157 | def playtime(date: int) -> str: 158 | return strftime('%Y-%m-%d %H:%M:%S', localtime(date / 1000)) 159 | 160 | 161 | def calc_songlen(len: int) -> str: 162 | map_len = list(divmod(int(len), 60)) 163 | map_len[1] = map_len[1] if map_len[1] >= 10 else f'0{map_len[1]}' 164 | music_len = f'{map_len[0]}:{map_len[1]}' 165 | return music_len 166 | 167 | 168 | def img2b64(img: Image.Image) -> str: 169 | bytes = BytesIO() 170 | img.save(bytes, 'PNG') 171 | base64_str = base64.b64encode(bytes.getvalue()).decode() 172 | return 'base64://' + base64_str 173 | -------------------------------------------------------------------------------- /src/Picture/Info.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from time import time 3 | from typing import List 4 | 5 | from nonebot import MessageSegment 6 | from PIL import Image, ImageDraw 7 | from pydantic import BaseModel 8 | 9 | from ... import * 10 | from ..Api import osuApi 11 | from ..DataBase import get_user_daily_data 12 | from ..Error import DrawImageError 13 | from ..File import getImage 14 | from ..Model import User 15 | from .Image import * 16 | 17 | 18 | class DrawInfo: 19 | 20 | def __init__(self, userInfo: User, day: int) -> None: 21 | self.user = userInfo 22 | self.score = userInfo.statistics 23 | self.day = day 24 | 25 | def scoreCalc(self, value: List[Union[int, float, bool]]) -> Tuple[str, Union[int, float]]: 26 | num = value[0] - value[1] 27 | if num < 0: 28 | if value[2]: 29 | op, v = '↑', num * -1 30 | elif value[3]: 31 | op, v = '↓', num * -1 32 | else: 33 | op, v = '-', num * -1 34 | elif num > 0: 35 | if value[2]: 36 | op, v = '↓', num 37 | elif value[3]: 38 | op, v = '↑', num 39 | else: 40 | op, v = '+', num 41 | else: 42 | op, v = '', 0 43 | return (op, v) 44 | 45 | 46 | class Calc(BaseModel): 47 | 48 | op: str 49 | value: Union[int, float] 50 | 51 | 52 | def _calc(self, value: Union[int, float], localvalue: Union[int, float], set1: bool, set2: bool) -> Calc: 53 | newvalue = value - localvalue 54 | if newvalue < 0: 55 | v = newvalue * -1 56 | if set1: 57 | op = '↑' 58 | elif set2: 59 | op = '↓' 60 | else: 61 | op = '-' 62 | elif newvalue > 0: 63 | v = newvalue 64 | if set1: 65 | op = '↓' 66 | elif set2: 67 | op = '↑' 68 | else: 69 | op = '+' 70 | else: 71 | op, v = '', newvalue 72 | return self.Calc(op=op, value=v) 73 | 74 | 75 | async def draw(self): 76 | try: 77 | country = FlagsDir / f'{self.user.country_code}.png' 78 | info_BG = static / 'info.png' 79 | supporter_BG = WorkDir / 'suppoter.png' 80 | exp_l = WorkDir / 'left.png' 81 | exp_c = WorkDir / 'center.png' 82 | exp_r = WorkDir / 'right.png' 83 | 84 | info_img = Image.open(info_BG).convert('RGBA') 85 | user_header_img = cropBG('Header', await getImage(self.user.cover_url)) 86 | user_icon_img = Image.open(await getImage(self.user.avatar_url)).convert('RGBA').resize((300, 300)) 87 | country_img = Image.open(country).convert('RGBA').resize((80, 54)) 88 | supporter_img = Image.open(supporter_BG).convert('RGBA').resize((54, 54)) 89 | exp_left_img = Image.open(exp_l).convert('RGBA') 90 | exp_width = self.user.statistics.level.progress * 7 - 3 91 | exp_center_img = Image.open(exp_c).convert('RGBA').resize((exp_width, 10)) 92 | exp_right_img = Image.open(exp_r).convert('RGBA') 93 | 94 | userData = get_user_daily_data(self.user.id, self.day, self.user.play_mode) 95 | if userData: 96 | crank, grank, _pp, accuracy, playcount, hit_count = userData.CountryRanked, userData.GlobalRanked, userData.Pp, userData.Accuracy, userData.PlayCount, userData.Hit 97 | else: 98 | crank, grank, _pp, accuracy, playcount, hit_count = self.score.country_rank, self.score.global_rank, self.score.pp, self.score.accuracy, self.score.play_count, self.score.total_hits 99 | 100 | cr = self._calc(self.score.country_rank, crank, True, False) 101 | gr = self._calc(self.score.global_rank, grank, True, False) 102 | pp = self._calc(self.score.pp, _pp, False, True) 103 | acc = self._calc(self.score.accuracy, accuracy, False, False) 104 | pc = self._calc(self.score.play_count, playcount, False, False) 105 | hit = self._calc(self.score.total_hits, hit_count, False, False) 106 | 107 | im = Image.new('RGBA', (1000, 1322)) 108 | text_im = ImageDraw.Draw(im) 109 | trfont = DrawText(text_im, TrFont) 110 | mrfont = DrawText(text_im, MeiryoR) 111 | # 头图 112 | im.alpha_composite(user_header_img, (0, 100)) 113 | # 底图 114 | im.alpha_composite(info_img) 115 | # 头像 116 | user_icon = draw_filler(user_icon_img, 25) 117 | im.alpha_composite(user_icon, (50, 148)) 118 | # 地区 119 | im.alpha_composite(country_img, (400, 394)) 120 | # supporter 121 | if self.user.is_supporter: 122 | im.alpha_composite(supporter_img, (400, 280)) 123 | # 经验 124 | if self.user.statistics.level.progress != 0: 125 | im.alpha_composite(exp_left_img, (50, 646)) 126 | im.alpha_composite(exp_center_img, (54, 646)) 127 | im.alpha_composite(exp_right_img, (int(54 + exp_width), 646)) 128 | # 模式 129 | trfont.draw(935, 50, 45, GameModeNum[self.user.play_mode], anchor='rm') 130 | # 名字 131 | trfont.draw(400, 205, 50, self.user.username, anchor='lm') 132 | # 地区排名 133 | mrfont.draw(495, 448, 30, f'#{self.score.country_rank:,}({cr.op}{cr.value:,})' if cr.value != 0 else f'#{self.score.country_rank:,}', anchor='lb') 134 | # 等级 135 | trfont.draw(900, 650, 25, self.score.level.current, anchor='mm') 136 | # 经验百分比 137 | trfont.draw(750, 660, 20, f'{self.score.level.progress}%', anchor='rt') 138 | # 全球排名 139 | trfont.draw(55, 785, 35, f'#{self.score.global_rank:,}') 140 | if gr.value != 0: 141 | mrfont.draw(65, 820, 20, f'{gr.op}{gr.value:,}') 142 | # pp 143 | trfont.draw(295, 785, 35, f'{self.score.pp:,}') 144 | if pp.value != 0: 145 | mrfont.draw(305, 820, 20, f'{pp.op}{self.score.pp:.2f}') 146 | # SS - A 147 | for gc_num, v in enumerate(self.score.grade_counts.dict().values()): 148 | trfont.draw(493 + 100 * gc_num, 775, 30, f'{v:,}', anchor='mt') 149 | # ranked总分 150 | trfont.draw(935, 895, 40, f'{self.score.ranked_score:,}', anchor='rt') 151 | # acc 152 | trfont.draw(935, 965, 40, f'{self.score.accuracy:.2f}%({acc.op}{acc.value:.2f}%)' if acc.value != 0 else f'{self.score.accuracy:.2f}%', anchor='rt') 153 | # 游玩次数 154 | trfont.draw(935, 1035, 40, f'{self.score.play_count:,}({pc.op}{pc.value:,})' if pc.value != 0 else f'{self.score.play_count:,}', anchor='rt') 155 | # 总分 156 | trfont.draw(935, 1105, 40, f'{self.score.total_score:,}', anchor='rt') 157 | # 总命中 158 | trfont.draw(935, 1175, 40, f'{self.score.total_hits:,}({hit.op}{hit.value:,})' if hit.value != 0 else f'{self.score.total_hits:,}', anchor='rt') 159 | # 游玩时间 160 | trfont.draw(935, 1245, 40, f'{self.score.play_time:,}', anchor='rt') 161 | 162 | return im 163 | except Exception as e: 164 | sv.logger.error(f'制图错误:{traceback.format_exc()}') 165 | raise DrawImageError(type(e)) 166 | 167 | 168 | async def draw_info(user_id: Union[int, str], mode: str, day: int = 1) -> Union[str, MessageSegment]: 169 | try: 170 | sv.logger.info(f'Start Request OsuAPI {playtime(time() * 1000)}') 171 | UserInfo = await osuApi.user(user_id, mode=mode) 172 | sv.logger.info(f'Ending Request OsuAPI {playtime(time() * 1000)}') 173 | if not UserInfo: 174 | return '未查询到该玩家' 175 | userData = User(**UserInfo) 176 | 177 | if not userData.statistics.play_count: 178 | return f'该玩家尚未游玩过{GameModeName[mode]}模式' 179 | 180 | data = DrawInfo(userData, day) 181 | im = await data.draw() 182 | # 输出 183 | msg = MessageSegment.image(img2b64(im)) 184 | except DrawImageError as e: 185 | sv.logger.error(f'制图错误:{traceback.format_exc()}\n类型:{e.value}') 186 | msg = f'Error: {e.value}' 187 | except Exception as e: 188 | sv.logger.error(f'制图错误:{traceback.format_exc()}') 189 | msg = f'Error: {type(e)}' 190 | return msg -------------------------------------------------------------------------------- /src/Picture/Map.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from typing import List 3 | 4 | from nonebot import MessageSegment 5 | from PIL import ImageEnhance, ImageFilter 6 | 7 | from ... import * 8 | from ..Api import osuApi 9 | from ..File import getImage 10 | from ..Model import Beatmap, Sayomap 11 | from ..Model.ScoreModel import Score, Statistics 12 | from ..PP import PPCalc 13 | from .Image import * 14 | 15 | 16 | async def draw_map(mapid: int, mods: List[str]): 17 | try: 18 | info = await osuApi.beatmap(mapid) 19 | if not info: 20 | return '未查询到该地图信息' 21 | if isinstance(info, str): 22 | return info 23 | map = Beatmap(**info) 24 | diffinfo = calc_songlen(map.total_length), map.bpm, map.count_circles, map.count_sliders 25 | # pp 26 | pp = await PPCalc(Score( 27 | accuracy=1, 28 | max_combo=map.max_combo, 29 | mode_int=map.mode_int, 30 | mods=mods, 31 | statistics=Statistics( 32 | count_100=0, 33 | count_300=0, 34 | count_50=0, 35 | count_geki=0, 36 | count_katu=0, 37 | count_miss=0, 38 | ), 39 | beatmap=map 40 | )).calc(isPlay=False) 41 | 42 | # 计算时间 43 | if map.beatmapset.ranked_date: 44 | new_time = strtime(map.beatmapset.ranked_date) 45 | else: 46 | new_time = '??-??-?? ??:??:??' 47 | 48 | bg = static / 'beatmapinfo.png' 49 | cover = await getImage(map.beatmapset.covers.cover2x) 50 | user_icon = await getImage(f'https://a.ppy.sh/{map.beatmapset.user_id}') 51 | 52 | bg_img = Image.open(bg).convert('RGBA') 53 | cover_img = ImageEnhance.Brightness(cropBG('MapBG', cover)).enhance(2 / 4.0) 54 | mode_img = starsDiff(map.mode_int, round(pp.StarRating, 2)).resize((50, 50)) 55 | icon = Image.open(user_icon).convert('RGBA').resize((100, 100)) 56 | 57 | # BG做地图 58 | im = Image.new('RGBA', (1200, 600)) 59 | im.alpha_composite(cover_img) 60 | # 字体 61 | text_im = ImageDraw.Draw(im) 62 | trfont = DrawText(text_im, TrFont) 63 | tsfont = DrawText(text_im, TsFont) 64 | msfont = DrawText(text_im, MeiryoS) 65 | # 获取地图info 66 | im.alpha_composite(bg_img) 67 | # 模式 68 | im.alpha_composite(mode_img, (50, 100)) 69 | # cs - diff 70 | if map.mode_int == 0: 71 | mapdiff = [round(map.cs, 1), round(map.drain, 1), round(pp.OD, 1), round(pp.AR, 1), round(pp.StarRating, 2)] 72 | else: 73 | mapdiff = [round(map.cs, 1), round(map.drain, 1), round(map.accuracy, 1), round(map.ar, 1), round(map.difficulty_rating, 2)] 74 | for num, i in enumerate(mapdiff): 75 | color = (255, 255, 255, 255) 76 | if num == 4: 77 | color = (255, 204, 34, 255) 78 | difflen = int(250 * i / 10) if i <= 10 else 250 79 | diff_len = Image.new('RGBA', (difflen, 8), color) 80 | im.alpha_composite(diff_len, (890, 426 + 35 * num)) 81 | tsfont.draw(1170, 426 + 35 * num, 20, i, anchor='mm') 82 | # mapper 83 | im.alpha_composite(icon, (50, 400)) 84 | # mapid 85 | tsfont.draw(800, 40, 22, f'Setid: {map.beatmapset_id} | Mapid: {mapid}', anchor='lm') 86 | # 版本 87 | tsfont.draw(120, 125, 25, map.version, anchor='lm') 88 | # 曲名 89 | msfont.draw(50, 170, 30, map.beatmapset.title_unicode or map.beatmapset.title) 90 | # 曲师 91 | msfont.draw(50, 210, 25, f'by {map.beatmapset.artist_unicode or map.beatmapset.artist}') 92 | # 来源 93 | msfont.draw(50, 260, 25, f'Source: {map.beatmapset.source}') 94 | # mapper 95 | tsfont.draw(160, 400, 20, 'mapper by:') 96 | tsfont.draw(160, 425, 20, map.beatmapset.creator) 97 | # ranked时间 98 | tsfont.draw(160, 460, 20, 'ranked by:') 99 | tsfont.draw(160, 485, 20, new_time) 100 | # 状态 101 | tsfont.draw(1100, 304, 20, map.beatmapset.status.capitalize(), anchor='mm') 102 | # 时长 - 滑条 103 | for num, i in enumerate(diffinfo): 104 | trfont.draw(770 + 120 * num, 365, 20, i, (255, 204, 34, 255), anchor='lm') 105 | # maxcb 106 | tsfont.draw(50, 570, 20, f'Max Combo: {map.max_combo}', anchor='lm') 107 | # pp 108 | tsfont.draw(320, 570, 20, f'SS PP: {round(pp.pp)}', anchor='lm') 109 | # 输出 110 | 111 | msg = MessageSegment.image(img2b64(im)) 112 | except Exception as e: 113 | sv.logger.error(f'制图错误:{traceback.print_exc()}') 114 | msg = f'Error: {type(e)}' 115 | return msg 116 | 117 | 118 | async def draw_beatmap(mapid: int, op: bool=False) -> Union[str, MessageSegment]: 119 | if op: 120 | info = await osuApi.beatmap(mapid) 121 | if not info: 122 | return '未查询到地图' 123 | elif isinstance(info, str): 124 | return info 125 | mapid = info['beatmapset_id'] 126 | 127 | info = await osuApi.sayomap(mapid) 128 | if info['status'] == -1: 129 | return '未查询到地图' 130 | elif isinstance(info, str): 131 | return info 132 | 133 | try: 134 | map = Sayomap(**info['data']) 135 | cover = await getImage(f'https://assets.ppy.sh/beatmaps/{mapid}/covers/cover@2x.jpg') 136 | # 作图 137 | if (count := map.bids_amount) > 20: 138 | im_h = 400 + 102 * 20 139 | else: 140 | im_h = 400 + 102 * count if count < 5 else 5 141 | im = Image.new('RGBA', (1200, im_h), (31, 41, 46, 255)) 142 | # 字体 143 | text_im = ImageDraw.Draw(im) 144 | tsfont = DrawText(text_im, TsFont) 145 | msfont = DrawText(text_im, MeiryoS) 146 | 147 | # 背景 148 | cover_crop = cropBG('BMapBG', cover) 149 | cover_gb = cover_crop.filter(ImageFilter.GaussianBlur(1)) 150 | cover_img = ImageEnhance.Brightness(cover_gb).enhance(2 / 4.0) 151 | 152 | # 分割线 153 | div = Image.new('RGBA', (1150, 2), (46, 53, 56, 255)).convert('RGBA') 154 | im.alpha_composite(cover_img, (0, 0)) 155 | # 曲名 156 | msfont.draw(25, 40, 38, map.titleU or map.title) 157 | # 曲师 158 | msfont.draw(25, 75, 20, f'by {map.artistU or map.artist}') 159 | # mapper 160 | tsfont.draw(25, 110, 20, f'mapper by {map.creator}') 161 | # rank时间 162 | if map.approved_date == -1: 163 | time = '??-??-?? ??:??:??' 164 | else: 165 | datearray = datetime.utcfromtimestamp(map.approved_date) 166 | time = (datearray + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') 167 | tsfont.draw(25, 145, 20, f'Approved Time: {time}') 168 | # 来源 169 | msfont.draw(25, 180, 20, f'Source: {map.source}') 170 | # bpm 171 | tsfont.draw(1150, 110, 20, f'BPM: {map.bpm}', anchor='rt') 172 | # 曲长 173 | # music_len = calc_songlen(map) 174 | # tsfont.draw(1150, 145, 20, f'lenght: {music_len}', anchor='rt') 175 | # Setid 176 | tsfont.draw(1150, 20, 20, f'Setid: {map.sid}', anchor='rt') 177 | 178 | bar_img = Image.open(WorkDir / 'bmap.png').convert('RGBA') 179 | 180 | maplist = sorted(map.bid_data, key=lambda x: x.star, reverse=False) 181 | for num, beatmap in enumerate(maplist): 182 | if num < 20: 183 | h_num = 102 * num 184 | # 难度 185 | mode_bg = starsDiff(beatmap.mode, beatmap.star) 186 | mode_img = mode_bg.resize((20, 20)) 187 | im.alpha_composite(mode_img, (20, 320 + h_num)) 188 | # 星星 189 | stars_bg = starsDiff('stars', beatmap.star) 190 | stars_img = stars_bg.resize((20, 20)) 191 | im.alpha_composite(stars_img, (50, 320 + h_num)) 192 | # diff 193 | im.alpha_composite(bar_img, (10, 365 + h_num)) 194 | gc = ['CS', 'HP', 'OD', 'AR'] 195 | for num, i in enumerate([beatmap.CS, beatmap.HP, beatmap.OD, beatmap.AR]): 196 | diff_len = int(200 * i / 10) if i <= 10 else 200 197 | diff_bg = Image.new('RGBA', (diff_len, 12), (255, 255, 255, 255)) 198 | im.alpha_composite(diff_bg, (50 + 300 * num, 365 + h_num)) 199 | tsfont.draw(20 + 300 * num, 369 + h_num, 20, gc[num], anchor='lm') 200 | tsfont.draw(265 + 300 * num, 369 + h_num, 20, i, (255, 204, 34, 255), anchor='lm') 201 | if num != 3: 202 | tsfont.draw(300 + 300 * num, 369 + h_num, 20, '|', anchor='lm') 203 | # 难度 204 | tsfont.draw(80, 328 + h_num, 20, beatmap.star, anchor='lm') 205 | # version 206 | tsfont.draw(125, 328 + h_num, 20, f' | {beatmap.version}', anchor='lm') 207 | # mapid 208 | tsfont.draw(1150, 328 + h_num, 20, f'Mapid: {beatmap.bid}', anchor='rm') 209 | # maxcb 210 | tsfont.draw(700, 328 + h_num, 20, f'Max Combo: {beatmap.maxcombo}', anchor='lm') 211 | # 分割线 212 | im.alpha_composite(div, (25, 400 + h_num)) 213 | else: 214 | plusnum = f'+ {num - 19}' 215 | if num >= 20: 216 | tsfont.draw(600, 350 + 102 * 20, 50, plusnum, anchor='mm') 217 | 218 | msg = MessageSegment.image(img2b64(im)) 219 | except Exception as e: 220 | sv.logger.error(f'制图错误:{traceback.print_exc()}') 221 | msg = f'Error: {type(e)}' 222 | return msg -------------------------------------------------------------------------------- /src/Picture/Performance.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from datetime import datetime, timedelta 3 | from time import mktime, strptime, time 4 | from typing import List 5 | 6 | from nonebot import MessageSegment 7 | from PIL import Image, ImageDraw 8 | 9 | from ... import * 10 | from ..Api import osuApi 11 | from ..Error import DrawImageError 12 | from ..Model import Score 13 | from ..Mods import Mods 14 | from .Image import * 15 | 16 | 17 | class DrawPerformance: 18 | 19 | def __init__(self, project: str, bplist: List[Score], setBpList: Union[List[int], range]) -> None: 20 | self.Project = project 21 | self.BPList = bplist 22 | self.setList = setBpList 23 | self.Username = bplist[0].user.username 24 | self.Mode = bplist[0].mode 25 | 26 | 27 | def draw_pfm(self) -> Image.Image: 28 | try: 29 | bg_img = Image.open(pfmbg).convert('RGBA') 30 | f_div = Image.new('RGBA', (1500, 2), (255, 255, 255, 255)).convert('RGBA') 31 | div = Image.new('RGBA', (1450, 2), (46, 53, 56, 255)).convert('RGBA') 32 | 33 | # 作图 34 | im = Image.new('RGBA', (1500, 180 + 82 * (len(self.setList) - 1)), (31, 41, 46, 255)) 35 | # 字体 36 | text_im = ImageDraw.Draw(im) 37 | trfont = DrawText(text_im, TrFont) 38 | tsfont = DrawText(text_im, TsFont) 39 | mrfont = DrawText(text_im, MeiryoR) 40 | # 底图 41 | im.alpha_composite(bg_img) 42 | # 分割线 43 | im.alpha_composite(f_div, (0, 100)) 44 | if self.Project == 'pfm': 45 | uinfo = f"{self.Username}'s | {self.Mode.capitalize()} | BP {self.setList.start + 1} - {self.setList.stop}" 46 | elif self.Project == 'tbp': 47 | uinfo = f"{self.Username}'s | {self.Mode.capitalize()} | Today New BP" 48 | else: 49 | uinfo = f"{self.Username}'s | {self.Mode.capitalize()} | Today Recent Plays" 50 | tsfont.draw(1450, 50, 25, uinfo, anchor='rm') 51 | for num, bp in enumerate(self.setList): 52 | h_num = 82 * num 53 | 54 | score = self.BPList[bp] 55 | # mods 56 | if score.mods: 57 | for mods_num, s_mods in enumerate(score.mods): 58 | mods_bg = ModsDir / f'{s_mods}.png' 59 | mods_img = Image.open(mods_bg).convert('RGBA') 60 | im.alpha_composite(mods_img, (1000 + 50 * mods_num, 126 + h_num)) 61 | if (score.rank == 'X' or score.rank == 'S') and ('HD' in score.mods or 'FL' in score.mods): 62 | score.rank += 'H' 63 | # BP排名 64 | if self.Project != 'tr': 65 | mrfont.draw(20, 143 + h_num, 20, bp + 1, anchor='mm') 66 | # rank 67 | rank_img = RankDir / f'ranking-{score.rank}.png' 68 | rank_bg = Image.open(rank_img).convert('RGBA').resize((64, 32)) 69 | im.alpha_composite(rank_bg, (45, 128 + h_num)) 70 | # 曲名&作曲 71 | mrfont.draw(125, 130 + h_num, 20, f'{score.beatmapset.title} | by {score.beatmapset.artist}', anchor='lm') 72 | # 地图版本 73 | x = trfont.get_box(score.beatmap.version, 18) 74 | trfont.draw(125, 158 + h_num, 18, score.beatmap.version, (238, 171, 0, 255), anchor='lm') 75 | # 时间 76 | trfont.draw(125 + x[2], 158 + h_num, 18, f' | {strtime(score.created_at)}', anchor='lm') 77 | # acc 78 | tsfont.draw(1250, 130 + h_num, 22, f'{score.accuracy * 100:.2f}%', (238, 171, 0, 255), anchor='lm') 79 | # mapid 80 | trfont.draw(1250, 158 + h_num, 18, f'ID: {score.beatmap.id}', anchor='lm') 81 | # pp 82 | tsfont.draw(1420, 140 + h_num, 25, round(score.pp) if score.pp != -1 else '--', (255, 102, 171, 255), anchor='rm') 83 | tsfont.draw(1450, 140 + h_num, 25, 'pp', (209, 148, 176, 255), anchor='rm') 84 | # 分割线 85 | im.alpha_composite(div, (25, 180 + h_num)) 86 | 87 | return im 88 | except Exception as e: 89 | sv.logger.error(f'制图错误:{traceback.print_exc()}') 90 | raise DrawImageError(type(e)) 91 | 92 | 93 | def findTodayBp(scoreList: List[Score]) -> List[int]: 94 | tempList = [] 95 | for index, value in enumerate(scoreList): 96 | today = datetime.now().date() 97 | today_stamp = mktime(strptime(str(today), '%Y-%m-%d')) 98 | playtime = datetime.strptime(value.created_at, '%Y-%m-%dT%H:%M:%SZ') + timedelta(hours=8) 99 | play_stamp = mktime(strptime(str(playtime), '%Y-%m-%d %H:%M:%S')) 100 | 101 | if play_stamp > today_stamp: 102 | tempList.append(index) 103 | 104 | return tempList 105 | 106 | 107 | async def best_pfm(project: str, user_id: Union[str, int], mode: str, min: int = 0, max: int = 0, mods: List[str] = None) -> Union[str, MessageSegment]: 108 | try: 109 | sv.logger.info(f'Start Request OsuAPI {playtime(time() * 1000)}') 110 | BPInfo = await osuApi.user_scores_best(user_id, mode=mode, limit=100) 111 | sv.logger.info(f'Ending Request OsuAPI {playtime(time() * 1000)}') 112 | if not BPInfo: 113 | return '未查询到游玩记录' 114 | 115 | BPList: List[Score] = [] 116 | for score in BPInfo: 117 | BPList.append(Score(**score)) 118 | 119 | if project == 'pfm': 120 | isr = False 121 | _range = None 122 | if min != 0 and max != 0: 123 | """指定范围""" 124 | _range = range(min, max) 125 | setBpList = _range 126 | isr = True 127 | if mods: 128 | indexList = Mods(BPList, mods).findModsIndex() 129 | if not indexList: 130 | return f'未找到 {"|".join(mods)} Mods的成绩' 131 | if isr and len(_range) < len(indexList): 132 | setBpList = indexList[min - 1, max] 133 | elif isr and len(_range) > len(indexList): 134 | setBpList = indexList[min:] 135 | else: 136 | setBpList = indexList 137 | elif project == 'tbp': 138 | setBpList = findTodayBp(BPList) 139 | if not setBpList: 140 | return f'今天没有新增的BP成绩' 141 | elif project == 'tr': 142 | setBpList = range(len(BPList)) 143 | 144 | info = DrawPerformance(project, BPList, setBpList) 145 | im = info.draw_pfm() 146 | 147 | msg = MessageSegment.image(img2b64(im)) 148 | except Exception as e: 149 | sv.logger.error(f'制图错误:{traceback.print_exc()}') 150 | msg = f'Error: {type(e)}' 151 | return msg -------------------------------------------------------------------------------- /src/Picture/Score.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import traceback 3 | from time import time 4 | from typing import List, Optional 5 | 6 | from matplotlib.figure import Figure 7 | from nonebot import MessageSegment 8 | from PIL import Image, ImageDraw, ImageEnhance, ImageFilter 9 | 10 | from ... import * 11 | from ..Api import osuApi 12 | from ..Error import DrawImageError 13 | from ..File import * 14 | from ..Model import Beatmap, BestScore, Score, User 15 | from ..PP import * 16 | from .Image import * 17 | 18 | 19 | class DrawScore: 20 | 21 | def __init__(self, project: str, scoreData: Union[Score, BestScore]) -> None: 22 | self.Project = project 23 | if self.Project == 'score': 24 | self.GRank = scoreData.position 25 | self.score: Score = scoreData.score 26 | self.user: User = scoreData.score.user 27 | else: 28 | self.score: Score = scoreData 29 | self.user: User = scoreData.user 30 | self.beatmap = self.score.beatmap 31 | 32 | 33 | def loadMap(self, map: Beatmap) -> None: 34 | """ 使用 `score` 指令时需要另外获取地图数据 """ 35 | self.beatmap = map 36 | 37 | 38 | def _wedgeAcc(self) -> BytesIO: 39 | size = [self.score.accuracy * 100, 100 - self.score.accuracy * 100] 40 | insize = [60, 20, 7, 7, 5, 1] 41 | insizecolor = ['#ff5858', '#ea7948', '#d99d03', '#72c904', '#0096a2', '#be0089'] 42 | fig = Figure() 43 | ax = fig.add_axes((0.1, 0.1, 0.8, 0.8)) 44 | patches = ax.pie(size, radius=1.1, startangle=90, counterclock=False, pctdistance=0.9, wedgeprops=dict(width=0.27)) 45 | ax.pie(insize, radius=0.8, colors=insizecolor, startangle=90, counterclock=False, pctdistance=0.9, wedgeprops=dict(width=0.05)) 46 | patches[0][1].set_alpha(0) 47 | img = BytesIO() 48 | fig.savefig(img, transparent=True) 49 | ax.cla() 50 | ax.clear() 51 | fig.clf() 52 | fig.clear() 53 | gc.collect() 54 | return img 55 | 56 | 57 | async def draw(self) -> Image.Image: 58 | try: 59 | if self.Project != 'score': 60 | self.GRank = '--' 61 | 62 | ppcalc = PPCalc(self.score) 63 | pp = await ppcalc.calc() 64 | 65 | if self.score.mode_int != 0: 66 | od = round(self.beatmap.accuracy, 1) 67 | ar = round(self.beatmap.ar, 1) 68 | self.stars = self.beatmap.difficulty_rating 69 | else: 70 | od = round(pp.OD, 1) 71 | ar = round(pp.AR, 1) 72 | self.stars = round(pp.StarRating, 2) 73 | 74 | if 'HD' in self.score.mods or 'FL' in self.score.mods: 75 | ranking = ['XH', 'SH', 'A', 'B', 'C', 'D', 'F'] 76 | else: 77 | ranking = ['X', 'S', 'A', 'B', 'C', 'D', 'F'] 78 | 79 | cover = await getImage(self.beatmap.beatmapset.covers.cover2x) 80 | cover_img = ImageEnhance.Brightness(cropBG('BG', cover).filter(ImageFilter.GaussianBlur(1))).enhance(2 / 4.0) 81 | bg_img = Image.open(bgImg[self.score.mode_int]).convert('RGBA') 82 | mode_img = starsDiff(self.score.mode_int, self.stars).resize((30, 30)) 83 | stars_img = starsDiff(self.score.mode_int, self.stars).resize((23, 23)) 84 | score_acc_img = Image.open(self._wedgeAcc()).convert('RGBA').resize((576, 432)) 85 | icon = Image.open(await getImage(self.user.avatar_url)).convert('RGBA').resize((150, 150)) 86 | icon_img = draw_filler(icon, 25) 87 | country_img = Image.open(FlagsDir / f'{self.user.country_code}.png').convert('RGBA').resize((58, 39)) 88 | status_img = Image.open(WorkDir / 'on-line.png' if self.user.is_online else 'off-line.png').convert('RGBA').resize((45, 45)) 89 | supporter_img = Image.open(WorkDir / 'suppoter.png').convert('RGBA').resize((40, 40)) 90 | 91 | # 作图 92 | im = Image.new('RGBA', (1500, 800)) 93 | # 字体 94 | text_im = ImageDraw.Draw(im) 95 | trfont = DrawText(text_im, TrFont) 96 | tsfont = DrawText(text_im, TsFont) 97 | msfont = DrawText(text_im, MeiryoS) 98 | vfont = DrawText(text_im, Venera) 99 | # 曲绘 100 | im.alpha_composite(cover_img, (0, 200)) 101 | # 底图 102 | im.alpha_composite(bg_img) 103 | # 模式 104 | im.alpha_composite(mode_img, (75, 154)) 105 | # 星级 106 | im.alpha_composite(stars_img, (134, 158)) 107 | # Mods 108 | if self.score.mods: 109 | for num, s in enumerate(self.score.mods): 110 | mods_img = Image.open(ModsDir / f'{s}.png').convert('RGBA') 111 | im.alpha_composite(mods_img, (500 + 50 * num, 240)) 112 | # 游玩评价 113 | rank_ok = False 114 | for num, i in enumerate(ranking): 115 | rank_img = RankDir / f'ranking-{i}.png' 116 | rank_b = Image.open(rank_img).resize((48, 24)) 117 | if rank_ok: 118 | rank_new = Image.new('RGBA', rank_b.size, (0, 0, 0, 0)) 119 | rank_bg = Image.blend(rank_new, rank_b, 0.5) 120 | elif i != self.score.rank: 121 | rank_new = Image.new('RGBA', rank_b.size, (0, 0, 0, 0)) 122 | rank_bg = Image.blend(rank_new, rank_b, 0.2) 123 | else: 124 | rank_bg = rank_b 125 | rank_ok = True 126 | im.alpha_composite(rank_bg, (75, 243 + 39 * num)) 127 | # Acc作图 128 | im.alpha_composite(score_acc_img, (15, 153)) 129 | # 头像 130 | im.alpha_composite(icon_img, (50, 606)) 131 | # 地区 132 | im.alpha_composite(country_img, (230, 606)) 133 | # 状态 134 | im.alpha_composite(status_img, (230, 712)) 135 | # supporter 136 | if self.user.is_supporter: 137 | im.alpha_composite(supporter_img, (300, 606)) 138 | # 地图难度 139 | if self.score.mode_int == 0: 140 | mapdiff = [self.beatmap.cs, self.beatmap.drain, od, ar, self.stars] 141 | else: 142 | mapdiff = [self.beatmap.cs, self.beatmap.drain, self.beatmap.accuracy, self.beatmap.ar, self.stars] 143 | for num, i in enumerate(mapdiff): 144 | color = (255, 204, 34, 255) if num == 4 else (255, 255, 255, 255) 145 | len = int(250 * i / 10) if i <= 10 else 250 146 | im.alpha_composite(Image.new('RGBA', (len, 8), color), (1190, 386 + 35 * num)) 147 | tsfont.draw(1470, 386 + 35 * num, 20, i, anchor='mm') 148 | # 地图数据 149 | for num, i in enumerate([calc_songlen(self.beatmap.total_length), self.beatmap.bpm, self.beatmap.count_circles, self.beatmap.count_sliders]): 150 | trfont.draw(1070 + 120 * num, 325, 20, i, (255, 204, 34, 255), anchor='lm') 151 | # 地图状态 152 | tsfont.draw(1400, 264, 20, self.beatmap.beatmapset.status.capitalize(), anchor='mm') 153 | # ID 154 | tsfont.draw(1425, 40, 27, f'Setid: {self.beatmap.beatmapset_id} | Mapid: {self.beatmap.id}', anchor='rm') 155 | # 标题和曲师 156 | msfont.draw(75, 118, 30, f'{self.beatmap.beatmapset.title_unicode} | by {self.beatmap.beatmapset.artist_unicode}', anchor='lm') 157 | # 星数 158 | tsfont.draw(162, 169, 18, self.stars, anchor='lm') 159 | # 版本 160 | tsfont.draw(225, 169, 22, f'{self.beatmap.version} | mapper by {self.beatmap.beatmapset.creator}', anchor='lm') 161 | # 评价 162 | vfont.draw(309, 375, 75, self.score.rank, anchor='mm') 163 | # 分数 164 | trfont.draw(498, 331, 75, f'{self.score.score:,}', anchor='lm') 165 | # 玩家名 166 | tsfont.draw(498, 396, 18, 'Played by:', anchor='lm') 167 | tsfont.draw(630, 396, 18, self.user.username, anchor='lm') 168 | # 游玩时间 169 | tsfont.draw(498, 421, 18, 'Submitted on:', anchor='lm') 170 | tsfont.draw(630, 421, 18, strtime(self.score.created_at), anchor='lm') 171 | # 全球排名 172 | tsfont.draw(513, 496, 24, self.GRank, anchor='lm') 173 | # 玩家名 174 | tsfont.draw(230, 670, 24, self.user.username, anchor='lm') 175 | # 在线状态 176 | tsfont.draw(300, 732, 30, 'online' if self.user.is_online else 'offline', anchor='lm') 177 | # 游玩信息 178 | if self.score.mode_int == 0: 179 | trfont.draw(650, 625, 30, round(pp.sspp), anchor='mm') 180 | trfont.draw(770, 625, 30, round(pp.ifpp), anchor='mm') 181 | trfont.draw(890, 625, 30, round(pp.pp), anchor='mm') 182 | trfont.draw(650, 720, 30, round(pp.aim), anchor='mm') 183 | trfont.draw(770, 720, 30, round(pp.speed), anchor='mm') 184 | trfont.draw(890, 720, 30, round(pp.accuracy), anchor='mm') 185 | trfont.draw(1087, 625, 30, f'{self.score.accuracy * 100:.2f}%', anchor='mm') 186 | trfont.draw(1315, 625, 30, f'{self.score.max_combo:,}/{self.beatmap.max_combo:,}', anchor='mm') 187 | trfont.draw(1030, 720, 30, self.score.statistics.count_300, anchor='mm') 188 | trfont.draw(1144, 720, 30, self.score.statistics.count_100, anchor='mm') 189 | trfont.draw(1258, 720, 30, self.score.statistics.count_50, anchor='mm') 190 | trfont.draw(1372, 720, 30, self.score.statistics.count_miss, anchor='mm') 191 | elif self.score.mode_int == 1: 192 | trfont.draw(1050, 625, 30, f'{self.score.accuracy * 100:.2f}%', anchor='mm') 193 | trfont.draw(1202, 625, 30, f'{self.score.max_combo:,}/{self.beatmap.max_combo:,}', anchor='mm') 194 | trfont.draw(1352, 625, 30, f'{round(pp.pp)}/{round(pp.ifpp)}', anchor='mm') 195 | trfont.draw(1050, 720, 30, self.score.statistics.count_300, anchor='mm') 196 | trfont.draw(1202, 720, 30, self.score.statistics.count_100, anchor='mm') 197 | trfont.draw(1352, 720, 30, self.score.statistics.count_miss, anchor='mm') 198 | elif self.score.mode_int == 2: 199 | trfont.draw(1016, 625, 30, f'{self.score.accuracy * 100:.2f}%', anchor='mm') 200 | trfont.draw(1180, 625, 30, f'{self.score.max_combo:,}/{self.beatmap.max_combo:,}', anchor='mm') 201 | trfont.draw(1344, 625, 30, f'{round(pp.pp)}/{round(pp.ifpp)}', anchor='mm') 202 | trfont.draw(995, 720, 30, self.score.statistics.count_300, anchor='mm') 203 | trfont.draw(1118, 720, 30, self.score.statistics.count_100, anchor='mm') 204 | trfont.draw(1242, 720, 30, self.score.statistics.count_katu, anchor='mm') 205 | trfont.draw(1365, 720, 30, self.score.statistics.count_miss, anchor='mm') 206 | else: 207 | trfont.draw(935, 625, 30, f'{self.score.accuracy * 100:.2f}%', anchor='mm') 208 | trfont.draw(1130, 625, 30, f'{self.score.max_combo:,}', anchor='mm') 209 | trfont.draw(1328, 625, 30, f'{round(pp.pp)}/{round(pp.ifpp)}', anchor='mm') 210 | trfont.draw(886, 720, 30, self.score.statistics.count_geki, anchor='mm') 211 | trfont.draw(984, 720, 30, self.score.statistics.count_300, anchor='mm') 212 | trfont.draw(1083, 720, 30, self.score.statistics.count_katu, anchor='mm') 213 | trfont.draw(1182, 720, 30, self.score.statistics.count_100, anchor='mm') 214 | trfont.draw(1280, 720, 30, self.score.statistics.count_50, anchor='mm') 215 | trfont.draw(1378, 720, 30, self.score.statistics.count_miss, anchor='mm') 216 | 217 | return im 218 | except PPError as e: 219 | raise PPError 220 | except Exception as e: 221 | sv.logger.error(f'制图错误:{traceback.print_exc()}') 222 | raise DrawImageError(type(e)) 223 | 224 | async def draw_score(project: str, user_id: str, mode: str, *, mapid: Optional[int] = 0, best: Optional[int] = 0, mods: List[str] = None) -> Union[str, MessageSegment]: 225 | try: 226 | sv.logger.info(f'Start Request OsuAPI {playtime(time() * 1000)}') 227 | if project == 'recent' or project == 'passrecent': 228 | fails = 1 if project == 'recent' else 0 229 | data = await osuApi.user_recent(user_id, mode=mode, include_fails=fails) 230 | scoreData = Score(**data[best]) 231 | elif project == 'score': 232 | data = await osuApi.user_scores(user_id, mapid, mode=mode, mods=mods) 233 | scoreData = BestScore(**data) 234 | else: 235 | data = await osuApi.user_scores_best(user_id, mode=mode, limit=100) 236 | scoreData = Score(**data[best - 1]) 237 | 238 | ds = DrawScore(project, scoreData) 239 | mapJson = await osuApi.beatmap(ds.beatmap.id) 240 | ds.loadMap(Beatmap(**mapJson)) 241 | sv.logger.info(f'Ending Request OsuAPI {playtime(time() * 1000)}') 242 | 243 | im = await ds.draw() 244 | msg = MessageSegment.image(img2b64(im)) 245 | except Exception as e: 246 | sv.logger.error(f'制图错误:{traceback.print_exc()}') 247 | msg = f'Error: {type(e)}' 248 | return msg -------------------------------------------------------------------------------- /src/Picture/__init__.py: -------------------------------------------------------------------------------- 1 | from .Info import * 2 | from .Map import * 3 | from .Performance import * 4 | from .Score import * 5 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from asyncio import Task 3 | 4 | from .. import * 5 | from .Api import osuApi 6 | from .DataBase import get_all_user_osuid, insert_user_daily_data 7 | from .Model import User 8 | 9 | 10 | async def update_daily_data() -> int: 11 | 12 | tasks: list[Task] = [] 13 | result = get_all_user_osuid() 14 | loop = asyncio.get_event_loop() 15 | 16 | async def update(osuid: int, mode: str) -> User: 17 | data = await osuApi.user(osuid, mode) 18 | return User(**data) 19 | 20 | for mode in range(4): 21 | for user in result: 22 | task = loop.create_task(update(user.Osuid, mode=GameMode[mode])) 23 | tasks.append(task) 24 | 25 | done, pending = await asyncio.wait(tasks) 26 | userdata = [] 27 | for _ in done: 28 | try: 29 | userdata.append(_.result()) 30 | except: 31 | continue 32 | _in = insert_user_daily_data(mode, userdata) 33 | if _in: 34 | sv.logger.info(f'模式 {GameMode[mode]} 添加每日数据完成') 35 | else: 36 | sv.logger.error(f'模式 {GameMode[mode]} 添加每日数据错误') 37 | 38 | for _ in tasks: 39 | _.cancel() 40 | 41 | return len(result) -------------------------------------------------------------------------------- /src/api.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Mapping, Optional, Union 2 | 3 | from aiohttp import ClientResponse, ClientSession 4 | from oauthlib.oauth2 import BackendApplicationClient, OAuth2Error, TokenExpiredError 5 | from requests_oauthlib import OAuth2Session 6 | 7 | from .. import * 8 | from .Model import * 9 | 10 | 11 | class OAuthClient(OAuth2Session): 12 | 13 | async def requestAsync(self, 14 | method: str, 15 | url: str, 16 | session: ClientSession, 17 | *, 18 | headers: Mapping[str, str] = None, 19 | params: Mapping[str, str] = None, 20 | data: Mapping[str, str] = None 21 | ) -> ClientResponse: 22 | 23 | for hook in self.compliance_hook['protected_request']: 24 | url, headers, data = hook(url, headers, data) 25 | 26 | url, headers, data = self._client.add_token(url, method, data, headers) 27 | 28 | return await session.request(method, url, headers=headers, params=params, data=data) 29 | 30 | 31 | class OsuAPI: 32 | 33 | OsuAPIv2 = 'https://osu.ppy.sh/api/v2' 34 | TokenUrl = 'https://osu.ppy.sh/oauth/token' 35 | OAuthUrl = 'https://osu.ppy.sh/authorize' 36 | OsuPPAPI = 'https://api.yuzuai.xyz/osu' 37 | SayoAPI = 'https://api.sayobot.cn' 38 | 39 | def __init__(self, 40 | client_id: int, 41 | client_secret: str, 42 | *, 43 | refresh_token: Optional[str] = None 44 | ) -> None: 45 | 46 | self.client_id = client_id 47 | self.client_secret = client_secret 48 | self.refresh_token = refresh_token 49 | self.session = self._new_session() 50 | 51 | def _new_session(self) -> OAuthClient: 52 | client = BackendApplicationClient(self.client_id, scope=['public']) 53 | session = OAuthClient(client=client) 54 | token = session.fetch_token( 55 | self.TokenUrl, client_secret=self.client_secret) 56 | self.access_token = token['access_token'] 57 | return session 58 | 59 | async def _request(self, method: str, url: str, params: Mapping[str, str] = None): 60 | """OsuAPIv2 HTTP 请求""" 61 | client_session = ClientSession() 62 | 63 | async def create_request() -> ClientResponse: 64 | return await self.session.requestAsync(method, url, client_session, params=params) 65 | 66 | async def reauthenticate_retry(): 67 | self.session = self._new_session() 68 | return await create_request() 69 | 70 | try: 71 | res = await create_request() 72 | except TokenExpiredError: 73 | res = await reauthenticate_retry() 74 | except OAuth2Error as e: 75 | if e.description != "The refresh token is invalid.": 76 | raise 77 | res = await reauthenticate_retry() 78 | 79 | data = await res.json() 80 | 81 | if data == {"authentication": "basic"}: 82 | res = await reauthenticate_retry() 83 | data = await res.json() 84 | 85 | await client_session.close() 86 | 87 | return data 88 | 89 | async def _totalrequest(self, method: str, url: str, *, content_type: str = 'application/json', **kwargs): 90 | """ 91 | 通用 HTTP 请求 92 | """ 93 | session = ClientSession() 94 | try: 95 | res = await session.request(method, url, **kwargs) 96 | except: 97 | pass 98 | data = await res.json(encoding='utf8', content_type=content_type) 99 | await session.close() 100 | return data 101 | 102 | 103 | async def user(self, user_id: Union[str, int], *, mode: Optional[str] = None): 104 | """获取用户信息""" 105 | return await self._request('GET', self.OsuAPIv2 + f'/users/{user_id}/{mode if mode else ""}') 106 | 107 | 108 | async def _get_user(self, user_id: str) -> int: 109 | data = await self.user(user_id) 110 | return data['id'] 111 | 112 | 113 | async def user_recent(self, user_id: Union[str, int], *, mode: Optional[str] = None, include_fails: Optional[int] = None): 114 | """获取用户最近游玩成绩""" 115 | if isinstance(user_id, str) and user_id.isdigit(): 116 | user_id = await self._get_user(user_id) 117 | params = {} 118 | if mode: 119 | params['mode'] = mode 120 | if include_fails: 121 | params['include_fails'] = include_fails 122 | return await self._request('GET', self.OsuAPIv2 + f'/users/{user_id}/scores/recent', params) 123 | 124 | 125 | async def user_scores(self, user_id: Union[str, int], mapid: int, *, mode: Optional[str] = None, mods: Optional[List[str]] = None): 126 | """获取用户游玩指定地图的最好成绩""" 127 | if isinstance(user_id, str) and not user_id.isdigit(): 128 | user_id = await self._get_user(user_id) 129 | params = {} 130 | if mode: 131 | params['mode'] = mode 132 | if mods: 133 | params['mods[]'] = mods 134 | return await self._request('GET', self.OsuAPIv2 + f'/beatmaps/{mapid}/scores/users/{user_id}', params) 135 | 136 | 137 | async def user_scores_all(self, user_id: Union[str, int], mapid: int, *, mode: Optional[str] = None): 138 | """获取用户游玩指定地图的所有成绩""" 139 | if isinstance(user_id, str) and user_id.isdigit(): 140 | user_id = await self._get_user(user_id) 141 | params = {} 142 | if mode: 143 | params['mode'] = mode 144 | return await self._request('GET', self.OsuAPIv2 + f'/beatmaps/{mapid}/scores/users/{user_id}/all', params) 145 | 146 | 147 | async def user_scores_best(self, user_id: Union[str, int], *, mode: Optional[str] = None, limit: Optional[int] = None): 148 | """获取用户的最好成绩排行榜""" 149 | if isinstance(user_id, str) and user_id.isdigit(): 150 | user_id = await self._get_user(user_id) 151 | params = {} 152 | if mode: 153 | params['mode'] = mode 154 | if limit: 155 | params['limit'] = limit 156 | return await self._request('GET', self.OsuAPIv2 + f'/users/{user_id}/scores/best', params) 157 | 158 | 159 | async def beatmap(self, mapid: int): 160 | """获取指定地图信息""" 161 | return await self._request('GET', self.OsuAPIv2 + f'/beatmaps/{mapid}') 162 | 163 | 164 | async def sayomap(self, beatmapsetid: int): 165 | """获取 SayoAPI 的地图信息""" 166 | return await self._totalrequest('GET', self.SayoAPI + f'/v2/beatmapinfo', params={'0': beatmapsetid}, content_type='text/html') 167 | 168 | 169 | async def pp(self, params: Mapping[str, Any]): 170 | """计算 pp""" 171 | return await self._totalrequest('GET', self.OsuPPAPI + '/PPCalc', params=params) 172 | 173 | 174 | osuApi = OsuAPI(client_id, client_secret) 175 | -------------------------------------------------------------------------------- /src/error.py: -------------------------------------------------------------------------------- 1 | 2 | class UserNotBindError(Exception): 3 | 4 | def __str__(self) -> str: 5 | return '该账号尚未绑定,请输入 !bind 用户名 绑定账号' 6 | 7 | class UserNotFoundError(Exception): 8 | 9 | def __str__(self) -> str: 10 | return '未查询到该玩家' 11 | 12 | class PPError(Exception): 13 | 14 | def __str__(self) -> str: 15 | return f'Error: PP计算服务错误,请联系bot管理员' 16 | 17 | class UserEnterError(Exception): 18 | 19 | def __str__(self) -> str: 20 | return '请输入正确参数' 21 | 22 | class DrawImageError(Exception): 23 | 24 | def __init__(self, value: str) -> None: 25 | self.value = value 26 | 27 | class ModsError(Exception): 28 | 29 | def __init__(self, value: str) -> None: 30 | self.value = value 31 | 32 | class TokenError(Exception): 33 | 34 | def __init__(self, value: str) -> None: 35 | self.value = value 36 | -------------------------------------------------------------------------------- /src/file.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from typing import Union 3 | 4 | import aiohttp 5 | 6 | from .. import * 7 | 8 | 9 | async def getImage(url: str) -> Union[BytesIO, Exception, Path]: 10 | try: 11 | if 'avatar-guest.png' in url: 12 | url = 'https://osu.ppy.sh/images/layout/avatar-guest.png' 13 | async with aiohttp.request('GET', url) as resp: 14 | if resp.status == 403: 15 | return WorkDir / 'mapbg.png' 16 | data = await resp.read() 17 | return BytesIO(data) 18 | except Exception as e: 19 | sv.logger.error(f'Image Failed: {e}') 20 | return e 21 | 22 | -------------------------------------------------------------------------------- /src/mods.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from .. import NewMod 4 | from .Model import Score 5 | 6 | 7 | class Mods: 8 | 9 | tempMods: Optional[int] 10 | 11 | def __init__(self, scoreList: List[Score], mods: List[str]) -> None: 12 | self.ScoreList = scoreList 13 | self.Sum = self.calcModsValue(mods) 14 | 15 | 16 | def calcModsValue(self, mods: List[str]) -> int: 17 | temp = 0 18 | for mod in mods: 19 | temp += int(NewMod[mod]) 20 | return temp 21 | 22 | 23 | def findModsIndex(self) -> Optional[List[int]]: 24 | """获取当前 `mods` 的列表""" 25 | listIndex = [] 26 | for index, value in enumerate(self.ScoreList): 27 | if self.calcModsValue(value.mods) == self.Sum: 28 | listIndex.append(index) 29 | return listIndex -------------------------------------------------------------------------------- /src/pp.py: -------------------------------------------------------------------------------- 1 | from .Api import osuApi 2 | from .Error import PPError 3 | from .Model import PP, Score 4 | 5 | 6 | class PPCalc: 7 | 8 | def __init__(self, score: Score) -> None: 9 | self.score = score 10 | self.beatmap = score.beatmap 11 | 12 | 13 | async def calc(self, *, isPlay: bool = True) -> PP: 14 | data = { 15 | 'BeatmapID': self.beatmap.id, 16 | 'Mode': self.score.mode_int, 17 | 'Accuracy': self.score.accuracy, 18 | 'C300': self.score.statistics.count_300, 19 | 'C100': self.score.statistics.count_100, 20 | 'C50': self.score.statistics.count_50, 21 | 'Miss': self.score.statistics.count_miss, 22 | 'Mods': ''.join(self.score.mods), 23 | 'isPlay': str(isPlay) 24 | } 25 | if self.score.mode_int != 3: 26 | data['Combo'] = self.score.max_combo 27 | if self.score.mode_int == 2 or self.score.mode_int == 3: 28 | data['Katu'] = self.score.statistics.count_katu 29 | if self.score.mode_int == 3: 30 | data['Score'] = self.score.score 31 | data['Geki'] = self.score.statistics.count_geki 32 | try: 33 | pp = await osuApi.pp(data) 34 | return PP(**pp) 35 | except Exception as e: 36 | raise PPError -------------------------------------------------------------------------------- /static/Best Performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/Best Performance.png -------------------------------------------------------------------------------- /static/OAuth.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id": "", 3 | "client_secret": "" 4 | } -------------------------------------------------------------------------------- /static/beatmapinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/beatmapinfo.png -------------------------------------------------------------------------------- /static/flags/AD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AD.png -------------------------------------------------------------------------------- /static/flags/AE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AE.png -------------------------------------------------------------------------------- /static/flags/AF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AF.png -------------------------------------------------------------------------------- /static/flags/AG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AG.png -------------------------------------------------------------------------------- /static/flags/AI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AI.png -------------------------------------------------------------------------------- /static/flags/AL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AL.png -------------------------------------------------------------------------------- /static/flags/AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AM.png -------------------------------------------------------------------------------- /static/flags/AO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AO.png -------------------------------------------------------------------------------- /static/flags/AQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AQ.png -------------------------------------------------------------------------------- /static/flags/AR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AR.png -------------------------------------------------------------------------------- /static/flags/AS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AS.png -------------------------------------------------------------------------------- /static/flags/AT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AT.png -------------------------------------------------------------------------------- /static/flags/AU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AU.png -------------------------------------------------------------------------------- /static/flags/AW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AW.png -------------------------------------------------------------------------------- /static/flags/AX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AX.png -------------------------------------------------------------------------------- /static/flags/AZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/AZ.png -------------------------------------------------------------------------------- /static/flags/BA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BA.png -------------------------------------------------------------------------------- /static/flags/BB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BB.png -------------------------------------------------------------------------------- /static/flags/BD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BD.png -------------------------------------------------------------------------------- /static/flags/BE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BE.png -------------------------------------------------------------------------------- /static/flags/BF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BF.png -------------------------------------------------------------------------------- /static/flags/BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BG.png -------------------------------------------------------------------------------- /static/flags/BH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BH.png -------------------------------------------------------------------------------- /static/flags/BI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BI.png -------------------------------------------------------------------------------- /static/flags/BJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BJ.png -------------------------------------------------------------------------------- /static/flags/BL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BL.png -------------------------------------------------------------------------------- /static/flags/BM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BM.png -------------------------------------------------------------------------------- /static/flags/BN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BN.png -------------------------------------------------------------------------------- /static/flags/BO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BO.png -------------------------------------------------------------------------------- /static/flags/BQ (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BQ (1).png -------------------------------------------------------------------------------- /static/flags/BQ (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BQ (2).png -------------------------------------------------------------------------------- /static/flags/BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BR.png -------------------------------------------------------------------------------- /static/flags/BS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BS.png -------------------------------------------------------------------------------- /static/flags/BT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BT.png -------------------------------------------------------------------------------- /static/flags/BV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BV.png -------------------------------------------------------------------------------- /static/flags/BW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BW.png -------------------------------------------------------------------------------- /static/flags/BY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BY.png -------------------------------------------------------------------------------- /static/flags/BZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/BZ.png -------------------------------------------------------------------------------- /static/flags/CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CA.png -------------------------------------------------------------------------------- /static/flags/CD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CD.png -------------------------------------------------------------------------------- /static/flags/CF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CF.png -------------------------------------------------------------------------------- /static/flags/CG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CG.png -------------------------------------------------------------------------------- /static/flags/CH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CH.png -------------------------------------------------------------------------------- /static/flags/CI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CI.png -------------------------------------------------------------------------------- /static/flags/CK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CK.png -------------------------------------------------------------------------------- /static/flags/CL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CL.png -------------------------------------------------------------------------------- /static/flags/CM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CM.png -------------------------------------------------------------------------------- /static/flags/CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CN.png -------------------------------------------------------------------------------- /static/flags/CO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CO.png -------------------------------------------------------------------------------- /static/flags/CR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CR.png -------------------------------------------------------------------------------- /static/flags/CU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CU.png -------------------------------------------------------------------------------- /static/flags/CV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CV.png -------------------------------------------------------------------------------- /static/flags/CX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CX.png -------------------------------------------------------------------------------- /static/flags/CY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CY.png -------------------------------------------------------------------------------- /static/flags/CZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/CZ.png -------------------------------------------------------------------------------- /static/flags/DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/DE.png -------------------------------------------------------------------------------- /static/flags/DJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/DJ.png -------------------------------------------------------------------------------- /static/flags/DK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/DK.png -------------------------------------------------------------------------------- /static/flags/DM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/DM.png -------------------------------------------------------------------------------- /static/flags/DO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/DO.png -------------------------------------------------------------------------------- /static/flags/DZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/DZ.png -------------------------------------------------------------------------------- /static/flags/EC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/EC.png -------------------------------------------------------------------------------- /static/flags/EE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/EE.png -------------------------------------------------------------------------------- /static/flags/EG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/EG.png -------------------------------------------------------------------------------- /static/flags/EH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/EH.png -------------------------------------------------------------------------------- /static/flags/ER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ER.png -------------------------------------------------------------------------------- /static/flags/ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ES.png -------------------------------------------------------------------------------- /static/flags/ET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ET.png -------------------------------------------------------------------------------- /static/flags/FI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/FI.png -------------------------------------------------------------------------------- /static/flags/FJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/FJ.png -------------------------------------------------------------------------------- /static/flags/FK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/FK.png -------------------------------------------------------------------------------- /static/flags/FO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/FO.png -------------------------------------------------------------------------------- /static/flags/GA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GA.png -------------------------------------------------------------------------------- /static/flags/GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GB.png -------------------------------------------------------------------------------- /static/flags/GD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GD.png -------------------------------------------------------------------------------- /static/flags/GE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GE.png -------------------------------------------------------------------------------- /static/flags/GF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GF.png -------------------------------------------------------------------------------- /static/flags/GG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GG.png -------------------------------------------------------------------------------- /static/flags/GH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GH.png -------------------------------------------------------------------------------- /static/flags/GI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GI.png -------------------------------------------------------------------------------- /static/flags/GL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GL.png -------------------------------------------------------------------------------- /static/flags/GM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GM.png -------------------------------------------------------------------------------- /static/flags/GN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GN.png -------------------------------------------------------------------------------- /static/flags/GP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GP.png -------------------------------------------------------------------------------- /static/flags/GQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GQ.png -------------------------------------------------------------------------------- /static/flags/GR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GR.png -------------------------------------------------------------------------------- /static/flags/GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GS.png -------------------------------------------------------------------------------- /static/flags/GT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GT.png -------------------------------------------------------------------------------- /static/flags/GU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GU.png -------------------------------------------------------------------------------- /static/flags/GW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GW.png -------------------------------------------------------------------------------- /static/flags/GY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/GY.png -------------------------------------------------------------------------------- /static/flags/HK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/HK.png -------------------------------------------------------------------------------- /static/flags/HM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/HM.png -------------------------------------------------------------------------------- /static/flags/HN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/HN.png -------------------------------------------------------------------------------- /static/flags/HR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/HR.png -------------------------------------------------------------------------------- /static/flags/HT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/HT.png -------------------------------------------------------------------------------- /static/flags/HU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/HU.png -------------------------------------------------------------------------------- /static/flags/ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ID.png -------------------------------------------------------------------------------- /static/flags/IE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IE.png -------------------------------------------------------------------------------- /static/flags/IL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IL.png -------------------------------------------------------------------------------- /static/flags/IM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IM.png -------------------------------------------------------------------------------- /static/flags/IN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IN.png -------------------------------------------------------------------------------- /static/flags/IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IO.png -------------------------------------------------------------------------------- /static/flags/IQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IQ.png -------------------------------------------------------------------------------- /static/flags/IR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IR.png -------------------------------------------------------------------------------- /static/flags/IS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IS.png -------------------------------------------------------------------------------- /static/flags/IT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/IT.png -------------------------------------------------------------------------------- /static/flags/JE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/JE.png -------------------------------------------------------------------------------- /static/flags/JM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/JM.png -------------------------------------------------------------------------------- /static/flags/JO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/JO.png -------------------------------------------------------------------------------- /static/flags/JP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/JP.png -------------------------------------------------------------------------------- /static/flags/KE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KE.png -------------------------------------------------------------------------------- /static/flags/KG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KG.png -------------------------------------------------------------------------------- /static/flags/KH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KH.png -------------------------------------------------------------------------------- /static/flags/KI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KI.png -------------------------------------------------------------------------------- /static/flags/KM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KM.png -------------------------------------------------------------------------------- /static/flags/KN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KN.png -------------------------------------------------------------------------------- /static/flags/KP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KP.png -------------------------------------------------------------------------------- /static/flags/KR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KR.png -------------------------------------------------------------------------------- /static/flags/KW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KW.png -------------------------------------------------------------------------------- /static/flags/KY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KY.png -------------------------------------------------------------------------------- /static/flags/KZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/KZ.png -------------------------------------------------------------------------------- /static/flags/LA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LA.png -------------------------------------------------------------------------------- /static/flags/LB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LB.png -------------------------------------------------------------------------------- /static/flags/LC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LC.png -------------------------------------------------------------------------------- /static/flags/LI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LI.png -------------------------------------------------------------------------------- /static/flags/LK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LK.png -------------------------------------------------------------------------------- /static/flags/LR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LR.png -------------------------------------------------------------------------------- /static/flags/LS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LS.png -------------------------------------------------------------------------------- /static/flags/LT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LT.png -------------------------------------------------------------------------------- /static/flags/LU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LU.png -------------------------------------------------------------------------------- /static/flags/LV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LV.png -------------------------------------------------------------------------------- /static/flags/LY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/LY.png -------------------------------------------------------------------------------- /static/flags/MA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MA.png -------------------------------------------------------------------------------- /static/flags/MC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MC.png -------------------------------------------------------------------------------- /static/flags/MD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MD.png -------------------------------------------------------------------------------- /static/flags/ME.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ME.png -------------------------------------------------------------------------------- /static/flags/MF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MF.png -------------------------------------------------------------------------------- /static/flags/MG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MG.png -------------------------------------------------------------------------------- /static/flags/MH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MH.png -------------------------------------------------------------------------------- /static/flags/MK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MK.png -------------------------------------------------------------------------------- /static/flags/ML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ML.png -------------------------------------------------------------------------------- /static/flags/MM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MM.png -------------------------------------------------------------------------------- /static/flags/MN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MN.png -------------------------------------------------------------------------------- /static/flags/MO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MO.png -------------------------------------------------------------------------------- /static/flags/MP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MP.png -------------------------------------------------------------------------------- /static/flags/MQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MQ.png -------------------------------------------------------------------------------- /static/flags/MR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MR.png -------------------------------------------------------------------------------- /static/flags/MS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MS.png -------------------------------------------------------------------------------- /static/flags/MT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MT.png -------------------------------------------------------------------------------- /static/flags/MU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MU.png -------------------------------------------------------------------------------- /static/flags/MV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MV.png -------------------------------------------------------------------------------- /static/flags/MW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MW.png -------------------------------------------------------------------------------- /static/flags/MX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MX.png -------------------------------------------------------------------------------- /static/flags/MY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MY.png -------------------------------------------------------------------------------- /static/flags/MZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/MZ.png -------------------------------------------------------------------------------- /static/flags/NA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NA.png -------------------------------------------------------------------------------- /static/flags/NC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NC.png -------------------------------------------------------------------------------- /static/flags/NE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NE.png -------------------------------------------------------------------------------- /static/flags/NF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NF.png -------------------------------------------------------------------------------- /static/flags/NG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NG.png -------------------------------------------------------------------------------- /static/flags/NI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NI.png -------------------------------------------------------------------------------- /static/flags/NL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NL.png -------------------------------------------------------------------------------- /static/flags/NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NO.png -------------------------------------------------------------------------------- /static/flags/NP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NP.png -------------------------------------------------------------------------------- /static/flags/NR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NR.png -------------------------------------------------------------------------------- /static/flags/NU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NU.png -------------------------------------------------------------------------------- /static/flags/NZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/NZ.png -------------------------------------------------------------------------------- /static/flags/OM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/OM.png -------------------------------------------------------------------------------- /static/flags/PA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PA.png -------------------------------------------------------------------------------- /static/flags/PE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PE.png -------------------------------------------------------------------------------- /static/flags/PF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PF.png -------------------------------------------------------------------------------- /static/flags/PG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PG.png -------------------------------------------------------------------------------- /static/flags/PH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PH.png -------------------------------------------------------------------------------- /static/flags/PK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PK.png -------------------------------------------------------------------------------- /static/flags/PL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PL.png -------------------------------------------------------------------------------- /static/flags/PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PM.png -------------------------------------------------------------------------------- /static/flags/PN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PN.png -------------------------------------------------------------------------------- /static/flags/PR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PR.png -------------------------------------------------------------------------------- /static/flags/PS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PS.png -------------------------------------------------------------------------------- /static/flags/PT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PT.png -------------------------------------------------------------------------------- /static/flags/PW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PW.png -------------------------------------------------------------------------------- /static/flags/PY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/PY.png -------------------------------------------------------------------------------- /static/flags/QA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/QA.png -------------------------------------------------------------------------------- /static/flags/RE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/RE.png -------------------------------------------------------------------------------- /static/flags/RO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/RO.png -------------------------------------------------------------------------------- /static/flags/RS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/RS.png -------------------------------------------------------------------------------- /static/flags/RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/RU.png -------------------------------------------------------------------------------- /static/flags/RW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/RW.png -------------------------------------------------------------------------------- /static/flags/SA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SA.png -------------------------------------------------------------------------------- /static/flags/SB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SB.png -------------------------------------------------------------------------------- /static/flags/SC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SC.png -------------------------------------------------------------------------------- /static/flags/SD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SD.png -------------------------------------------------------------------------------- /static/flags/SE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SE.png -------------------------------------------------------------------------------- /static/flags/SG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SG.png -------------------------------------------------------------------------------- /static/flags/SH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SH.png -------------------------------------------------------------------------------- /static/flags/SI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SI.png -------------------------------------------------------------------------------- /static/flags/SJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SJ.png -------------------------------------------------------------------------------- /static/flags/SK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SK.png -------------------------------------------------------------------------------- /static/flags/SL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SL.png -------------------------------------------------------------------------------- /static/flags/SM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SM.png -------------------------------------------------------------------------------- /static/flags/SN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SN.png -------------------------------------------------------------------------------- /static/flags/SO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SO.png -------------------------------------------------------------------------------- /static/flags/SR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SR.png -------------------------------------------------------------------------------- /static/flags/SS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SS.png -------------------------------------------------------------------------------- /static/flags/ST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ST.png -------------------------------------------------------------------------------- /static/flags/SV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SV.png -------------------------------------------------------------------------------- /static/flags/SY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SY.png -------------------------------------------------------------------------------- /static/flags/SZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/SZ.png -------------------------------------------------------------------------------- /static/flags/TC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TC.png -------------------------------------------------------------------------------- /static/flags/TD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TD.png -------------------------------------------------------------------------------- /static/flags/TF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TF.png -------------------------------------------------------------------------------- /static/flags/TG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TG.png -------------------------------------------------------------------------------- /static/flags/TH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TH.png -------------------------------------------------------------------------------- /static/flags/TJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TJ.png -------------------------------------------------------------------------------- /static/flags/TK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TK.png -------------------------------------------------------------------------------- /static/flags/TL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TL.png -------------------------------------------------------------------------------- /static/flags/TM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TM.png -------------------------------------------------------------------------------- /static/flags/TN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TN.png -------------------------------------------------------------------------------- /static/flags/TO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TO.png -------------------------------------------------------------------------------- /static/flags/TR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TR.png -------------------------------------------------------------------------------- /static/flags/TT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TT.png -------------------------------------------------------------------------------- /static/flags/TV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TV.png -------------------------------------------------------------------------------- /static/flags/TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TW.png -------------------------------------------------------------------------------- /static/flags/TZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/TZ.png -------------------------------------------------------------------------------- /static/flags/UA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/UA.png -------------------------------------------------------------------------------- /static/flags/UG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/UG.png -------------------------------------------------------------------------------- /static/flags/UM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/UM.png -------------------------------------------------------------------------------- /static/flags/US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/US.png -------------------------------------------------------------------------------- /static/flags/UY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/UY.png -------------------------------------------------------------------------------- /static/flags/UZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/UZ.png -------------------------------------------------------------------------------- /static/flags/VA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VA.png -------------------------------------------------------------------------------- /static/flags/VC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VC.png -------------------------------------------------------------------------------- /static/flags/VE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VE.png -------------------------------------------------------------------------------- /static/flags/VG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VG.png -------------------------------------------------------------------------------- /static/flags/VI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VI.png -------------------------------------------------------------------------------- /static/flags/VN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VN.png -------------------------------------------------------------------------------- /static/flags/VU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/VU.png -------------------------------------------------------------------------------- /static/flags/WF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/WF.png -------------------------------------------------------------------------------- /static/flags/WS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/WS.png -------------------------------------------------------------------------------- /static/flags/YE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/YE.png -------------------------------------------------------------------------------- /static/flags/YT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/YT.png -------------------------------------------------------------------------------- /static/flags/ZA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ZA.png -------------------------------------------------------------------------------- /static/flags/ZM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ZM.png -------------------------------------------------------------------------------- /static/flags/ZW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/flags/ZW.png -------------------------------------------------------------------------------- /static/fonts/Meiryo Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/fonts/Meiryo Regular.ttf -------------------------------------------------------------------------------- /static/fonts/Meiryo SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/fonts/Meiryo SemiBold.ttf -------------------------------------------------------------------------------- /static/fonts/Torus Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/fonts/Torus Regular.otf -------------------------------------------------------------------------------- /static/fonts/Torus SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/fonts/Torus SemiBold.otf -------------------------------------------------------------------------------- /static/fonts/Venera.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/fonts/Venera.otf -------------------------------------------------------------------------------- /static/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/help.png -------------------------------------------------------------------------------- /static/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/info.png -------------------------------------------------------------------------------- /static/map/s: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/mods/4K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/4K.png -------------------------------------------------------------------------------- /static/mods/5K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/5K.png -------------------------------------------------------------------------------- /static/mods/6K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/6K.png -------------------------------------------------------------------------------- /static/mods/9K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/9K.png -------------------------------------------------------------------------------- /static/mods/DT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/DT.png -------------------------------------------------------------------------------- /static/mods/EZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/EZ.png -------------------------------------------------------------------------------- /static/mods/FI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/FI.png -------------------------------------------------------------------------------- /static/mods/FL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/FL.png -------------------------------------------------------------------------------- /static/mods/HD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/HD.png -------------------------------------------------------------------------------- /static/mods/HR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/HR.png -------------------------------------------------------------------------------- /static/mods/HT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/HT.png -------------------------------------------------------------------------------- /static/mods/MR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/MR.png -------------------------------------------------------------------------------- /static/mods/NC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/NC.png -------------------------------------------------------------------------------- /static/mods/NF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/NF.png -------------------------------------------------------------------------------- /static/mods/PF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/PF.png -------------------------------------------------------------------------------- /static/mods/SD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/SD.png -------------------------------------------------------------------------------- /static/mods/SO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/SO.png -------------------------------------------------------------------------------- /static/mods/TD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/mods/TD.png -------------------------------------------------------------------------------- /static/pfm_ctb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/pfm_ctb.png -------------------------------------------------------------------------------- /static/pfm_mania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/pfm_mania.png -------------------------------------------------------------------------------- /static/pfm_std.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/pfm_std.png -------------------------------------------------------------------------------- /static/pfm_taiko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/pfm_taiko.png -------------------------------------------------------------------------------- /static/ranking/ranking-A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-A.png -------------------------------------------------------------------------------- /static/ranking/ranking-B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-B.png -------------------------------------------------------------------------------- /static/ranking/ranking-C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-C.png -------------------------------------------------------------------------------- /static/ranking/ranking-D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-D.png -------------------------------------------------------------------------------- /static/ranking/ranking-F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-F.png -------------------------------------------------------------------------------- /static/ranking/ranking-S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-S.png -------------------------------------------------------------------------------- /static/ranking/ranking-SH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-SH.png -------------------------------------------------------------------------------- /static/ranking/ranking-X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-X.png -------------------------------------------------------------------------------- /static/ranking/ranking-XH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/ranking/ranking-XH.png -------------------------------------------------------------------------------- /static/work/bmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/bmap.png -------------------------------------------------------------------------------- /static/work/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/center.png -------------------------------------------------------------------------------- /static/work/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/color.png -------------------------------------------------------------------------------- /static/work/ctb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/ctb.png -------------------------------------------------------------------------------- /static/work/ctb_expertplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/ctb_expertplus.png -------------------------------------------------------------------------------- /static/work/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/left.png -------------------------------------------------------------------------------- /static/work/mania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/mania.png -------------------------------------------------------------------------------- /static/work/mania_expertplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/mania_expertplus.png -------------------------------------------------------------------------------- /static/work/mapbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/mapbg.png -------------------------------------------------------------------------------- /static/work/off-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/off-line.png -------------------------------------------------------------------------------- /static/work/on-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/on-line.png -------------------------------------------------------------------------------- /static/work/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/right.png -------------------------------------------------------------------------------- /static/work/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/stars.png -------------------------------------------------------------------------------- /static/work/stars_expertplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/stars_expertplus.png -------------------------------------------------------------------------------- /static/work/std.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/std.png -------------------------------------------------------------------------------- /static/work/std_expertplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/std_expertplus.png -------------------------------------------------------------------------------- /static/work/suppoter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/suppoter.png -------------------------------------------------------------------------------- /static/work/taiko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/taiko.png -------------------------------------------------------------------------------- /static/work/taiko_expertplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuri-YuzuChaN/osuv2/9c7b7568b5536274597a1992c964183ccdb0c8fd/static/work/taiko_expertplus.png --------------------------------------------------------------------------------