Coverage report: + 38% +
+ + ++ Files + Functions + Classes +
++ coverage.py v7.6.4, + created at 2024-11-05 17:25 +0800 +
+From bc6a7fa6c6cf85064c5c4e8e2129d635efe8a442 Mon Sep 17 00:00:00 2001 From: LittleStone <1526631427@qq.com> Date: Wed, 30 Oct 2024 17:11:23 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0ut=E5=B9=B6=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- irc_dlient.py | 42 ++++++------- test_irc_client.py | 154 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 test_irc_client.py diff --git a/irc_dlient.py b/irc_dlient.py index 28c9f3e..f54ed58 100755 --- a/irc_dlient.py +++ b/irc_dlient.py @@ -39,7 +39,6 @@ def __init__(self): class MyIRCClient: - def __init__(self, server, port, nickname, password): self.irc_react = irc.client.Reactor() self.server = self.irc_react.server() @@ -705,7 +704,7 @@ def get_mp_settings(self, connection, event): class Beatmap: - def __init__(self): + def __init__(self, client_id, client_secret): self.osu_client_id = client_id self.osu_client_secret = client_secret self.osu_token = "" @@ -783,7 +782,7 @@ def get_beatmap_info(self): )['beatmapset']['artist_unicode'] self.beatmap_star = response.json()['difficulty_rating'] self.beatmap_status = response.json()['status'] - self.beatemap_bpm = response.json()['bpm'] + self.beatmap_bpm = response.json()['bpm'] self.beatmap_cs = response.json()['cs'] self.beatmap_ar = response.json()['ar'] self.beatmap_od = response.json()['accuracy'] @@ -805,7 +804,7 @@ def get_beatmap_info(self): self.beatmap_artist = "" self.beatmap_star = 0 self.beatmap_status = "" - self.beatemap_bpm = "" + self.beatmap_bpm = "" self.beatmap_cs = "" self.beatmap_ar = "" self.beatmap_od = "" @@ -1215,25 +1214,24 @@ def calculate_pp_obj(self, mods, acc, misses, combo): return f'now:{self.currpp}pp| if FC({self.maxbeatmapcombo}x):{self.fcpp}pp| 95%:{self.fc95pp}pp| 96%:{self.fc96pp}pp| 97%:{self.fc97pp}pp| 98%:{self.fc98pp}pp| 99%:{self.fc99pp}pp| SS:{self.maxpp}pp| aim:{self.curraimpp}/{self.maxaimpp}pp| speed:{self.currspeedpp}/{self.maxspeedpp}pp| acc:{self.curraccpp}/{self.maxaccpp}pp' -# 没有maps文件夹时自动创建maps文件夹 -maps_dir = os.path.join(os.getcwd(), './maps') -if not os.path.exists(maps_dir): - os.makedirs(maps_dir) - print(f"'{maps_dir}'文件夹不存在,已经自动创建") - -config = Config() - -client_id = config.osuclientid -client_secret = config.osuclientsecret +if __name__ == '__main__': + config = Config() + # 没有maps文件夹时自动创建maps文件夹 + maps_dir = os.path.join(os.getcwd(), './maps') + if not os.path.exists(maps_dir): + os.makedirs(maps_dir) + print(f"'{maps_dir}'文件夹不存在,已经自动创建") -osu_nickname = config.osunickname -osu_password = config.osupassword + client_id = config.osuclientid + client_secret = config.osuclientsecret + osu_nickname = config.osunickname + osu_password = config.osupassword -p = Player() -r = Room() -b = Beatmap() -pp = PP() + p = Player() + r = Room() + b = Beatmap(client_id, client_secret) + pp = PP() -client = MyIRCClient(osu_server, osu_port, osu_nickname, osu_password) -client.start() + client = MyIRCClient(osu_server, osu_port, osu_nickname, osu_password) + client.start() diff --git a/test_irc_client.py b/test_irc_client.py new file mode 100644 index 0000000..750f8e1 --- /dev/null +++ b/test_irc_client.py @@ -0,0 +1,154 @@ +import unittest +from unittest.mock import patch, MagicMock +import irc_dlient + +class TestConfig(unittest.TestCase): + @patch('irc_dlient.configparser.ConfigParser') + @patch('irc_dlient.chardet.detect') + @patch('builtins.open') + def test_config_initialization(self, mock_open, mock_detect, mock_configparser): + # 设置模拟返回值 + mock_detect.return_value = {'encoding': 'utf-8'} + mock_config = MagicMock() + mock_config.__getitem__.return_value = { + 'client_id': 'test_id', + 'client_secret': 'test_secret', + 'nickname': 'test_nick', + 'password': 'test_pass', + 'mpname': 'test_mp', + 'starlimit': '5.0', + 'timelimit': '300', + 'mppassword': 'mp_pass' + } + mock_configparser.return_value = mock_config + + config = irc_dlient.Config() + + self.assertEqual(config.osuclientid, 'test_id') + self.assertEqual(config.osuclientsecret, 'test_secret') + self.assertEqual(config.osunickname, 'test_nick') + self.assertEqual(config.osupassword, 'test_pass') + self.assertEqual(config.mpname, 'test_mp') + self.assertEqual(config.starlimit, '5.0') + self.assertEqual(config.timelimit, '300') + self.assertEqual(config.mppassword, 'mp_pass') + +class TestPlayer(unittest.TestCase): + def setUp(self): + self.player = irc_dlient.Player() + + def test_add_player(self): + self.player.add_player("Player1") + self.assertIn("Player1", self.player.player_list) + + def test_remove_player(self): + self.player.add_player("Player1") + self.player.remove_player("Player1") + self.assertNotIn("Player1", self.player.player_list) + + def test_add_host(self): + self.player.add_host("Host1") + self.assertIn("Host1", self.player.room_host_list) + + def test_remove_host(self): + self.player.add_host("Host1") + self.player.remove_host("Host1") + self.assertNotIn("Host1", self.player.room_host_list) + +class TestBeatmap(unittest.TestCase): + def setUp(self): + # 从 config.ini 中读取 client_id 和 client_secret + config = irc_dlient.Config() + self.client_id = config.osuclientid + self.client_secret = config.osuclientsecret + + # 实例化 Beatmap 对象并设置 client_id 和 client_secret + self.beatmap = irc_dlient.Beatmap(self.client_id, self.client_secret) + + @patch('irc_dlient.requests.post') + def test_get_token_success(self, mock_post): + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.json.return_value = {'access_token': 'test_token'} + mock_post.return_value = mock_response + + self.beatmap.get_token() + + self.assertEqual(self.beatmap.osu_token, 'test_token') + + def test_get_beatmap_info_success(self): + """ + 这个测试用例将发送真实的 HTTP 请求到 osu! API + 并验证返回的真实响应是否符合预期。 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 beatmap_id + self.beatmap.beatmap_id = '75' # osu第一个ranked图 + + # 调用获取 beatmap 信息的方法 + self.beatmap.get_beatmap_info() + + # 验证响应内容 + self.assertEqual(self.beatmap.beatmap_name, 'DISCO PRINCE') + self.assertEqual(self.beatmap.beatmap_songs_id, '1') + self.assertEqual(self.beatmap.beatmap_artist, 'Kenji Ninuma') + self.assertEqual(self.beatmap.beatmap_star, 2.55) + self.assertEqual(self.beatmap.beatmap_status, 'ranked') + self.assertEqual(self.beatmap.beatmap_bpm, 120) + self.assertEqual(self.beatmap.beatmap_cs, 4) + self.assertEqual(self.beatmap.beatmap_ar, 6) + self.assertEqual(self.beatmap.beatmap_od, 6) + self.assertEqual(self.beatmap.beatmap_hp, 6) + self.assertEqual(self.beatmap.beatmap_length, 142) + self.assertEqual(self.beatmap.beatmap_ranked_date, '2007-10-06') + self.assertEqual(self.beatmap.beatmap_osudirect_url, 'https://osu.ppy.sh/beatmaps/75') + self.assertEqual(self.beatmap.beatmap_mirror_sayo_url, 'https://osu.sayobot.cn/home?search=1') + self.assertEqual(self.beatmap.beatmap_mirror_inso_url, 'http://inso.link/yukiho/?b=75') + +class TestPP(unittest.TestCase): + @patch('irc_dlient.os.path.exists') + @patch('irc_dlient.requests.get') + def test_get_beatmap_file_exists(self, mock_get, mock_exists): + mock_exists.return_value = True + pp = irc_dlient.PP() + pp.get_beatmap_file('12345') + mock_get.assert_not_called() + + @patch('irc_dlient.os.path.exists') + @patch('irc_dlient.requests.get') + def test_get_beatmap_file_download(self, mock_get, mock_exists): + mock_exists.return_value = False + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.content = b'osu file content' + mock_get.return_value = mock_response + + pp = irc_dlient.PP() + pp.get_beatmap_file('12345') + + mock_get.assert_called_with('https://osu.ppy.sh/osu/12345') + # 可以进一步检查文件写入,但需要更多的patching + +class TestRoom(unittest.TestCase): + @patch('builtins.open') + def test_save_last_room_id(self, mock_open): + mock_file = MagicMock() + mock_open.return_value.__enter__.return_value = mock_file + + room = irc_dlient.Room() + room.room_id = '#room123' + room.save_last_room_id() + + mock_file.write.assert_called_with('#room123') + + @patch('builtins.open', side_effect=FileNotFoundError) + def test_get_last_room_id_file_not_found(self, mock_open): + room = irc_dlient.Room() + last_id = room.get_last_room_id() + self.assertEqual(last_id, '') + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 6b899e0a9b35ebdfffe561741f5b90d861429e9e Mon Sep 17 00:00:00 2001 From: LittleStone <1526631427@qq.com> Date: Wed, 30 Oct 2024 17:24:07 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0README=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a893a41..2207822 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,10 @@ ```python pip install -r requirements.txt ``` - +执行单元测试 +```bash +python3 -m unittest -v test_irc_client.py +``` 运行程序 ```bash python3 irc_dlient.py From 64c194a3f027d04fa223b5ae428f85637977b561 Mon Sep 17 00:00:00 2001 From: LittleStone <1526631427@qq.com> Date: Tue, 5 Nov 2024 17:24:10 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0beatmap=20ut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .coveragerc | 13 ++ README.md | 9 +- irc_dlient.py | 12 +- tests/__init__.py | 0 tests/test_irc_client.py | 377 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 399 insertions(+), 12 deletions(-) create mode 100644 .coveragerc create mode 100644 tests/__init__.py create mode 100644 tests/test_irc_client.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..1c48378 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,13 @@ +# .coveragerc +[run] +source = irc_dlient +omit = + tests/* + +[report] +exclude_lines = + pragma: no cover + if __name__ == "__main__": + +[html] +directory = htmlcov diff --git a/README.md b/README.md index 2207822..7daaf1b 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,15 @@ ```python pip install -r requirements.txt ``` -执行单元测试 -```bash -python3 -m unittest -v test_irc_client.py -``` 运行程序 ```bash python3 irc_dlient.py ``` +执行单元测试 +```bash +pip install coverage +coverage run -m unittest discover -s tests +``` # 配置 diff --git a/irc_dlient.py b/irc_dlient.py index f54ed58..c9f1d9f 100755 --- a/irc_dlient.py +++ b/irc_dlient.py @@ -19,8 +19,6 @@ osu_server = "irc.ppy.sh" osu_port = 6667 -# 定义IRC客户端类 - class Config: def __init__(self): @@ -37,7 +35,7 @@ def __init__(self): self.timelimit = self.config['OSU']['timelimit'] self.mppassword = self.config['OSU']['mppassword'] - +# 定义IRC客户端类 class MyIRCClient: def __init__(self, server, port, nickname, password): self.irc_react = irc.client.Reactor() @@ -701,8 +699,6 @@ def get_mp_settings(self, connection, event): # 定义谱面类 - - class Beatmap: def __init__(self, client_id, client_secret): self.osu_client_id = client_id @@ -778,8 +774,7 @@ def get_beatmap_info(self): self.beatmap_songs_id = str(response.json()['beatmapset_id']) self.beatmap_name = response.json()['beatmapset']['title_unicode'] - self.beatmap_artist = response.json( - )['beatmapset']['artist_unicode'] + self.beatmap_artist = response.json()['beatmapset']['artist_unicode'] self.beatmap_star = response.json()['difficulty_rating'] self.beatmap_status = response.json()['status'] self.beatmap_bpm = response.json()['bpm'] @@ -1214,8 +1209,9 @@ def calculate_pp_obj(self, mods, acc, misses, combo): return f'now:{self.currpp}pp| if FC({self.maxbeatmapcombo}x):{self.fcpp}pp| 95%:{self.fc95pp}pp| 96%:{self.fc96pp}pp| 97%:{self.fc97pp}pp| 98%:{self.fc98pp}pp| 99%:{self.fc99pp}pp| SS:{self.maxpp}pp| aim:{self.curraimpp}/{self.maxaimpp}pp| speed:{self.currspeedpp}/{self.maxspeedpp}pp| acc:{self.curraccpp}/{self.maxaccpp}pp' +config = Config() + if __name__ == '__main__': - config = Config() # 没有maps文件夹时自动创建maps文件夹 maps_dir = os.path.join(os.getcwd(), './maps') if not os.path.exists(maps_dir): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_irc_client.py b/tests/test_irc_client.py new file mode 100644 index 0000000..d52733b --- /dev/null +++ b/tests/test_irc_client.py @@ -0,0 +1,377 @@ +import unittest +from unittest.mock import patch, MagicMock +from io import StringIO +import irc_dlient + +class TestConfig(unittest.TestCase): + @patch('irc_dlient.configparser.ConfigParser') + @patch('irc_dlient.chardet.detect') + @patch('builtins.open') + def test_config_initialization(self, mock_open, mock_detect, mock_configparser): + # 设置模拟返回值 + mock_detect.return_value = {'encoding': 'utf-8'} + mock_config = MagicMock() + mock_config.__getitem__.return_value = { + 'client_id': 'test_id', + 'client_secret': 'test_secret', + 'nickname': 'test_nick', + 'password': 'test_pass', + 'mpname': 'test_mp', + 'starlimit': '5.0', + 'timelimit': '300', + 'mppassword': 'mp_pass' + } + mock_configparser.return_value = mock_config + + config = irc_dlient.Config() + + self.assertEqual(config.osuclientid, 'test_id') + self.assertEqual(config.osuclientsecret, 'test_secret') + self.assertEqual(config.osunickname, 'test_nick') + self.assertEqual(config.osupassword, 'test_pass') + self.assertEqual(config.mpname, 'test_mp') + self.assertEqual(config.starlimit, '5.0') + self.assertEqual(config.timelimit, '300') + self.assertEqual(config.mppassword, 'mp_pass') + +class TestPlayer(unittest.TestCase): + def setUp(self): + self.player = irc_dlient.Player() + + def test_add_player(self): + self.player.add_player("Player1") + self.assertIn("Player1", self.player.player_list) + + def test_remove_player(self): + self.player.add_player("Player1") + self.player.remove_player("Player1") + self.assertNotIn("Player1", self.player.player_list) + + def test_add_host(self): + self.player.add_host("Host1") + self.assertIn("Host1", self.player.room_host_list) + + def test_remove_host(self): + self.player.add_host("Host1") + self.player.remove_host("Host1") + self.assertNotIn("Host1", self.player.room_host_list) + +class TestBeatmap(unittest.TestCase): + def setUp(self): + # 从 config.ini 中读取 client_id 和 client_secret + config = irc_dlient.Config() + self.client_id = config.osuclientid + self.client_secret = config.osuclientsecret + + # 实例化 Beatmap 对象并设置 client_id 和 client_secret + self.beatmap = irc_dlient.Beatmap(self.client_id, self.client_secret) + + @patch('irc_dlient.requests.post') + def test_get_token_success(self, mock_post): + """ + 获取 Token 成功 + """ + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.json.return_value = {'access_token': 'test_token'} + mock_post.return_value = mock_response + + self.beatmap.get_token() + + self.assertEqual(self.beatmap.osu_token, 'test_token') + + @patch('irc_dlient.config') + def test_check_beatmap_if_out_of_time(self, mock_config): + """ + 检查谱面时间限制 + """ + mock_config.timelimit = 0 + self.beatmap.beatmap_length = 100 + self.beatmap.check_beatmap_if_out_of_time() + self.assertEqual(self.beatmap.check_beatmap_if_out_of_time(), False) + + self.beatmap.beatmap_length = 95950 + self.beatmap.check_beatmap_if_out_of_time() + self.assertEqual(self.beatmap.check_beatmap_if_out_of_time(), False) + + mock_config.timelimit = 100 + self.beatmap.beatmap_length = 100 + self.beatmap.check_beatmap_if_out_of_time() + self.assertEqual(self.beatmap.check_beatmap_if_out_of_time(), False) + + self.beatmap.beatmap_length = 101 + self.beatmap.check_beatmap_if_out_of_time() + self.assertEqual(self.beatmap.check_beatmap_if_out_of_time(), True) + + def test_get_beatmap_info_success(self): + """ + 发送正确的beatmap id到 osu! API + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 beatmap_id + self.beatmap.change_beatmap_id('75') # osu第一个ranked图 + + # 调用获取 beatmap 信息的方法 + self.beatmap.get_beatmap_info() + + # 验证响应内容 + self.assertEqual(self.beatmap.beatmap_name, 'DISCO PRINCE') + self.assertEqual(self.beatmap.beatmap_songs_id, '1') + self.assertEqual(self.beatmap.beatmap_artist, 'Kenji Ninuma') + self.assertEqual(self.beatmap.beatmap_star, 2.55) + self.assertEqual(self.beatmap.beatmap_status, 'ranked') + self.assertEqual(self.beatmap.beatmap_bpm, 120) + self.assertEqual(self.beatmap.beatmap_cs, 4) + self.assertEqual(self.beatmap.beatmap_ar, 6) + self.assertEqual(self.beatmap.beatmap_od, 6) + self.assertEqual(self.beatmap.beatmap_hp, 6) + self.assertEqual(self.beatmap.beatmap_length, 142) + self.assertEqual(self.beatmap.beatmap_ranked_date, '2007-10-06') + self.assertEqual(self.beatmap.beatmap_osudirect_url, 'https://osu.ppy.sh/beatmaps/75') + self.assertEqual(self.beatmap.beatmap_mirror_sayo_url, 'https://osu.sayobot.cn/home?search=1') + self.assertEqual(self.beatmap.beatmap_mirror_inso_url, 'http://inso.link/yukiho/?b=75') + + def test_get_beatmap_info_with_wrong_beatmap_id(self): + """ + 发送错误的beatmap id到 osu! API + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 beatmap_id + self.beatmap.change_beatmap_id('1') # 错误的beatmap id + + # 调用获取 beatmap 信息的方法 + self.beatmap.get_beatmap_info() + + # 验证响应内容 + self.assertEqual(self.beatmap.beatmap_name, '获取谱面信息失败') + self.assertEqual(self.beatmap.beatmap_songs_id, '') + self.assertEqual(self.beatmap.beatmap_artist, '') + self.assertEqual(self.beatmap.beatmap_star, 0) + self.assertEqual(self.beatmap.beatmap_status, '') + self.assertEqual(self.beatmap.beatmap_bpm, '') + self.assertEqual(self.beatmap.beatmap_cs, '') + self.assertEqual(self.beatmap.beatmap_ar, '') + self.assertEqual(self.beatmap.beatmap_od, '') + self.assertEqual(self.beatmap.beatmap_hp, '') + self.assertEqual(self.beatmap.beatmap_length, 0) + self.assertEqual(self.beatmap.beatmap_ranked_date, '') + self.assertEqual(self.beatmap.beatmap_osudirect_url, '') + self.assertEqual(self.beatmap.beatmap_mirror_sayo_url, '') + self.assertEqual(self.beatmap.beatmap_mirror_inso_url, '') + + def test_get_beatmap_score_success(self): + """ + 发送正确的username查询分数 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 username + self.beatmap.get_user_id("LittleStone") + self.assertIsNotNone(self.beatmap.id2name, "用户ID获取失败") + + # 设置 beatmap_id + self.beatmap.beatmap_id = '75' # osu第一个ranked图 + + # 调用获取 beatmap 信息的方法 + self.beatmap.get_beatmap_score("LittleStone") + + self.assertEqual(self.beatmap.pr_title, '') + self.assertEqual(self.beatmap.pr_artist, '') + self.assertEqual(self.beatmap.pr_star, 0) + self.assertEqual(self.beatmap.beatmap_score_created_at, '2020-12-04') + self.assertEqual(self.beatmap.pr_acc, 83.85) + self.assertEqual(self.beatmap.pr_maxcombo, 122) + self.assertEqual(self.beatmap.pr_300, 150) + self.assertEqual(self.beatmap.pr_100, 35) + self.assertEqual(self.beatmap.pr_50, 6) + self.assertEqual(self.beatmap.pr_miss, 3) + self.assertEqual(self.beatmap.pr_pp, 28.0404) + self.assertEqual(self.beatmap.pr_rank, 'C') + self.assertEqual(self.beatmap.pr_mods, 'HDHRDT') + self.assertEqual(self.beatmap.pr_beatmap_url, 'https://osu.ppy.sh/beatmaps/75') + self.assertEqual(self.beatmap.pr_username, 'LittleStone') + self.assertEqual(self.beatmap.pr_acc, 83.85) + + def test_get_beatmap_score_with_wrong_username_1(self): + """ + 发送错误的username查询分数-场景1 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置错误的 username,并捕获输出 + with patch('sys.stdout', new=StringIO()) as fake_out: + self.beatmap.get_user_id("PPYNOTPPYGUESSIMPPYORNOT") + self.assertIn("获取用户ID失败", fake_out.getvalue(), "输出不包含预期的字符串") + + def test_get_beatmap_score_with_wrong_username_2(self): + """ + 发送错误的username查询分数-场景2 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 username + self.beatmap.get_user_id("LittleStone") + self.assertIsNotNone(self.beatmap.id2name, "用户ID获取失败") + + # 设置 beatmap_id + self.beatmap.beatmap_id = '75' # osu第一个ranked图 + + # 设置错误的 username,并捕获输出 + with patch('sys.stdout', new=StringIO()) as fake_out: + self.beatmap.get_beatmap_score("ATRI1024") + self.assertIn("获取谱面成绩失败,错误信息:'ATRI1024'\n| [ 获取谱面成绩失败 - ]| *| [ ] 0pp acc:0% combo:0x| 0/0/0/0| date:|", + fake_out.getvalue(), "输出不包含预期的字符串") + + def test_get_beatmap_score_with_wrong_beatmap_id(self): + """ + 发送错误的beatmap id查询分数 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 username + self.beatmap.get_user_id("LittleStone") + self.assertIsNotNone(self.beatmap.id2name, "用户ID获取失败") + + # 设置 beatmap_id + self.beatmap.beatmap_id = '1145141919810' # 不存在的图 + + # 设置错误的 username,并捕获输出 + with patch('sys.stdout', new=StringIO()) as fake_out: + self.beatmap.get_beatmap_score("LittleStone") + self.assertIn("未查询到LittleStone在该谱面上留下的成绩\n", fake_out.getvalue(), "输出不包含预期的字符串") + + @patch('irc_dlient.requests.get') + def test_get_recent_info_success(self, mock_get): + """ + 发送正确的username查询最近成绩,并模拟返回数据 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 username + self.beatmap.get_user_id("LittleStone") + self.beatmap.id2name = {"LittleStone": "123456"} + + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.json.return_value = [ + { + "beatmap": { + "id": 123456, + "difficulty_rating": 5.25, + "url": "https://osu.ppy.sh/beatmapsets/123456#osu/654321" + }, + "beatmapset": { + "title_unicode": "示例歌曲", + "artist_unicode": "示例艺术家" + }, + "accuracy": 0.98, + "max_combo": 1000, + "statistics": { + "count_300": 950, + "count_100": 30, + "count_50": 10, + "count_miss": 5 + }, + "pp": 250.75, + "rank": "S", + "mods": ["HD", "HR"] + } + ] + mock_get.return_value = mock_response + + self.beatmap.get_recent_info("LittleStone") + + self.assertEqual(self.beatmap.pr_beatmap_id, 123456) + self.assertEqual(self.beatmap.pr_title, '示例歌曲') + self.assertEqual(self.beatmap.pr_artist, '示例艺术家') + self.assertEqual(self.beatmap.pr_star, 5.25) + self.assertEqual(self.beatmap.pr_acc, 98) + self.assertEqual(self.beatmap.pr_maxcombo, 1000) + self.assertEqual(self.beatmap.pr_300, 950) + self.assertEqual(self.beatmap.pr_100, 30) + self.assertEqual(self.beatmap.pr_50, 10) + self.assertEqual(self.beatmap.pr_miss, 5) + self.assertEqual(self.beatmap.pr_pp, 250.75) + self.assertEqual(self.beatmap.pr_rank, 'S') + self.assertEqual(self.beatmap.pr_mods, 'HDHR') + self.assertEqual(self.beatmap.pr_beatmap_url, 'https://osu.ppy.sh/beatmapsets/123456#osu/654321') + self.assertEqual(self.beatmap.pr_username, 'LittleStone') + + def test_get_recent_info_with_wrong_username(self): + """ + 发送错误的username查询最近成绩 + """ + # 获取 Token + self.beatmap.get_token() + self.assertIsNotNone(self.beatmap.osu_token, "Token 获取失败") + + # 设置 username + self.beatmap.get_user_id("LittleStone") + self.beatmap.id2name = {"LittleStone": "123456"} + + # 调用获取最近成绩的方法 + with patch('sys.stdout', new=StringIO()) as fake_out: + self.beatmap.get_recent_info("ATRI1024") + self.assertIn("获取最近成绩失败,错误信息:'ATRI1024'\n| [ 获取最近成绩失败 - ]| *| [ ] 0pp acc:0% combo:0x| 0/0/0/0|\n", + fake_out.getvalue(), "输出不包含预期的字符串") + +class TestPP(unittest.TestCase): + @patch('irc_dlient.os.path.exists') + @patch('irc_dlient.requests.get') + def test_get_beatmap_file_exists(self, mock_get, mock_exists): + mock_exists.return_value = True + pp = irc_dlient.PP() + pp.get_beatmap_file('12345') + mock_get.assert_not_called() + + @patch('irc_dlient.os.path.exists') + @patch('irc_dlient.requests.get') + def test_get_beatmap_file_download(self, mock_get, mock_exists): + mock_exists.return_value = False + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.content = b'osu file content' + mock_get.return_value = mock_response + + pp = irc_dlient.PP() + pp.get_beatmap_file('12345') + + mock_get.assert_called_with('https://osu.ppy.sh/osu/12345') + # 可以进一步检查文件写入,但需要更多的patching + +class TestRoom(unittest.TestCase): + @patch('builtins.open') + def test_save_last_room_id(self, mock_open): + mock_file = MagicMock() + mock_open.return_value.__enter__.return_value = mock_file + + room = irc_dlient.Room() + room.room_id = '#room123' + room.save_last_room_id() + + mock_file.write.assert_called_with('#room123') + + @patch('builtins.open', side_effect=FileNotFoundError) + def test_get_last_room_id_file_not_found(self, mock_open): + room = irc_dlient.Room() + last_id = room.get_last_room_id() + self.assertEqual(last_id, '') + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 40f6719f1a2cd797f792301e35ae5a54a6c3f8e6 Mon Sep 17 00:00:00 2001 From: LittleStone <1526631427@qq.com> Date: Tue, 5 Nov 2024 17:28:42 +0800 Subject: [PATCH 4/9] =?UTF-8?q?fix:=E4=B8=8A=E4=BC=A0=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E7=8E=87HTML=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- htmlcov/.gitignore | 2 + htmlcov/class_index.html | 163 ++++ htmlcov/coverage_html_cb_497bf287.js | 733 ++++++++++++++ htmlcov/favicon_32_cb_58284776.png | Bin 0 -> 1732 bytes htmlcov/function_index.html | 643 +++++++++++++ htmlcov/index.html | 111 +++ htmlcov/irc_dlient_py.html | 1330 ++++++++++++++++++++++++++ htmlcov/keybd_closed_cb_ce680311.png | Bin 0 -> 9004 bytes htmlcov/status.json | 1 + htmlcov/style_cb_718ce007.css | 337 +++++++ test_irc_client.py | 154 --- 11 files changed, 3320 insertions(+), 154 deletions(-) create mode 100644 htmlcov/.gitignore create mode 100644 htmlcov/class_index.html create mode 100644 htmlcov/coverage_html_cb_497bf287.js create mode 100644 htmlcov/favicon_32_cb_58284776.png create mode 100644 htmlcov/function_index.html create mode 100644 htmlcov/index.html create mode 100644 htmlcov/irc_dlient_py.html create mode 100644 htmlcov/keybd_closed_cb_ce680311.png create mode 100644 htmlcov/status.json create mode 100644 htmlcov/style_cb_718ce007.css delete mode 100644 test_irc_client.py diff --git a/htmlcov/.gitignore b/htmlcov/.gitignore new file mode 100644 index 0000000..ccccf14 --- /dev/null +++ b/htmlcov/.gitignore @@ -0,0 +1,2 @@ +# Created by coverage.py +* diff --git a/htmlcov/class_index.html b/htmlcov/class_index.html new file mode 100644 index 0000000..bcce43d --- /dev/null +++ b/htmlcov/class_index.html @@ -0,0 +1,163 @@ + + +
+ ++ coverage.py v7.6.4, + created at 2024-11-05 17:25 +0800 +
+File | +class | +statements | +missing | +excluded | +coverage | +
---|---|---|---|---|---|
irc_dlient.py | +Config | +12 | +0 | +0 | +100% | +
irc_dlient.py | +MyIRCClient | +285 | +285 | +0 | +0% | +
irc_dlient.py | +Player | +90 | +74 | +0 | +18% | +
irc_dlient.py | +Room | +44 | +32 | +0 | +27% | +
irc_dlient.py | +Beatmap | +207 | +23 | +0 | +89% | +
irc_dlient.py | +PP | +162 | +127 | +0 | +22% | +
irc_dlient.py | +(no class) | +101 | +14 | +0 | +86% | +
Total | ++ | 901 | +555 | +0 | +38% | +
+ No items found using the specified filter. +
++ coverage.py v7.6.4, + created at 2024-11-05 17:25 +0800 +
++ No items found using the specified filter. +
++ coverage.py v7.6.4, + created at 2024-11-05 17:25 +0800 +
+File | +statements | +missing | +excluded | +coverage | +
---|---|---|---|---|
irc_dlient.py | +901 | +555 | +0 | +38% | +
Total | +901 | +555 | +0 | +38% | +
+ No items found using the specified filter. +
++ « prev + ^ index + » next + + coverage.py v7.6.4, + created at 2024-11-05 17:25 +0800 +
+ +1import irc.client
+2import re
+3import requests
+ +5import threading
+6import chardet
+7import configparser
+ +9from requests.exceptions import HTTPError
+ +11from datetime import datetime
+ +13import rosu_pp_py as rosu
+ +15import os
+16import json
+17import time
+ +19osu_server = "irc.ppy.sh"
+20osu_port = 6667
+ + +23class Config:
+24 def __init__(self):
+25 self.config = configparser.ConfigParser()
+26 with open('config.ini', 'rb') as f:
+27 encoding = chardet.detect(f.read())['encoding']
+28 self.config.read('config.ini', encoding=encoding)
+29 self.osuclientid = self.config['OSUAPI']['client_id']
+30 self.osuclientsecret = self.config['OSUAPI']['client_secret']
+31 self.osunickname = self.config['OSUAPI']['nickname']
+32 self.osupassword = self.config['OSUAPI']['password']
+33 self.mpname = self.config['OSU']['mpname']
+34 self.starlimit = self.config['OSU']['starlimit']
+35 self.timelimit = self.config['OSU']['timelimit']
+36 self.mppassword = self.config['OSU']['mppassword']
+ +38# 定义IRC客户端类
+39class MyIRCClient:
+40 def __init__(self, server, port, nickname, password):
+41 self.irc_react = irc.client.Reactor()
+42 self.server = self.irc_react.server()
+43 self.server.connect(server, port, nickname, password)
+44 self.irc_react.add_global_handler("welcome", self.on_connect)
+45 self.irc_react.add_global_handler("pubmsg", self.on_pubmsg)
+46 self.irc_react.add_global_handler("privmsg", self.on_privmsg)
+47 self.timer = None # 定义定时器
+48 self.restarting_task = threading.Thread(target=(self.restart))
+ +50 def start(self):
+51 self.irc_react.process_forever()
+ +53 def reset_all(self):
+54 # 重置
+55 p.reset_player_list()
+56 p.reset_host_list()
+57 p.clear_approved_list()
+58 b.clear_cache()
+ +60 def restart(self):
+61 print(f'尝试重启...{datetime.now()+datetime.timedelta(hours=8)}')
+62 time.sleep(120)
+63 self.reset_all()
+64 r.create_room(self.server, "")
+65 self.restarting_task = threading.Thread(target=(self.restart))
+66 print(f'重启完成{datetime.now()+datetime.timedelta(hours=8)}')
+ +68 # 定义定时任务,每60s执行一次,检查房间状态
+69 def start_periodic_task(self):
+70 # Save the Timer object in an instance variable
+71 self.timer = threading.Timer(60, self.start_periodic_task)
+72 self.timer.start()
+73 self.check_room_status(r.room_id)
+ +75 # 停止定时任务
+76 def stop_periodic_task(self):
+77 if self.timer is not None:
+78 self.timer.cancel()
+79 self.timer = None
+ +81 def check_last_room_status(self,last_room_id):
+82 if last_room_id == "":
+83 return False
+84 b.get_token()
+85 try:
+86 text = b.get_match_info(re.findall(r'\d+', last_room_id)[0])
+87 except:
+88 text = ""
+89 # match-disbanded #比赛关闭
+90 if "match-disbanded" in str(text['events']):
+91 return False
+92 else:
+93 r.change_room_id(last_room_id)
+94 return True
+ +96 # 这是检查房间状态的流程 用于定时任务
+97 def check_room_status(self, room_id):
+98 b.get_token()
+99 try:
+100 text = b.get_match_info(re.findall(r'\d+', room_id)[0])
+101 except:
+102 text = ""
+103 # match-disbanded #比赛关闭
+104 try:
+105 if ("match-disbanded" in str(text['events'])) == True:
+106 self.stop_periodic_task()
+107 # 重置
+108 p.reset_player_list()
+109 p.reset_host_list()
+110 p.clear_approved_list()
+111 p.approved_host_rotate_list.clear()
+112 b.clear_cache()
+113 # 尝试重新创建房间
+114 try:
+115 r.create_room(self.server, "")
+116 except:
+117 print("创建房间失败")
+118 self.timer.start()
+119 except:
+120 print("无法判断比赛信息")
+ +122 def export_json(self):
+123 result = {}
+124 result['player_list'] = p.player_list
+125 result['beatmap_name'] = b.beatmap_name
+126 result['beatmap_artist'] = b.beatmap_artist
+127 result['beatmap_star'] = b.beatmap_star
+ +129 try:
+130 with open('data.json', 'w', encoding='utf-8') as f:
+131 json.dump(result, f)
+132 print("导出json")
+133 except:
+134 print("导出json失败")
+ +136 def on_connect(self, connection, event):
+137 last_room_id = r.get_last_room_id()
+ +139 # 如果房间存在
+140 if self.check_last_room_status(last_room_id):
+141 r.join_room(connection, event)
+142 r.change_password(connection, event)
+143 r.get_mp_settings(connection, event)
+144 try:
+145 self.start_periodic_task()
+146 except:
+147 print("定时任务启动失败")
+148 # 如果房间不存在
+149 else:
+150 r.create_room(connection, event)
+ +152 def send_loop():
+153 while True:
+154 message = input(">")
+155 r.send_msg(connection, event, message)
+ +157 threading.Thread(target=(send_loop)).start()
+ +159 def on_privmsg(self, connection, event):
+160 # 打印接收到的私人消息
+161 print(f"收到私人消息 {event.source.split('!')[0]}:{event.arguments[0]}")
+162 text = event.arguments[0]
+163 # 匹配第一次收到的房间号
+164 if text.find("Created the tournament match") != -1 and event.source.find("BanchoBot") != -1:
+165 try:
+166 romm_id = "#mp_"+re.findall(r'\d+', text)[0]
+167 except:
+168 romm_id = ""
+169 # 更新room变量
+170 r.change_room_id(romm_id)
+171 # 加入并监听房间
+172 r.join_room(connection, event)
+173 # 修改房间密码
+174 r.change_password(connection, event)
+175 # 保存房间IDs
+176 r.save_last_room_id()
+177 # 启动定时任务
+178 self.start_periodic_task()
+ +180 def on_pubmsg(self, connection, event):
+ +182 try:
+183 # 打印接收到的消息
+184 print(f"收到频道消息 {event.source.split('!')[0]}:{event.arguments[0]}")
+185 text = event.arguments[0]
+186 # 判断是否是banchobot发送的消息
+187 if event.source.find("BanchoBot") != -1 or event.source.find("ATRI1024") != -1:
+188 # 加入房间
+189 if text.find("joined in slot") != -1:
+190 # 尝试
+191 try:
+192 playerid = re.findall(
+193 r'.*(?= joined in slot)', text)[0]
+194 except:
+195 playerid = ""
+196 print(f'玩家{playerid}加入房间')
+197 # 发送欢迎消息
+198 if "ATRI1024" not in playerid:
+199 if b.beatmap_length != "" and r.game_start_time != "":
+200 timeleft = int(b.beatmap_length)+10 - \
+201 int((datetime.now()-r.game_start_time).seconds)
+202 text_timeleft = f'| 剩余游玩时间:{timeleft}s 请主人耐心等待哦~'
+203 else:
+204 timeleft = 0
+205 text_Welcome = f'欢迎{playerid}酱~\(≧▽≦)/ 输入help获取指令详情'
+206 if timeleft > 0:
+207 r.send_msg(connection, event,
+208 text_Welcome+text_timeleft)
+209 else:
+210 r.send_msg(connection, event, text_Welcome)
+211 # 如果第一次加入房间,更换房主,清空房主队列,设置FM
+212 if len(p.player_list) == 0:
+213 p.reset_host_list()
+214 r.change_host(connection, event, playerid)
+215 r.change_mods_to_FM(connection, event)
+216 # 加入房间队列,玩家队列
+217 p.add_host(playerid)
+218 p.add_player(playerid)
+219 print(f'玩家队列{p.player_list}')
+220 print(f'房主队列{p.room_host_list}')
+221 # 输出
+222 self.export_json()
+223 # 离开房间
+224 if text.find("left the game") != -1:
+225 # 尝试
+226 try:
+227 playerid = re.findall(r'.*(?= left the game)', text)[0]
+228 except:
+229 playerid = ""
+230 print(f'玩家{playerid}离开房间')
+231 # 不移除房主队列
+232 p.remove_player(playerid)
+233 # 房主离开立刻更换房主
+234 if playerid == p.room_host and len(p.player_list) != 0:
+235 p.host_rotate(connection, event)
+236 print(f'玩家队列{p.player_list}')
+237 # 输出
+238 self.export_json()
+239 # 修改 on_pubmsg 方法中处理玩家列表的部分
+240 if text.find("Slot") != -1:
+241 players = re.findall(r'Slot \d+\s+(?:Not Ready|Ready)\s+(https://osu\.ppy\.sh/u/\d+\s+.+)', text)
+242 if players:
+243 for player_info in players:
+244 player_id = p.extract_player_name(player_info)
+245 if player_id:
+246 p.add_host(player_id)
+247 p.add_player(player_id)
+248 print(f'玩家队列{p.player_list}')
+249 print(f'房主队列{p.room_host_list}')
+250 # 输出
+251 self.export_json()
+252 # 这个是加入房间后要把当前beatmap_id给change一下
+253 if text.find("Beatmap:") != -1:
+254 match = re.search(r'https://osu\.ppy\.sh/b/(\d+)', text)
+255 if match:
+256 beatmap_id = match.group(1)
+257 b.change_beatmap_id(beatmap_id)
+ +259 # 谱面变化
+260 if text.find("Beatmap changed to") != -1:
+261 # 尝试
+262 try:
+263 beatmap_url = re.findall(r'(?<=\()\S+(?=\))', text)[0]
+264 beatmap_id = re.findall(r'\d+', beatmap_url)[0]
+265 except:
+266 beatmap_url = ""
+267 beatmap_id = ""
+ +269 last_beatmap_id = b.beatmap_id
+270 if last_beatmap_id == "":
+271 last_beatmap_id = "3459231"
+272 b.change_beatmap_id(beatmap_id)
+273 # 获取谱面信息
+274 b.get_token()
+275 b.get_beatmap_info()
+ +277 if b.check_beatmap_if_out_of_star():
+278 r.send_msg(connection, event,
+279 f'{b.beatmap_star}*>{config.starlimit}* 请重新选择')
+280 r.change_beatmap_to(connection, event, last_beatmap_id)
+281 b.change_beatmap_id(last_beatmap_id)
+282 return
+283 if b.check_beatmap_if_out_of_time():
+284 r.send_msg(connection, event,
+285 f'{b.beatmap_length}s>{config.timelimit}s 请重新选择')
+286 r.change_beatmap_to(connection, event, last_beatmap_id)
+287 b.change_beatmap_id(last_beatmap_id)
+288 return
+ +290 r.send_msg(connection, event, b.return_beatmap_info())
+291 # 输出
+292 self.export_json()
+ +294 # 房主变化
+295 if text.find("became the host") != -1:
+296 # 尝试
+297 try:
+298 p.room_host = re.findall(
+299 r'.*(?= became the host)', text)[0]
+300 except:
+301 p.room_host = ""
+302 print(f'房主变为{p.room_host}')
+ +304 # 准备就绪,开始游戏
+305 if text.find("All players are ready") != -1:
+306 r.start_room(connection, event)
+ +308 # 开始游戏
+309 if text.find("The match has started") != -1:
+310 # 将房主队列第一个人移动到最后
+311 p.host_rotate_pending(connection, event)
+312 print(f'游戏开始,房主队列{p.room_host_list}')
+313 p.clear_approved_list()
+314 # 获取游戏开始时间
+315 r.set_game_start_time()
+ +317 # 游戏结束,更换房主
+318 if text.find("The match has finished") != -1:
+319 # 对比房主队列,去除离开的玩家,更新房主队列
+320 p.host_rotate(connection, event)
+321 print(f'游戏结束,房主队列{p.room_host_list}')
+322 # 换了房主以后立即清空投票列表
+323 p.approved_host_rotate_list.clear()
+324 p.clear_approved_list()
+325 # 发送队列
+326 p.convert_host()
+327 r.send_msg(connection, event, str(
+328 f'当前队列:{p.room_host_list_apprence_text}'))
+329 # 重置游戏开始时间
+330 r.reset_game_start_time()
+ +332 # 游戏被丢弃
+333 if text.find("Aborted the match") != -1:
+334 # 判断游戏是否结束
+335 timeleft = int(b.beatmap_length)+10 - \
+336 int((datetime.now()-r.game_start_time).seconds)
+337 if timeleft > 0: # 大于0代表没打,先不更换房主,退回队列
+338 p.reverse_host_pending(connection, event)
+339 print("比赛被丢弃,房主队列退回")
+340 else: # 小于0代表已经打完,更换房主
+341 # 对比房主队列,去除离开的玩家,更新房主队列
+342 p.host_rotate(connection, event)
+343 print(f'游戏结束,房主队列{p.room_host_list}')
+344 # 换了房主以后立即清空投票列表
+345 p.approved_host_rotate_list.clear()
+346 p.clear_approved_list()
+347 # 发送队列
+348 p.convert_host()
+349 r.send_msg(connection, event, str(
+350 f'当前队列:{p.room_host_list_apprence_text}'))
+351 # 重置游戏开始时间
+352 r.reset_game_start_time()
+353 # bancho重启
+354 if text.find("Bancho will be right back!") != -1:
+355 r.send_msg(connection, event,
+356 "Bancho重启中,房间将在2min后自动重启")
+357 self.restarting_task.start()
+ +359 # 玩家发送的消息响应部分
+ +361 # 投票丢弃游戏
+362 if text in ["!abort", "!abort", "!ABORT", "!ABORT"]:
+363 p.vote_for_abort(connection, event)
+ +365 # 投票开始游戏
+366 if text in ["!start", "!start", "!START", "!START"]:
+367 p.vote_for_start(connection, event)
+ +369 # 投票跳过房主
+370 if text in ["!skip", "!skip", "!SKIP", "!SKIP"]:
+371 p.vote_for_host_rotate(connection, event)
+ +373 # 投票关闭房间s
+374 if text in ["!close", "!close", "!CLOSE", "!CLOSE"]:
+375 p.vote_for_close_room(connection, event)
+ +377 # 手动查看队列,就只返回前面剩余多少人
+378 if text in ["!queue", "!queue", "!QUEUE", "!QUEUE", "!q", "!q", "!Q", "!Q"]:
+379 p.convert_host()
+380 index = p.remain_hosts_to_player(event.source.split('!')[0])
+381 r.send_msg(connection, event, str(
+382 f'你前面剩余人数:{index}'))
+ +384 # 帮助
+385 if text in ["help", "HELP", "!help", "!help", "!HELP", "!HELP", "!h", "!h", "!H", "!H"]:
+386 r.send_msg(connection, event, r.help())
+ +388 # ping
+389 if text in ["ping", "PING", "!ping", "!ping", "!PING", "!PING"]:
+390 r.send_msg(connection, event, r'pong')
+ +392 # 快速查询成绩
+393 if text in ["!pr", "!pr", "!PR", "!PR", "!p", "!p", "!P", "!P"]:
+394 b.get_user_id(event.source.split('!')[0])
+395 detail_1 = b.get_recent_info(event.source.split('!')[0])
+396 pp.get_beatmap_file(beatmap_id=b.pr_beatmap_id)
+397 print(b.pr_mods)
+398 detail_2 = pp.calculate_pp_obj(
+399 mods=b.pr_mods, combo=b.pr_maxcombo, acc=b.pr_acc, misses=b.pr_miss)
+400 r.send_msg(connection, event, detail_1)
+401 r.send_msg(connection, event, detail_2)
+ +403 # 快速当前谱面成绩
+404 if text in ["!s", "!s", "!S", "!S"]:
+405 b.get_user_id(event.source.split('!')[0])
+406 s = b.get_beatmap_score(event.source.split('!')[0])
+407 r.send_msg(connection, event, s)
+408 if s.find("未查询到") == -1:
+409 pp.get_beatmap_file(beatmap_id=b.beatmap_id)
+410 r.send_msg(connection, event, pp.calculate_pp_obj(
+411 mods=b.pr_mods, combo=b.pr_maxcombo, acc=b.pr_acc, misses=b.pr_miss))
+ +413 # 快速查询谱面得分情况
+414 if text.find("!m+") != -1:
+415 try:
+416 modslist_str = re.findall(r'\+(.*)', event.arguments[0])[0]
+417 except:
+418 modslist_str = ""
+419 pp.get_beatmap_file(beatmap_id=b.beatmap_id)
+420 r.send_msg(connection, event, pp.calculate_pp_fully(modslist_str))
+421 if text.find("!M+") != -1:
+422 try:
+423 modslist_str = re.findall(r'\+(.*)', event.arguments[0])[0]
+424 except:
+425 modslist_str = ""
+426 pp.get_beatmap_file(beatmap_id=b.beatmap_id)
+427 r.send_msg(connection, event, pp.calculate_pp_fully(modslist_str))
+ +429 if text.find("!M+") != -1:
+430 try:
+431 modslist_str = re.findall(r'\+(.*)', event.arguments[0])[0]
+432 except:
+433 modslist_str = ""
+434 pp.get_beatmap_file(beatmap_id=b.beatmap_id)
+435 r.send_msg(connection, event, pp.calculate_pp_fully(modslist_str))
+ +437 if text.find("!M+") != -1:
+438 try:
+439 modslist_str = re.findall(r'\+(.*)', event.arguments[0])[0]
+440 except:
+441 modslist_str = ""
+442 pp.get_beatmap_file(beatmap_id=b.beatmap_id)
+443 r.send_msg(connection, event, pp.calculate_pp_fully(modslist_str))
+ +445 if text in ["!m", "!m", "!M", "!M"]:
+446 pp.get_beatmap_file(beatmap_id=b.beatmap_id)
+447 r.send_msg(connection, event,pp.calculate_pp_fully(''))
+ +449 # 快速获取剩余时间 大约10s游戏延迟
+450 if text in ["!ttl", "!ttl", "!TTL", "!TTL"]:
+451 if b.beatmap_length != "" and r.game_start_time != "":
+452 timeleft = int(b.beatmap_length)+10 - \
+453 int((datetime.now()-r.game_start_time).seconds)
+454 r.send_msg(connection, event, f'剩余游玩时间:{timeleft}s')
+455 else:
+456 r.send_msg(connection, event, f'剩余游玩时间:未知')
+ +458 if text in ["!i", "!i"]:
+459 b.get_token()
+460 b.get_beatmap_info()
+461 r.send_msg(connection, event, b.return_beatmap_info())
+ +463 if text in ["!about", "!about", "!ABOUT", "!ABORT"]:
+464 r.send_msg(connection, event,
+465 "[https://github.com/Ohdmire/osu-ircbot-py ATRI高性能bot]")
+ +467 except Exception as e:
+468 print(f'-----------------未知错误---------------------\n{e}')
+ + +471# 定义玩家类
+472class Player:
+473 def __init__(self):
+474 self.player_list = []
+475 self.room_host_list = []
+476 self.room_host_list_apprence_text = ""
+477 self.approved_abort_list = []
+478 self.approved_start_list = []
+479 self.approved_host_rotate_list = []
+480 self.approved_close_list = []
+481 self.room_host = ""
+ +483 def add_player(self, name):
+484 if name not in self.player_list:
+485 self.player_list.append(name)
+ +487 def add_host(self, name):
+488 if name not in self.room_host_list:
+489 self.room_host_list.append(name)
+ +491 def remove_host(self, name):
+492 if name in self.room_host_list:
+493 self.room_host_list.remove(name)
+ +495 def remain_hosts_to_player(self, name):
+496 name_normalized = name.replace(" ", "_")
+497 for index, host in enumerate(self.room_host_list):
+498 host_normalized = host.replace(" ", "_")
+499 if name_normalized == host_normalized:
+500 return index
+501 print(f"{name} is not in room_host_list", self.room_host_list)
+502 return -1 # 如果没有找到匹配的名字,返回-1
+ +504 def extract_player_name(self, text):
+505 match = re.search(r'https://osu\.ppy\.sh/u/\d+\s*(.*?)(?:\s*\[.*\])?$', text)
+506 if match:
+507 playername = match.group(1).strip()
+508 return playername
+509 return ""
+ +511 def convert_host(self):
+512 try:
+513 self.room_host_list_apprence_text = ""
+514 for index, host in enumerate(self.room_host_list):
+515 if index == 0:
+516 self.room_host_list_apprence_text += host
+517 else:
+518 # 在每个字符之间插入零宽空格
+519 self.room_host_list_apprence_text += "\u200B".join(host)
+ +521 if index < len(self.room_host_list) - 1:
+522 self.room_host_list_apprence_text += "-->"
+523 except:
+524 print("房主队列转换失败")
+ +526 def remove_player(self, name):
+527 if name in self.player_list:
+528 self.player_list.remove(name)
+ +530 def reset_player_list(self):
+531 self.player_list.clear()
+ +533 def reset_host_list(self):
+534 self.room_host_list.clear()
+ +536 def clear_approved_list(self):
+537 self.approved_abort_list.clear()
+538 self.approved_start_list.clear()
+539 self.approved_close_list.clear()
+540 self.approved_host_rotate_list.clear()
+ +542 def host_rotate_pending(self, connection, event):
+543 now_host = self.room_host_list[0]
+544 self.remove_host(now_host)
+545 self.add_host(now_host)
+ +547 def reverse_host_pending(self, connection, event):
+548 self.remove_host(self.room_host)
+549 self.room_host_list.insert(0, self.room_host)
+ +551 def host_rotate(self, connection, event):
+552 result_list = []
+553 for i in self.room_host_list:
+554 if i in self.player_list:
+555 result_list.append(i)
+556 self.room_host_list = result_list
+557 r.change_host(connection, event, self.room_host_list[0])
+ +559 def vote_for_abort(self, connection, event):
+560 # 获取发送者名字
+561 name = event.source.split('!')[0]
+562 if name not in self.approved_abort_list:
+563 self.approved_abort_list.append(name)
+564 if len(self.approved_abort_list) >= round(len(self.player_list)/2):
+565 r.abort_room(connection, event)
+566 self.approved_abort_list.clear()
+567 else:
+568 r.send_msg(connection, event, r'输入!abort强制放弃比赛 {} / {} '.format(
+569 str(len(self.approved_abort_list)), str(round(len(self.player_list)/2))))
+ +571 def vote_for_start(self, connection, event):
+572 # 获取发送者名字
+573 name = event.source.split('!')[0]
+574 if name not in self.approved_start_list:
+575 self.approved_start_list.append(name)
+576 if len(self.approved_start_list) >= round(len(self.player_list)/2):
+577 r.start_room(connection, event)
+578 self.approved_start_list.clear()
+579 else:
+580 r.send_msg(connection, event, r'输入!start强制开始比赛 {} / {} '.format(
+581 str(len(self.approved_start_list)), str(round(len(self.player_list)/2))))
+ +583 def vote_for_host_rotate(self, connection, event):
+584 # 获取发送者名字
+585 name = event.source.split('!')[0]
+586 # 如果发送者是房主,直接换房主
+587 if name == self.room_host:
+588 self.host_rotate_pending(connection, event)
+589 self.host_rotate(connection, event)
+590 self.approved_host_rotate_list.clear()
+591 print("房主自行更换")
+592 return
+593 if name not in self.approved_host_rotate_list:
+594 self.approved_host_rotate_list.append(name)
+595 if len(self.approved_host_rotate_list) >= round(len(self.player_list)/2):
+596 self.host_rotate_pending(connection, event)
+597 self.host_rotate(connection, event)
+598 self.approved_host_rotate_list.clear()
+599 else:
+600 r.send_msg(connection, event, r'输入!skip强制跳过房主 {} / {} '.format(
+601 str(len(self.approved_host_rotate_list)), str(round(len(self.player_list)/2))))
+ +603 def vote_for_close_room(self, connection, event):
+604 # 获取发送者名字
+605 name = event.source.split('!')[0]
+606 if name not in self.approved_close_list:
+607 self.approved_close_list.append(name)
+608 if len(self.approved_close_list) == len(self.player_list):
+609 r.close_room(connection, event)
+610 self.approved_close_list.clear()
+611 else:
+612 r.send_msg(connection, event, r'输入!close强制关闭房间(1min后自动重启) {} / {} '.format(
+613 str(len(self.approved_close_list)), str(len(self.player_list))))
+ + +616# 定义房间操作类
+617class Room:
+618 def __init__(self):
+619 self.room_id = ""
+620 self.last_romm_id = ""
+621 self.game_start_time = ""
+ +623 def set_game_start_time(self):
+624 self.game_start_time = datetime.now()
+625 return self.game_start_time
+ +627 def reset_game_start_time(self):
+628 self.game_start_time = ""
+ +630 def get_last_room_id(self):
+631 try:
+632 with open('last_room_id.txt', 'r') as f:
+633 self.last_romm_id = f.read()
+634 print(f'获取上一个房间ID{self.last_romm_id}')
+635 except:
+636 print("未获取上一个房间ID")
+637 return self.last_romm_id
+ +639 # 保存当前id到文件
+640 def save_last_room_id(self):
+641 try:
+642 with open('last_room_id.txt', 'w') as f:
+643 f.write(self.room_id)
+644 print(f'保存当前房间ID{self.room_id}')
+645 except:
+646 print("未保存当前房间ID")
+ +648 def help(self):
+649 return r'!queue(!q) 查看队列 | !abort 投票丢弃游戏 | !start 投票开始游戏 | !skip 投票跳过房主 | !pr(!p) 查询最近成绩 | !s 查询当前谱面bp | !m+{MODS} 查询谱面模组PP| !i 返回当前谱面信息| !ttl 查询剩余时间 | !close 投票关闭(1min后自动重启)房间 | help(!h) 查看帮助 | !about 关于机器人'
+ +651 def change_room_id(self, id):
+652 self.room_id = id
+653 print(f'更换当前房间ID为{self.room_id}')
+ +655 def send_msg(self, connection, evetn, msg_text):
+656 connection.privmsg(self.room_id, msg_text)
+657 print("发送消息:"+msg_text)
+ +659 def create_room(self, connection, event):
+660 connection.privmsg(
+661 "BanchoBot", "!mp make "+config.mpname)
+662 print("创建房间")
+ +664 def join_room(self, connection, event):
+665 connection.join(self.room_id) # 加入 #osu 频道
+666 print(f'加入房间{self.room_id}')
+ +668 def close_room(self, connection, event):
+669 connection.privmsg(self.room_id, "!mp close")
+670 print(f'关闭房间{self.room_id}')
+ +672 def change_host(self, connection, event, playerid):
+673 connection.privmsg(self.room_id, "!mp host "+playerid)
+674 print("更换房主为 "+playerid)
+ +676 def start_room(self, connection, event):
+677 connection.privmsg(self.room_id, "!mp start")
+678 print("开始游戏")
+ +680 def abort_room(self, connection, event):
+681 connection.privmsg(self.room_id, "!mp abort")
+682 print("丢弃游戏")
+ +684 def change_password(self, connection, event):
+685 connection.privmsg(self.room_id, "!mp password "+config.mppassword)
+686 print("修改密码")
+ +688 def change_beatmap_to(self, connection, event, beatmapid):
+689 connection.privmsg(self.room_id, "!mp map "+beatmapid)
+690 print("更换谱面为"+beatmapid)
+ +692 def change_mods_to_FM(self, connection, event):
+693 connection.privmsg(self.room_id, "!mp mods FreeMod")
+694 print("开启Freemod")
+ +696 def get_mp_settings(self, connection, event):
+697 connection.privmsg(self.room_id, "!mp settings")
+698 print("获取房间详情成功")
+ + +701# 定义谱面类
+702class Beatmap:
+703 def __init__(self, client_id, client_secret):
+704 self.osu_client_id = client_id
+705 self.osu_client_secret = client_secret
+706 self.osu_token = ""
+707 self.beatmap_id = ""
+708 self.beatmap_songs_id = ""
+709 self.beatmap_name = ""
+710 self.beatmap_artist = ""
+711 self.beatmap_star = 0
+712 self.beatmap_status = ""
+713 self.beatemap_bpm = ""
+714 self.beatmap_cs = ""
+715 self.beatmap_ar = ""
+716 self.beatmap_od = ""
+717 self.beatmap_hp = ""
+718 self.beatmap_length = 0
+719 self.beatmap_ranked_date = ""
+720 self.beatmatp_submit_date = ""
+721 self.beatmap_mirror_sayo_url = ""
+722 self.beatmap_osudirect_url = ""
+ +724 self.id2name = {}
+ +726 self.pr_beatmap_id = ""
+727 self.pr_beatmap_url = ""
+ +729 self.pr_title = ""
+730 self.pr_artist = ""
+731 self.pr_star = ""
+ +733 self.pr_acc = 0
+734 self.pr_maxcombo = 0
+735 self.pr_300 = 0
+736 self.pr_100 = 0
+737 self.pr_50 = 0
+738 self.pr_miss = 0
+739 self.pr_pp = 0
+740 self.pr_rank = ""
+741 self.pr_mods = ""
+ +743 self.pr_username = ""
+ +745 def clear_cache(self):
+746 self.osu_token = ""
+747 self.id2name.clear()
+ +749 def get_token(self):
+750 try:
+751 url = 'https://osu.ppy.sh/oauth/token'
+752 data = {
+753 'client_id': self.osu_client_id,
+754 'client_secret': self.osu_client_secret,
+755 'grant_type': 'client_credentials',
+756 'scope': 'public'
+757 }
+758 response = requests.post(url, data=data)
+759 response.raise_for_status() # 如果请求失败,这会抛出一个异常
+760 self.osu_token = response.json()['access_token']
+761 except:
+762 self.osu_token = ""
+763 print("获取访问令牌失败")
+ +765 # 使用访问令牌查询
+766 def get_beatmap_info(self):
+ +768 try:
+769 url = f'https://osu.ppy.sh/api/v2/beatmaps/'+self.beatmap_id
+770 headers = {'Authorization': f'Bearer {self.osu_token}'}
+771 response = requests.get(url, headers=headers)
+772 response.raise_for_status() # 如果请求失败,这会抛出一个异常
+ +774 self.beatmap_songs_id = str(response.json()['beatmapset_id'])
+ +776 self.beatmap_name = response.json()['beatmapset']['title_unicode']
+777 self.beatmap_artist = response.json()['beatmapset']['artist_unicode']
+778 self.beatmap_star = response.json()['difficulty_rating']
+779 self.beatmap_status = response.json()['status']
+780 self.beatmap_bpm = response.json()['bpm']
+781 self.beatmap_cs = response.json()['cs']
+782 self.beatmap_ar = response.json()['ar']
+783 self.beatmap_od = response.json()['accuracy']
+784 self.beatmap_hp = response.json()['drain']
+785 self.beatmap_length = response.json()['total_length']
+786 if self.beatmap_status == "ranked":
+787 self.beatmap_ranked_date = response.json(
+788 )['beatmapset']['ranked_date'][:10]
+789 else:
+790 self.beatmap_ranked_date = response.json(
+791 )['beatmapset']['submitted_date'][:10]
+792 self.beatmap_mirror_sayo_url = "https://osu.sayobot.cn/home?search="+self.beatmap_songs_id
+793 self.beatmap_mirror_inso_url = "http://inso.link/yukiho/?b="+self.beatmap_id
+794 self.beatmap_osudirect_url = response.json()['url']
+795 except Exception as e:
+796 print(f'获取谱面信息失败,原因:{e}')
+797 self.beatmap_name = "获取谱面信息失败"
+798 self.beatmap_songs_id = ""
+799 self.beatmap_artist = ""
+800 self.beatmap_star = 0
+801 self.beatmap_status = ""
+802 self.beatmap_bpm = ""
+803 self.beatmap_cs = ""
+804 self.beatmap_ar = ""
+805 self.beatmap_od = ""
+806 self.beatmap_hp = ""
+807 self.beatmap_length = 0
+808 self.beatmap_ranked_date = ""
+809 self.beatmap_mirror_sayo_url = ""
+810 self.beatmap_mirror_inso_url = ""
+811 self.beatmap_osudirect_url = ""
+ +813 def change_beatmap_id(self, id):
+814 self.beatmap_id = id
+815 print(f'更换谱面ID为 {self.beatmap_id}')
+ +817 def check_beatmap_if_out_of_star(self):
+818 if float(config.starlimit) == 0:
+819 return False
+820 if self.beatmap_star > float(config.starlimit):
+821 return True
+822 else:
+823 return False
+ +825 def check_beatmap_if_out_of_time(self):
+826 if float(config.timelimit) == 0:
+827 return False
+828 if self.beatmap_length > float(config.timelimit):
+829 return True
+830 else:
+831 return False
+ +833 def return_beatmap_info(self):
+834 result = r'{} {}| {}*| [{} {} - {}]| bpm:{} length:{}s| ar:{} cs:{} od:{} hp:{}| [{} Sayobot] OR [{} inso]'.format(self.beatmap_ranked_date, self.beatmap_status, self.beatmap_star, self.beatmap_osudirect_url,
+835 self.beatmap_name, self.beatmap_artist, self.beatemap_bpm, self.beatmap_length, self.beatmap_ar, self.beatmap_cs, self.beatmap_od, self.beatmap_hp, self.beatmap_mirror_sayo_url, self.beatmap_mirror_inso_url)
+836 print(result)
+837 return result
+ +839 def get_match_info(self, match_id):
+840 try:
+841 url = f'https://osu.ppy.sh/api/v2/matches/{match_id}'
+842 headers = {'Authorization': f'Bearer {self.osu_token}'}
+843 response = requests.get(url, headers=headers)
+844 response.raise_for_status() # 如果请求失败,这将抛出一个异常
+845 return response.json()
+846 except:
+847 print("获取比赛信息失败")
+848 return ""
+ +850 def get_user_id(self, username):
+851 try:
+852 if username not in self.id2name:
+853 print("获取用户ID")
+854 url = f'https://osu.ppy.sh/api/v2/users/{username}?key=username'
+855 headers = {'Authorization': f'Bearer {self.osu_token}'}
+856 response = requests.get(url, headers=headers)
+857 response.raise_for_status() # 如果请求失败,这将抛出一个异常
+858 self.id2name[username] = response.json()['id']
+859 print(self.id2name)
+860 except:
+861 print("获取用户ID失败")
+ +863 def get_beatmap_score(self, username):
+864 try:
+865 user_id = self.id2name[username]
+866 url = f"https://osu.ppy.sh/api/v2/beatmaps/{self.beatmap_id}/scores/users/{user_id}"
+867 headers = {'Authorization': f'Bearer {self.osu_token}'}
+868 response = requests.get(url, headers=headers)
+869 response.raise_for_status() # 如果请求失败,这会抛出一个异常
+ +871 self.pr_title = self.beatmap_name
+872 self.pr_artist = self.beatmap_artist
+873 self.pr_star = self.beatmap_star
+ +875 self.beatmap_score_created_at = response.json()[
+876 'score']['created_at'][:10]
+ +878 self.pr_acc = response.json()['score']['accuracy']
+879 self.pr_maxcombo = response.json()['score']['max_combo']
+880 self.pr_300 = response.json()['score']['statistics']['count_300']
+881 self.pr_100 = response.json()['score']['statistics']['count_100']
+882 self.pr_50 = response.json()['score']['statistics']['count_50']
+883 self.pr_miss = response.json()['score']['statistics']['count_miss']
+884 self.pr_pp = response.json()['score']['pp']
+885 self.pr_rank = response.json()['score']['rank']
+886 self.pr_mods = response.json()['score']['mods']
+887 self.pr_mods = "".join([str(mod) for mod in self.pr_mods])
+ +889 self.pr_beatmap_url = response.json()['score']['beatmap']['url']
+ +891 self.pr_username = username
+ +893 self.pr_acc = round(self.pr_acc*100, 2)
+ +895 except HTTPError:
+896 print(f"未查询到{username}在该谱面上留下的成绩")
+897 return f"未查询到{username}在该谱面上留下的成绩"
+ +899 except Exception as e:
+900 print(f'获取谱面成绩失败,错误信息:{e}')
+901 self.pr_title = "获取谱面成绩失败"
+902 self.pr_artist = ""
+903 self.pr_star = ""
+904 self.pr_acc = 0
+905 self.pr_maxcombo = 0
+906 self.pr_300 = 0
+907 self.pr_100 = 0
+908 self.pr_50 = 0
+909 self.pr_miss = 0
+910 self.pr_pp = 0
+911 self.pr_rank = ""
+912 self.pr_mods = ""
+913 self.pr_username = ""
+ +915 self.beatmap_score_created_at = ""
+ +917 result = r'{}| [{} {} - {}]| {}*| {} [ {} ] {}pp acc:{}% combo:{}x| {}/{}/{}/{}| date:{}|'.format(
+918 self.pr_username, self.pr_beatmap_url, self.pr_title, self.pr_artist, self.pr_star, self.pr_mods, self.pr_rank, self.pr_pp, self.pr_acc, self.pr_maxcombo, self.pr_300, self.pr_100, self.pr_50, self.pr_miss, self.beatmap_score_created_at)
+919 print(result)
+920 return result
+ +922 def get_recent_info(self, username):
+923 try:
+924 user_id = self.id2name[username]
+925 url = f'https://osu.ppy.sh/api/v2/users/{user_id}/scores/recent?&include_fails=1'
+926 headers = {'Authorization': f'Bearer {self.osu_token}'}
+927 response = requests.get(url, headers=headers)
+928 response.raise_for_status() # 如果请求失败,这将抛出一个异常
+ +930 self.pr_beatmap_id = response.json()[0]['beatmap']['id']
+931 self.pr_title = response.json()[0]['beatmapset']['title_unicode']
+932 self.pr_artist = response.json()[0]['beatmapset']['artist_unicode']
+933 self.pr_star = response.json()[0]['beatmap']['difficulty_rating']
+ +935 self.pr_acc = response.json()[0]['accuracy']
+936 self.pr_maxcombo = response.json()[0]['max_combo']
+937 self.pr_300 = response.json()[0]['statistics']['count_300']
+938 self.pr_100 = response.json()[0]['statistics']['count_100']
+939 self.pr_50 = response.json()[0]['statistics']['count_50']
+940 self.pr_miss = response.json()[0]['statistics']['count_miss']
+941 self.pr_pp = response.json()[0]['pp']
+942 self.pr_rank = response.json()[0]['rank']
+943 self.pr_mods = response.json()[0]['mods']
+944 self.pr_mods = "".join([str(mod) for mod in self.pr_mods])
+ +946 self.pr_beatmap_url = response.json()[0]['beatmap']['url']
+ +948 self.pr_username = username
+ +950 self.pr_acc = round(self.pr_acc*100, 2)
+ +952 except Exception as e:
+953 print(f'获取最近成绩失败,错误信息:{e}')
+954 self.pr_title = "获取最近成绩失败"
+955 self.pr_artist = ""
+956 self.pr_star = ""
+957 self.pr_acc = 0
+958 self.pr_maxcombo = 0
+959 self.pr_300 = 0
+960 self.pr_100 = 0
+961 self.pr_50 = 0
+962 self.pr_miss = 0
+963 self.pr_pp = 0
+964 self.pr_rank = ""
+965 self.pr_mods = ""
+966 self.pr_username = ""
+967 self.pr_beatmap_url = ""
+ +969 result = r'{}| [{} {} - {}]| {}*| {} [ {} ] {}pp acc:{}% combo:{}x| {}/{}/{}/{}|'.format(
+970 self.pr_username, self.pr_beatmap_url, self.pr_title, self.pr_artist, self.pr_star, self.pr_mods, self.pr_rank, self.pr_pp, self.pr_acc, self.pr_maxcombo, self.pr_300, self.pr_100, self.pr_50, self.pr_miss,)
+971 print(result)
+972 return result
+ + +975class PP:
+976 def __init__(self):
+977 self.beatmap_id = ""
+978 self.mods = 0
+979 self.acc = 0
+980 self.combo = 0
+981 self.misses = 0
+ +983 self.maxbeatmapcombo = 0
+ +985 self.stars = 0
+ +987 self.maxpp = 0
+988 self.maxaimpp = 0
+989 self.maxspeedpp = 0
+990 self.maxaccpp = 0
+ +992 self.afterar = 0
+993 self.aftercs = 0
+994 self.afterod = 0
+995 self.afterhp = 0
+ +997 self.currpp = 0
+998 self.curraimpp = 0
+999 self.currspeedpp = 0
+1000 self.curraccpp = 0
+ +1002 self.fcpp = 0
+1003 self.fc95pp = 0
+1004 self.fc96pp = 0
+1005 self.fc97pp = 0
+1006 self.fc98pp = 0
+1007 self.fc99pp = 0
+ + +1010 def get_beatmap_file(self, beatmap_id):
+1011 self.beatmap_id = beatmap_id
+ +1013 if os.path.exists(f'./maps/{beatmap_id}.osu'):
+1014 print(f'谱面文件已存在')
+1015 else:
+1016 try:
+1017 url = f'https://osu.ppy.sh/osu/{beatmap_id}'
+1018 response = requests.get(url)
+1019 response.raise_for_status() # 如果请求失败,这会抛出一个异常
+1020 with open(f'./maps/{beatmap_id}.osu', 'wb') as f:
+1021 f.write(response.content)
+1022 except:
+1023 print("获取谱面文件失败")
+ +1025 def calculate_pp_fully(self, mods):
+1026 try:
+1027 self.mods = mods
+ +1029 beatmap = rosu.Beatmap(path=f"./maps/{self.beatmap_id}.osu")
+ +1031 max_perf = rosu.Performance(mods=mods)
+ +1033 attrs = max_perf.calculate(beatmap)
+ +1035 self.maxpp = attrs.pp
+ +1037 # 计算maxbeatmapcombo
+1038 self.maxbeatmapcombo = attrs.difficulty.max_combo
+ +1040 # 计算stars
+1041 self.stars = attrs.difficulty.stars
+ +1043 # 计算4维
+1044 beatmap_attr_builder = rosu.BeatmapAttributesBuilder(mods=mods)
+1045 beatmap_attr_builder.set_map(beatmap)
+1046 beatmap_attr = beatmap_attr_builder.build()
+1047 self.afterar = beatmap_attr.ar
+1048 self.aftercs = beatmap_attr.cs
+1049 self.afterod = beatmap_attr.od
+1050 self.afterhp = beatmap_attr.hp
+ +1052 # 计算if 95% pp
+1053 max_perf.set_accuracy(95)
+1054 fc95_perf = max_perf.calculate(beatmap)
+1055 self.fc95pp = fc95_perf.pp
+ +1057 # 计算if 96% pp
+1058 max_perf.set_accuracy(96)
+1059 fc96_perf = max_perf.calculate(beatmap)
+1060 self.fc96pp = fc96_perf.pp
+ +1062 # 计算if 97% pp
+1063 max_perf.set_accuracy(97)
+1064 fc97_perf = max_perf.calculate(beatmap)
+1065 self.fc97pp = fc97_perf.pp
+ +1067 # 计算if 98% pp
+1068 max_perf.set_accuracy(98)
+1069 fc98_perf = max_perf.calculate(beatmap)
+1070 self.fc98pp = fc98_perf.pp
+ +1072 # 计算if 99% pp
+1073 max_perf.set_accuracy(99)
+1074 fc99_perf = max_perf.calculate(beatmap)
+1075 self.fc99pp = fc99_perf.pp
+ +1077 self.maxpp = round(self.maxpp)
+1078 self.fc95pp = round(self.fc95pp)
+1079 self.fc96pp = round(self.fc96pp)
+1080 self.fc97pp = round(self.fc97pp)
+1081 self.fc98pp = round(self.fc98pp)
+1082 self.fc99pp = round(self.fc99pp)
+1083 self.stars = round(self.stars, 2)
+ +1085 self.afterar = round(self.afterar, 1)
+1086 self.aftercs = round(self.aftercs, 1)
+1087 self.afterod = round(self.afterod, 1)
+1088 self.afterhp = round(self.afterhp, 1)
+ +1090 except:
+1091 print("计算pp失败")
+1092 self.maxpp = 0
+1093 self.maxbeatmapcombo = 0
+1094 self.fc95pp = 0
+1095 self.fc96pp = 0
+1096 self.fc97pp = 0
+1097 self.fc98pp = 0
+1098 self.fc99pp = 0
+1099 self.stars = 0
+ +1101 self.afterar = 0
+1102 self.aftercs = 0
+1103 self.afterod = 0
+1104 self.afterhp = 0
+ +1106 return f'{self.mods}| {self.stars}*| {self.maxbeatmapcombo}x| ar:{self.afterar} cs:{self.aftercs} od:{self.afterod} hp:{self.afterhp} | SS:{self.maxpp}pp| 99%:{self.fc99pp}pp| 98%:{self.fc98pp}pp| 97%:{self.fc97pp}pp| 96%:{self.fc96pp}pp| 95%:{self.fc95pp}pp'
+ +1108 def calculate_pp_obj(self, mods, acc, misses, combo):
+ +1110 try:
+ +1112 self.mods = mods
+ +1114 map = rosu.Beatmap(path=f"./maps/{self.beatmap_id}.osu")
+ +1116 max_perf = rosu.Performance(mods=mods)
+ +1118 attrs = max_perf.calculate(map)
+ +1120 self.maxpp = attrs.pp
+ +1122 self.maxbeatmapcombo = attrs.difficulty.max_combo
+ +1124 self.maxaimpp = attrs.pp_aim
+1125 self.maxspeedpp = attrs.pp_speed
+1126 self.maxaccpp = attrs.pp_accuracy
+ +1128 # 计算玩家的current performance
+1129 max_perf.set_misses(misses)
+1130 max_perf.set_accuracy(acc)
+1131 max_perf.set_combo(combo)
+ +1133 curr_perf = max_perf.calculate(map)
+1134 self.currpp = curr_perf.pp
+1135 self.curraccpp = curr_perf.pp
+1136 self.curraimpp = curr_perf.pp_aim
+1137 self.currspeedpp = curr_perf.pp_speed
+1138 self.curraccpp = curr_perf.pp_accuracy
+ +1140 # 计算if fc pp
+1141 max_perf.set_misses(0)
+1142 max_perf.set_combo(None)
+ +1144 fc_perf = max_perf.calculate(map)
+1145 self.fcpp = fc_perf.pp
+ +1147 # 计算if 95% pp
+1148 max_perf.set_accuracy(95)
+1149 fc95_perf = max_perf.calculate(map)
+1150 self.fc95pp = fc95_perf.pp
+ +1152 # 计算if 96% pp
+1153 max_perf.set_accuracy(96)
+1154 fc96_perf = max_perf.calculate(map)
+1155 self.fc96pp = fc96_perf.pp
+ +1157 # 计算if 97% pp
+1158 max_perf.set_accuracy(97)
+1159 fc97_perf = max_perf.calculate(map)
+1160 self.fc97pp = fc97_perf.pp
+ +1162 # 计算if 98% pp
+1163 max_perf.set_accuracy(98)
+1164 fc98_perf = max_perf.calculate(map)
+1165 self.fc98pp = fc98_perf.pp
+ +1167 # 计算if 99% pp
+1168 max_perf.set_accuracy(99)
+1169 fc99_perf = max_perf.calculate(map)
+1170 self.fc99pp = fc99_perf.pp
+ +1172 self.maxpp = round(self.maxpp)
+1173 self.maxaimpp = round(self.maxaimpp)
+1174 self.maxspeedpp = round(self.maxspeedpp)
+1175 self.maxaccpp = round(self.maxaccpp)
+ +1177 self.currpp = round(self.currpp)
+1178 self.curraimpp = round(self.curraimpp)
+1179 self.currspeedpp = round(self.currspeedpp)
+1180 self.curraccpp = round(self.curraccpp)
+ +1182 self.fcpp = round(self.fcpp)
+1183 self.fc95pp = round(self.fc95pp)
+1184 self.fc96pp = round(self.fc96pp)
+1185 self.fc97pp = round(self.fc97pp)
+1186 self.fc98pp = round(self.fc98pp)
+1187 self.fc99pp = round(self.fc99pp)
+ +1189 except Exception as e:
+1190 print(f'计算pp失败: {e}')
+1191 self.maxpp = 0
+1192 self.maxaimpp = 0
+1193 self.maxspeedpp = 0
+1194 self.maxaccpp = 0
+ +1196 self.maxbeatmapcombo = 0
+ +1198 self.currpp = 0
+1199 self.curraimpp = 0
+1200 self.currspeedpp = 0
+1201 self.curraccpp = 0
+ +1203 self.fcpp = 0
+1204 self.fc95pp = 0
+1205 self.fc96pp = 0
+1206 self.fc97pp = 0
+1207 self.fc98pp = 0
+1208 self.fc99pp = 0
+ +1210 return f'now:{self.currpp}pp| if FC({self.maxbeatmapcombo}x):{self.fcpp}pp| 95%:{self.fc95pp}pp| 96%:{self.fc96pp}pp| 97%:{self.fc97pp}pp| 98%:{self.fc98pp}pp| 99%:{self.fc99pp}pp| SS:{self.maxpp}pp| aim:{self.curraimpp}/{self.maxaimpp}pp| speed:{self.currspeedpp}/{self.maxspeedpp}pp| acc:{self.curraccpp}/{self.maxaccpp}pp'
+ +1212config = Config()
+ +1214if __name__ == '__main__':
+1215 # 没有maps文件夹时自动创建maps文件夹
+1216 maps_dir = os.path.join(os.getcwd(), './maps')
+1217 if not os.path.exists(maps_dir):
+1218 os.makedirs(maps_dir)
+1219 print(f"'{maps_dir}'文件夹不存在,已经自动创建")
+ +1221 client_id = config.osuclientid
+1222 client_secret = config.osuclientsecret
+ +1224 osu_nickname = config.osunickname
+1225 osu_password = config.osupassword
+ +1227 p = Player()
+1228 r = Room()
+1229 b = Beatmap(client_id, client_secret)
+1230 pp = PP()
+ +1232 client = MyIRCClient(osu_server, osu_port, osu_nickname, osu_password)
+1233 client.start()
+