PythonでChallonge APIを使うチュートリアル

　日本語でまとまった情報がなかったので。

# Challongeとは

　https://challonge.com/ja

　トーナメントを管理するためのWebサービスです。ゲーム配信等で大会を行うにあたってはデファクトスタンダード的な存在かなと思います。Qiitaでは反映できないので貼りませんが、トーナメント表を埋め込んだりできるので便利です。

　ChallongeはAPIを用意してくれていて（[ドキュメント](https://api.challonge.com/ja/v1)）、以下の言語でライブラリが用意されています。

- Ruby
- Python
- Perl

　今回はこの内、Pythonのライブラリを実際に使ってみることにします。

# インストール

　以下のコマンドで[PyPl](https://pypi.org/project/pychallonge/)からインストールします。

```
pip install pychallonge
```

# APIキーの取得

　プログラム上で本人認証を行うためのAPIキーを取得します。Challongeの個人ページから「デベロッパーAPI」のタブで取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/6cb0bd0e-dd8a-03d8-bf4f-2fe5f3888739.png)

　作文して返事を待たなければならないTwitterとかと比べるとだいぶ手軽ですね。

# tournaments

　テスト用に作った[ダミーページ](https://challonge.com/ja/t9v1965i)に対して実行していきます。まずはトーナメント用のAPIから。

```python
import challonge

challonge.set_credentials("user_name", "APIkey")
tournament = challonge.tournaments.show('t9v1965i') # URLで逆引き可能
print(tournament)
```

　以下のような結果が得られます。

```python
{'id': 10366918, 'name': 'JBSL2_dummytest', 'url': 't9v1965i', 'description': 'dummytest', 'tournament_type': 'single elimination', 'started_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 971000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'completed_at': None, 'require_score_agreement': False, 'notify_users_when_matches_open': True, 'created_at': datetime.datetime(2021, 10, 14, 9, 23, 33, 991000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 
9, 21, 28, 76000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'state': 'underway', 'open_signup': False, 'notify_users_when_the_tournament_ends': True, 'progress_meter': 0, 'quick_advance': False, 'hold_third_place_match': True, 'pts_for_game_win': 0.0, 'pts_for_game_tie': 0.0, 'pts_for_match_win': 1.0, 'pts_for_match_tie': 0.5, 'pts_for_bye': 1.0, 'swiss_rounds': 0, 'private': False, 'ranked_by': 'match wins', 'show_rounds': True, 'hide_forum': False, 'sequential_pairings': False, 'accept_attachments': False, 'rr_pts_for_game_win': 0.0, 'rr_pts_for_game_tie': 0.0, 'rr_pts_for_match_win': 1.0, 'rr_pts_for_match_tie': 0.5, 'created_by_api': False, 'credit_capped': False, 'category': None, 'hide_seeds': False, 'prediction_method': 0, 'predictions_opened_at': None, 'anonymous_voting': False, 'max_predictions_per_user': 1, 'signup_cap': None, 'game_id': 139362, 'participants_count': 4, 'group_stages_enabled': False, 'allow_participant_match_reporting': True, 'teams': False, 'check_in_duration': None, 'start_at': datetime.datetime(2021, 10, 16, 9, 23, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'started_checking_in_at': None, 'tie_breaks': ['match wins vs tied', 'game wins', 'points scored'], 'locked_at': None, 'event_id': None, 'public_predictions_before_start_time': False, 'ranked': False, 'grand_finals_modifier': None, 'predict_the_losers_bracket': False, 'spam': None, 'ham': None, 'rr_iterations': None, 'tournament_registration_id': None, 'donation_contest_enabled': None, 'mandatory_donation': None, 'non_elimination_tournament_data': {'participants_per_match': ''}, 'auto_assign_stations': None, 'only_start_matches_with_stations': None, 'registration_fee': 0.0, 'registration_type': 'free', 'split_participants': False, 'allowed_regions': [], 'show_participant_country': None, 'program_id': None, 'program_classification_ids_allowed': None, 'team_size_range': None, 'toxic': True, 'use_new_style': None, 'optional_display_data': {'show_standings': '1', 'show_announcements': True}, 'processing': False, 'oauth_application_id': None, 'description_source': 'dummytest', 'subdomain': None, 'full_challonge_url': 'https://challonge.com/t9v1965i', 'live_image_url': 'https://challonge.com/t9v1965i.svg', 'sign_up_url': None, 'review_before_finalizing': True, 'accepting_predictions': False, 'participants_locked': True, 'game_name': 'Beat Saber', 'participants_swappable': False, 'team_convertable': False, 'group_stages_were_started': False}
```

　あまりにも項目が多いので、項目ごとに表示するようにしたものが以下。

```python
id : 10366918
name : JBSL2_dummytest
url : t9v1965i
description : dummytest
tournament_type : single elimination
started_at : 2021-10-14 09:21:27.971000+09:00
completed_at : None
require_score_agreement : False
notify_users_when_matches_open : True
created_at : 2021-10-14 09:23:33.991000+09:00
updated_at : 2021-10-14 09:21:28.076000+09:00
state : underway
open_signup : False
notify_users_when_the_tournament_ends : True
progress_meter : 0
quick_advance : False
hold_third_place_match : True
pts_for_game_win : 0.0
pts_for_game_tie : 0.0
pts_for_match_win : 1.0
pts_for_match_tie : 0.5
pts_for_bye : 1.0
swiss_rounds : 0
private : False
ranked_by : match wins
show_rounds : True
hide_forum : False
sequential_pairings : False
accept_attachments : False
rr_pts_for_game_win : 0.0
rr_pts_for_game_tie : 0.0
rr_pts_for_match_win : 1.0
rr_pts_for_match_tie : 0.5
created_by_api : False
credit_capped : False
category : None
hide_seeds : False
prediction_method : 0
predictions_opened_at : None
anonymous_voting : False
max_predictions_per_user : 1
signup_cap : None
game_id : 139362
participants_count : 4
group_stages_enabled : False
allow_participant_match_reporting : True
teams : False
check_in_duration : None
start_at : 2021-10-16 09:23:00+09:00
started_checking_in_at : None
tie_breaks : ['match wins vs tied', 'game wins', 'points scored']
locked_at : None
event_id : None
public_predictions_before_start_time : False
ranked : False
grand_finals_modifier : None
predict_the_losers_bracket : False
spam : None
ham : None
rr_iterations : None
tournament_registration_id : None
donation_contest_enabled : None
mandatory_donation : None
non_elimination_tournament_data : {'participants_per_match': ''}
auto_assign_stations : None
only_start_matches_with_stations : None
registration_fee : 0.0
registration_type : free
split_participants : False
allowed_regions : []
show_participant_country : None
program_id : None
program_classification_ids_allowed : None
team_size_range : None
toxic : True
use_new_style : None
optional_display_data : {'show_standings': '1', 'show_announcements': True}
processing : False
oauth_application_id : None
description_source : dummytest
subdomain : None
full_challonge_url : https://challonge.com/t9v1965i
live_image_url : https://challonge.com/t9v1965i.svg
sign_up_url : None
review_before_finalizing : True
accepting_predictions : False
participants_locked : True
game_name : Beat Saber
participants_swappable : False
team_convertable : False
group_stages_were_started : False
```

# participants

　例えば、あるトーナメントに含まれる参加者一覧を取得するには以下のようにします。

```python
participants = challonge.participants.index(tournament['id'])
print(participants)
```

```python
[{'id': 156557288, 'tournament_id': 10366918, 'name': 'hibit1', 'seed': 1, 'active': True, 'created_at': datetime.datetime(2021, 10, 14, 9, 21, 1, 324000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 23, 34, 551000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'invite_email': None, 'final_rank': None, 'misc': None, 'icon': None, 'on_waiting_list': False, 'invitation_id': None, 'group_id': None, 'checked_in_at': None, 'ranked_member_id': None, 'custom_field_response': None, 'clinch': None, 'integration_uids': None, 'challonge_username': None, 'challonge_email_address_verified': None, 'removable': True, 'participatable_or_invitation_attached': False, 'confirm_remove': True, 'invitation_pending': False, 'display_name_with_invitation_email_address': 'hibit1', 'email_hash': None, 'username': None, 'display_name': 'hibit1', 'attached_participatable_portrait_url': None, 'can_check_in': False, 'checked_in': False, 'reactivatable': False, 'check_in_open': False, 'group_player_ids': [], 'has_irrelevant_seed': False}, {'id': 156557284, 'tournament_id': 10366918, 'name': 'hibit2', 'seed': 2, 'active': True, 'created_at': datetime.datetime(2021, 10, 14, 9, 22, 44, 850000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 22, 44, 850000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'invite_email': None, 'final_rank': None, 'misc': None, 'icon': None, 'on_waiting_list': False, 'invitation_id': None, 'group_id': None, 'checked_in_at': None, 'ranked_member_id': None, 'custom_field_response': None, 'clinch': None, 'integration_uids': None, 'challonge_username': None, 'challonge_email_address_verified': None, 'removable': True, 'participatable_or_invitation_attached': False, 'confirm_remove': True, 'invitation_pending': False, 'display_name_with_invitation_email_address': 'hibit2', 'email_hash': None, 'username': None, 'display_name': 'hibit2', 'attached_participatable_portrait_url': None, 'can_check_in': False, 'checked_in': False, 'reactivatable': False, 'check_in_open': False, 'group_player_ids': [], 'has_irrelevant_seed': False}, {'id': 156557285, 'tournament_id': 10366918, 'name': 'hibit3', 'seed': 3, 'active': True, 'created_at': 
datetime.datetime(2021, 10, 14, 9, 22, 44, 886000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 22, 44, 886000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'invite_email': None, 'final_rank': 
None, 'misc': None, 'icon': None, 'on_waiting_list': False, 'invitation_id': None, 'group_id': None, 'checked_in_at': None, 'ranked_member_id': None, 'custom_field_response': None, 'clinch': None, 'integration_uids': None, 'challonge_username': None, 'challonge_email_address_verified': None, 'removable': True, 'participatable_or_invitation_attached': False, 'confirm_remove': 
True, 'invitation_pending': False, 'display_name_with_invitation_email_address': 'hibit3', 'email_hash': None, 'username': None, 'display_name': 'hibit3', 'attached_participatable_portrait_url': None, 'can_check_in': False, 'checked_in': False, 'reactivatable': False, 'check_in_open': False, 'group_player_ids': [], 'has_irrelevant_seed': False}, {'id': 156557286, 'tournament_id': 10366918, 'name': 'hibit4', 'seed': 4, 'active': True, 'created_at': datetime.datetime(2021, 10, 14, 9, 22, 44, 916000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 22, 44, 916000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'invite_email': None, 'final_rank': None, 'misc': None, 'icon': None, 'on_waiting_list': False, 'invitation_id': None, 'group_id': None, 'checked_in_at': None, 'ranked_member_id': None, 'custom_field_response': None, 'clinch': None, 'integration_uids': None, 'challonge_username': None, 'challonge_email_address_verified': None, 'removable': True, 'participatable_or_invitation_attached': False, 'confirm_remove': True, 'invitation_pending': False, 'display_name_with_invitation_email_address': 'hibit4', 'email_hash': None, 'username': None, 'display_name': 'hibit4', 'attached_participatable_portrait_url': None, 'can_check_in': False, 'checked_in': False, 'reactivatable': False, 'check_in_open': False, 'group_player_ids': [], 'has_irrelevant_seed': False}]
```

　返されるのが辞書データではなくリストであることに注意です。

# matches

　試合用のAPIです。

```python
matches = challonge.matches.index(tournament['id'])
print('matches')
```

```python
[{'id': 253333313, 'tournament_id': 10366918, 'state': 'open', 'player1_id': 156557288, 'player2_id': 156557286, 'player1_prereq_match_id': None, 'player2_prereq_match_id': None, 'player1_is_prereq_match_loser': False, 'player2_is_prereq_match_loser': 
False, 'winner_id': None, 'loser_id': None, 'started_at': datetime.datetime(2021, 10, 14, 9, 21, 28, 31000, tzinfo=<DstTzInfo 
'Asia/Tokyo' JST+9:00:00 STD>), 'created_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 929000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 21, 28, 31000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'identifier': 'A', 'has_attachment': False, 'round': 1, 'player1_votes': None, 'player2_votes': None, 'group_id': None, 'attachment_count': None, 'scheduled_time': None, 'location': None, 'underway_at': None, 'optional': False, 'rushb_id': None, 'completed_at': None, 'suggested_play_order': None, 'forfeited': None, 'open_graph_image_file_name': None, 'open_graph_image_content_type': None, 'open_graph_image_file_size': None, 'prerequisite_match_ids_csv': '', 'scores_csv': ''}, {'id': 253333314, 'tournament_id': 10366918, 'state': 'open', 'player1_id': 156557284, 'player2_id': 156557285, 'player1_prereq_match_id': None, 'player2_prereq_match_id': None, 'player1_is_prereq_match_loser': False, 'player2_is_prereq_match_loser': False, 'winner_id': None, 'loser_id': None, 'started_at': datetime.datetime(2021, 10, 14, 9, 21, 28, 46000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'created_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 933000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 21, 28, 47000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'identifier': 'B', 'has_attachment': False, 'round': 1, 'player1_votes': None, 'player2_votes': None, 'group_id': None, 'attachment_count': None, 'scheduled_time': None, 'location': None, 'underway_at': None, 'optional': False, 'rushb_id': None, 'completed_at': None, 'suggested_play_order': None, 'forfeited': None, 'open_graph_image_file_name': None, 'open_graph_image_content_type': None, 'open_graph_image_file_size': None, 'prerequisite_match_ids_csv': '', 'scores_csv': ''}, {'id': 253333315, 'tournament_id': 10366918, 'state': 'pending', 'player1_id': None, 'player2_id': None, 'player1_prereq_match_id': 253333313, 'player2_prereq_match_id': 253333314, 'player1_is_prereq_match_loser': False, 'player2_is_prereq_match_loser': False, 'winner_id': None, 'loser_id': None, 'started_at': None, 'created_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 946000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 946000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'identifier': 'C', 'has_attachment': False, 'round': 2, 'player1_votes': None, 'player2_votes': None, 'group_id': None, 'attachment_count': None, 'scheduled_time': None, 'location': None, 'underway_at': None, 'optional': False, 'rushb_id': None, 'completed_at': None, 'suggested_play_order': None, 'forfeited': None, 'open_graph_image_file_name': None, 'open_graph_image_content_type': None, 'open_graph_image_file_size': None, 'prerequisite_match_ids_csv': '253333313,253333314', 'scores_csv': ''}, {'id': 253333316, 'tournament_id': 10366918, 'state': 'pending', 'player1_id': None, 'player2_id': None, 'player1_prereq_match_id': 253333313, 'player2_prereq_match_id': 253333314, 'player1_is_prereq_match_loser': True, 'player2_is_prereq_match_loser': True, 'winner_id': None, 'loser_id': None, 'started_at': None, 'created_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 959000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'updated_at': datetime.datetime(2021, 10, 14, 9, 21, 27, 959000, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>), 'identifier': '3P', 'has_attachment': False, 'round': 0, 'player1_votes': None, 'player2_votes': None, 'group_id': None, 'attachment_count': None, 'scheduled_time': None, 'location': None, 'underway_at': None, 'optional': None, 'rushb_id': None, 'completed_at': None, 'suggested_play_order': None, 'forfeited': None, 'open_graph_image_file_name': None, 'open_graph_image_content_type': None, 'open_graph_image_file_size': None, 'prerequisite_match_ids_csv': '253333313,253333314', 'scores_csv': ''}]
```

　これも`participants`と同様に、リストが返されます。

# match_attachements

　試合の付加情報？　のようなもので、そこまでChallongeを高度に利用したことがないのでわかりませんが、一応このようなAPIがあるらしいです。トーナメントと試合のIDを指定すれば情報が得られます。

```python
attachements = challonge.attachments.index(tournament['id'],match['id']) # ダミー大会では何も設定しないたいめ、返り値は空リスト
```