spoonbot 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of spoonbot might be problematic. Click here for more details.
- spoonbot-0.1.0/LICENSE +21 -0
- spoonbot-0.1.0/PKG-INFO +209 -0
- spoonbot-0.1.0/README.md +169 -0
- spoonbot-0.1.0/pyproject.toml +43 -0
- spoonbot-0.1.0/pyproject.toml.orig +41 -0
- spoonbot-0.1.0/src/spoonbot/__init__.py +25 -0
- spoonbot-0.1.0/src/spoonbot/config/ConstantsConfig.py +21 -0
- spoonbot-0.1.0/src/spoonbot/constants.py +12 -0
- spoonbot-0.1.0/src/spoonbot/controller/SpoonBotController.py +56 -0
- spoonbot-0.1.0/src/spoonbot/controller/SpoonBotWebController.py +52 -0
- spoonbot-0.1.0/src/spoonbot/controller/SpoonOAuthController.py +76 -0
- spoonbot-0.1.0/src/spoonbot/db/DBType.py +6 -0
- spoonbot-0.1.0/src/spoonbot/db/DatabaseClient.py +75 -0
- spoonbot-0.1.0/src/spoonbot/db/SpoonUserRepository.py +278 -0
- spoonbot-0.1.0/src/spoonbot/db/config/MySQLConfig.py +11 -0
- spoonbot-0.1.0/src/spoonbot/db/dto/TokenDataDto.py +23 -0
- spoonbot-0.1.0/src/spoonbot/db/dto/UpdateResult.py +7 -0
- spoonbot-0.1.0/src/spoonbot/db/mysql/MySQLClient.py +147 -0
- spoonbot-0.1.0/src/spoonbot/db/sqlite3/Sqlite3Client.py +111 -0
- spoonbot-0.1.0/src/spoonbot/db/types.py +1 -0
- spoonbot-0.1.0/src/spoonbot/dto/AuthorizeUrl.py +7 -0
- spoonbot-0.1.0/src/spoonbot/dto/ChatBody.py +12 -0
- spoonbot-0.1.0/src/spoonbot/dto/DonationBody.py +12 -0
- spoonbot-0.1.0/src/spoonbot/dto/DonationData.py +23 -0
- spoonbot-0.1.0/src/spoonbot/dto/FanData.py +13 -0
- spoonbot-0.1.0/src/spoonbot/dto/FollowBody.py +10 -0
- spoonbot-0.1.0/src/spoonbot/dto/LikeBody.py +21 -0
- spoonbot-0.1.0/src/spoonbot/dto/ListenerData.py +23 -0
- spoonbot-0.1.0/src/spoonbot/dto/LiveRoom.py +16 -0
- spoonbot-0.1.0/src/spoonbot/dto/PresenceBody.py +19 -0
- spoonbot-0.1.0/src/spoonbot/dto/RateLimit.py +11 -0
- spoonbot-0.1.0/src/spoonbot/dto/SpoonAuthorization.py +35 -0
- spoonbot-0.1.0/src/spoonbot/dto/User.py +6 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/ChatEvent.py +9 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/ConnectEvent.py +8 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/DonationEvent.py +9 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/EndEvent.py +7 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/FollowEvent.py +9 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/JoinEvent.py +9 -0
- spoonbot-0.1.0/src/spoonbot/dto/event/LikeEvent.py +9 -0
- spoonbot-0.1.0/src/spoonbot/enums/Country.py +6 -0
- spoonbot-0.1.0/src/spoonbot/enums/SpoonPermission.py +16 -0
- spoonbot-0.1.0/src/spoonbot/enums/TokenStatus.py +7 -0
- spoonbot-0.1.0/src/spoonbot/exception/BackoffRetryException.py +2 -0
- spoonbot-0.1.0/src/spoonbot/exception/BurstRequestException.py +11 -0
- spoonbot-0.1.0/src/spoonbot/exception/ChatRoomCrowdedException.py +5 -0
- spoonbot-0.1.0/src/spoonbot/exception/DailyRequestLimitException.py +12 -0
- spoonbot-0.1.0/src/spoonbot/exception/MessageSendUnavailableException.py +5 -0
- spoonbot-0.1.0/src/spoonbot/exception/NonBackoffRetryException.py +2 -0
- spoonbot-0.1.0/src/spoonbot/exception/NonRetryException.py +2 -0
- spoonbot-0.1.0/src/spoonbot/exception/TokenRefreshException.py +2 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/APIClient.py +113 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/SpoonApiClient.py +148 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/APIResponse.py +10 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/ChatSendRequestBody.py +24 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/DonationResponseDto.py +20 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/FanResponseDto.py +13 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/ListenersResponseDto.py +16 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/LiveRoomDto.py +16 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/TokenResponse.py +9 -0
- spoonbot-0.1.0/src/spoonbot/httpclient/dto/UserDataDto.py +5 -0
- spoonbot-0.1.0/src/spoonbot/manager/SpoonEventStreamManager.py +295 -0
- spoonbot-0.1.0/src/spoonbot/py.typed +0 -0
- spoonbot-0.1.0/src/spoonbot/spoon_bot.py +473 -0
- spoonbot-0.1.0/src/spoonbot/types/callback_types.py +17 -0
spoonbot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YomogiBeta
|
|
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.
|
spoonbot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: spoonbot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An unofficial wrapper library that makes it easier to use the official API of the live streaming service "Spoon"
|
|
5
|
+
Author: YomogiBeta
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 YomogiBeta
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Requires-Dist: pydantic>=2.13.5
|
|
28
|
+
Requires-Dist: oauthlib>=4.0.0
|
|
29
|
+
Requires-Dist: aiohttp>=3.14.3
|
|
30
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
31
|
+
Requires-Dist: aiomysql>=0.3.2
|
|
32
|
+
Requires-Dist: uuid-utils>=1.0.0
|
|
33
|
+
Requires-Dist: itsdangerous>=2.2.0
|
|
34
|
+
Requires-Dist: aiohttp-sse-client2>=0.3.0
|
|
35
|
+
Requires-Dist: pymysql<=1.2.0
|
|
36
|
+
Requires-Dist: fastapi>=0.142.2 ; extra == 'fastapi'
|
|
37
|
+
Requires-Python: >=3.13
|
|
38
|
+
Provides-Extra: fastapi
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# Spoonbot
|
|
42
|
+
|
|
43
|
+
Spoonbotは、オーディオライブプラットフォーム「Spoon」が公開している、Bot機能の提供API「[Spoon Developpers](https://developers.spooncast.net/docs/ja)」を非常に簡単に利用できるようにするラッパーライブラリです。
|
|
44
|
+
|
|
45
|
+
主な特徴は以下の通りです。
|
|
46
|
+
|
|
47
|
+
- **周辺処理不要**: Oauth認可、トークン管理、エラーハンドリング、再接続などSpoon Developpersを利用するために本来必要な様々な周辺処理を実装せずに済みます。
|
|
48
|
+
- **直感的**: PythonでBot開発を行うときによく見られる直感的な書き方でSpoonのBotを開発できます。
|
|
49
|
+
- **簡単**: 少ない記述で実装可能。水平スケールしないのであれば、内臓のSQLite3ですぐに動作します。(MySQLも利用可)
|
|
50
|
+
- **型安全**: Spoon Developpersからの応答はPydanticによって検証され、エディタ補完の効くデータとして受け取ることができます。
|
|
51
|
+
|
|
52
|
+
## Spoon Developpersの申請
|
|
53
|
+
|
|
54
|
+
本ライブラリの使用するためには、以下の申請が完了している必要があります。
|
|
55
|
+
|
|
56
|
+
<https://developers.spooncast.net/docs/ja/getting-started/app-registration>
|
|
57
|
+
|
|
58
|
+
### 注意
|
|
59
|
+
Spoon Developpers申請に利用したアカウントはそのままボットアカウントとして、連携先のDJのライブでチャット送信などを行います。(Botアカウントが発行されるわけではなく、自分のアカウントがBotのように振る舞うようになる。)
|
|
60
|
+
これは、本ライブラリの仕様ではなくSpoon Developpersの仕様です。詳細はSpoon Developpersにお問い合わせください。
|
|
61
|
+
|
|
62
|
+
## インストール
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install spoonbot
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
uvを利用するのであれば、
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv add spoonbot
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
以下の内容で`main.py`を作成します。
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from spoonbot import (
|
|
80
|
+
ChatEvent,
|
|
81
|
+
SpoonBot,
|
|
82
|
+
SpoonPermission,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
spoonbot = SpoonBot(
|
|
86
|
+
permissions=set(SpoonPermission)
|
|
87
|
+
# 権限を個別に指定する場合 ↓
|
|
88
|
+
# ここで指定しなかったとしても、チャットの送受信と連携ユーザーの情報取得権限は本ライブラリの最低要件として自動指定されます。
|
|
89
|
+
# permissions={SpoonPermission.EVENTS_LIKE, SpoonPermission.DJ_FOLLOW}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@spoonbot.chat_event
|
|
94
|
+
async def hello(chat_event: ChatEvent):
|
|
95
|
+
if chat_event.chat_body.message == "Hello":
|
|
96
|
+
await spoonbot.send_chat(
|
|
97
|
+
f"Hello world {chat_event.chat_body.user.nickname}!!",
|
|
98
|
+
chat_event.authorization,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
spoonbot.start()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
<details markdown="1">
|
|
105
|
+
<summary>他にも、様々なイベントを受け取れます</summary>
|
|
106
|
+
|
|
107
|
+
```Python
|
|
108
|
+
@spoonbot.connect_event
|
|
109
|
+
async def first_message(connect_event: ConnectEvent):
|
|
110
|
+
if not connect_event.reconnect:
|
|
111
|
+
await spoonbot.send_chat(
|
|
112
|
+
"I’m here!",
|
|
113
|
+
connect_event.authorization,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@spoonbot.like_event
|
|
117
|
+
async def like(like_event: LikeEvent):
|
|
118
|
+
await spoonbot.send_chat(
|
|
119
|
+
f"Thankyou Heart!! {like_event.like_body.user.nickname}!!",
|
|
120
|
+
like_event.authorization,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@spoonbot.join_event
|
|
125
|
+
async def join(join_event: JoinEvent):
|
|
126
|
+
await spoonbot.send_chat(
|
|
127
|
+
f"Welcome {join_event.presence_body.user.nickname}!!",
|
|
128
|
+
join_event.authorization,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@spoonbot.gift_event
|
|
133
|
+
async def gift(gift_event: GiftEvent):
|
|
134
|
+
await spoonbot.send_chat(
|
|
135
|
+
f"Thankyou gift! {gift_event.donation_body.user.nickname}!!",
|
|
136
|
+
gift_event.authorization,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@spoonbot.follow_event
|
|
141
|
+
async def follow(follow_event: FollowEvent):
|
|
142
|
+
await spoonbot.send_chat(
|
|
143
|
+
f"Thankyou follow! {follow_event.follow_body.user.nickname}!!",
|
|
144
|
+
follow_event.authorization,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@spoonbot.end_event
|
|
149
|
+
async def end(end_event: EndEvent):
|
|
150
|
+
print(f"bye {end_event.authorization.id}")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
</details>
|
|
154
|
+
|
|
155
|
+
## 必要な環境変数
|
|
156
|
+
|
|
157
|
+
必須の物は必ず指定する必要があります。ただし、環境変数キーまたはSpoonBot初期荷時引数のどちらかで指定すればよいです。
|
|
158
|
+
|
|
159
|
+
| 環境変数キー | SpoonBot初期化時引数 | 説明 | 必須 |
|
|
160
|
+
| ------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
|
|
161
|
+
| SPOON_CLIENT_ID | spoon_client_id | Spoon Developpersのコンソールページで発行したClient id。 | ✅ |
|
|
162
|
+
| SPOON_CLIENT_SECRET | spoon_client_secret | `sk_live_` から始まる、Spoon Developpersのコンソールページで発行したClient Secret。この値は漏洩するとbotを乗っ取られるので、適切に管理します。 | ✅ |
|
|
163
|
+
| SPOON_OAUTH_REDIRECT_URL | oauth_redirect_url | Spoon Developpersのコンソールページで指定したコールバックURL。コンソールで指定したものと1字1句同じである必要があります。本ライブラリを用いて構築した場合、pathは自動的に「/oauth/callback」となります。 | ✅ |
|
|
164
|
+
| SPOON_SECRET_KEY | secret_key | ランダムな32文字以上のシークレットキーを生成して指定します。これはSpoon Developpersで発行されるものではありません。sessionのキーなどに利用するので、水平スケールする場合は全てのpodで同一のIDにする必要があります。<br>生成方法の一例: ```openssl rand -base64 22``` | ✅ |
|
|
165
|
+
| SPOON_BOT_COUNTRY | country | どの国のSpoonで提供するかを指定します。Spoon Developpersでbotを発行したドメインの国と合わせる必要があります。 JP | KR | ✅ |
|
|
166
|
+
| SPOON_BOT_PORT | port | spoonbotを立ち上げるポートを指定します。デフォルトは8080です。 | |
|
|
167
|
+
| SPOON_BOT_NAME | bot_name | Oauth連携ページや、Bot招待ページにて表示される名前を指定します。デフォルトは「Spoon Bot」です。 | |
|
|
168
|
+
| | bot_operation_page | botに関する追加のページを実装し提供する場合、そのパスもしくはURLを指定してください。Bot招待ページに引数で指定したページへ遷移するボタンが出現します。 例えば、web上で入力した値を保存しておき、毎回ライブの最初に送信するといったbotを実装するとき、該当のwebページのパスまたはurlを指定するとよいでしょう。 | |
|
|
169
|
+
|
|
170
|
+
## 実行
|
|
171
|
+
|
|
172
|
+
以下のコマンドで実行し、web上に公開します。必要に応じて上段にNginxなどを設ければよいと思いますが、小規模であればその必要もないと思います。
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python main.py
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
または
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
uv run main.py
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 連携
|
|
185
|
+
|
|
186
|
+
Botを連携するには、まず以下のURLを案内します。
|
|
187
|
+
https://{your domain}/spoonbot/connect
|
|
188
|
+
|
|
189
|
+
<img width="400" height="492" alt="spoonbot-test aakun-da f5 si_spoonbot_connect(iPhone 16)" src="https://github.com/user-attachments/assets/58e98073-f30e-4afd-87cf-28e622828f61" />
|
|
190
|
+
|
|
191
|
+
「ボットを連携する」をクリックし、spoonのページで認証認可を完了すると、以下のページへ遷移します
|
|
192
|
+
|
|
193
|
+
<img width="400" height="492" alt="spoonbot-test aakun-da f5 si_spoonbot_success(iPhone 16)" src="https://github.com/user-attachments/assets/3b3becba-e6cf-4e75-9209-9741c97a11c4" />
|
|
194
|
+
|
|
195
|
+
「ボット招待ページへ」を押した後、先ほど連携したアカウントで、**Spoonライブ配信を開始してください。**
|
|
196
|
+
ライブ配信を開始したら、「今すぐBotを招待する」を押してください。
|
|
197
|
+
|
|
198
|
+
<img width="400" height="480" alt="spoonbot-test aakun-da f5 si_spoonbot_invite(iPhone 16)" src="https://github.com/user-attachments/assets/03a6f1bc-90a0-4a71-afb5-63eca8222bc0" />
|
|
199
|
+
|
|
200
|
+
**Spoon Developpersを申請したアカウントがBotとして配信に入室してきたら、連携成功です。**
|
|
201
|
+
|
|
202
|
+
<img width="552" height="135" alt="スクリーンショット 2026-11-10 210610" src="https://github.com/user-attachments/assets/1ad98723-bd5c-4b5a-bafd-a04c819d3f51" />
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## 最後に
|
|
207
|
+
|
|
208
|
+
- このラッパーライブラリは現在ベータ版です
|
|
209
|
+
- 本ライブラリは㈱Spoonlabs Japan が提供するオーディオライブプラットフォーム「Spoon」内で利用できるAPIである「[Spoon Developpers](https://developers.spooncast.net/jp)」を便利に利用できるようにしたものですが、本ライブラリおよび開発者は㈱Spoonlabs Japan とは一切関係ありません。
|
spoonbot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Spoonbot
|
|
2
|
+
|
|
3
|
+
Spoonbotは、オーディオライブプラットフォーム「Spoon」が公開している、Bot機能の提供API「[Spoon Developpers](https://developers.spooncast.net/docs/ja)」を非常に簡単に利用できるようにするラッパーライブラリです。
|
|
4
|
+
|
|
5
|
+
主な特徴は以下の通りです。
|
|
6
|
+
|
|
7
|
+
- **周辺処理不要**: Oauth認可、トークン管理、エラーハンドリング、再接続などSpoon Developpersを利用するために本来必要な様々な周辺処理を実装せずに済みます。
|
|
8
|
+
- **直感的**: PythonでBot開発を行うときによく見られる直感的な書き方でSpoonのBotを開発できます。
|
|
9
|
+
- **簡単**: 少ない記述で実装可能。水平スケールしないのであれば、内臓のSQLite3ですぐに動作します。(MySQLも利用可)
|
|
10
|
+
- **型安全**: Spoon Developpersからの応答はPydanticによって検証され、エディタ補完の効くデータとして受け取ることができます。
|
|
11
|
+
|
|
12
|
+
## Spoon Developpersの申請
|
|
13
|
+
|
|
14
|
+
本ライブラリの使用するためには、以下の申請が完了している必要があります。
|
|
15
|
+
|
|
16
|
+
<https://developers.spooncast.net/docs/ja/getting-started/app-registration>
|
|
17
|
+
|
|
18
|
+
### 注意
|
|
19
|
+
Spoon Developpers申請に利用したアカウントはそのままボットアカウントとして、連携先のDJのライブでチャット送信などを行います。(Botアカウントが発行されるわけではなく、自分のアカウントがBotのように振る舞うようになる。)
|
|
20
|
+
これは、本ライブラリの仕様ではなくSpoon Developpersの仕様です。詳細はSpoon Developpersにお問い合わせください。
|
|
21
|
+
|
|
22
|
+
## インストール
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install spoonbot
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
uvを利用するのであれば、
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv add spoonbot
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Example
|
|
35
|
+
|
|
36
|
+
以下の内容で`main.py`を作成します。
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from spoonbot import (
|
|
40
|
+
ChatEvent,
|
|
41
|
+
SpoonBot,
|
|
42
|
+
SpoonPermission,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
spoonbot = SpoonBot(
|
|
46
|
+
permissions=set(SpoonPermission)
|
|
47
|
+
# 権限を個別に指定する場合 ↓
|
|
48
|
+
# ここで指定しなかったとしても、チャットの送受信と連携ユーザーの情報取得権限は本ライブラリの最低要件として自動指定されます。
|
|
49
|
+
# permissions={SpoonPermission.EVENTS_LIKE, SpoonPermission.DJ_FOLLOW}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@spoonbot.chat_event
|
|
54
|
+
async def hello(chat_event: ChatEvent):
|
|
55
|
+
if chat_event.chat_body.message == "Hello":
|
|
56
|
+
await spoonbot.send_chat(
|
|
57
|
+
f"Hello world {chat_event.chat_body.user.nickname}!!",
|
|
58
|
+
chat_event.authorization,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
spoonbot.start()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<details markdown="1">
|
|
65
|
+
<summary>他にも、様々なイベントを受け取れます</summary>
|
|
66
|
+
|
|
67
|
+
```Python
|
|
68
|
+
@spoonbot.connect_event
|
|
69
|
+
async def first_message(connect_event: ConnectEvent):
|
|
70
|
+
if not connect_event.reconnect:
|
|
71
|
+
await spoonbot.send_chat(
|
|
72
|
+
"I’m here!",
|
|
73
|
+
connect_event.authorization,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@spoonbot.like_event
|
|
77
|
+
async def like(like_event: LikeEvent):
|
|
78
|
+
await spoonbot.send_chat(
|
|
79
|
+
f"Thankyou Heart!! {like_event.like_body.user.nickname}!!",
|
|
80
|
+
like_event.authorization,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@spoonbot.join_event
|
|
85
|
+
async def join(join_event: JoinEvent):
|
|
86
|
+
await spoonbot.send_chat(
|
|
87
|
+
f"Welcome {join_event.presence_body.user.nickname}!!",
|
|
88
|
+
join_event.authorization,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@spoonbot.gift_event
|
|
93
|
+
async def gift(gift_event: GiftEvent):
|
|
94
|
+
await spoonbot.send_chat(
|
|
95
|
+
f"Thankyou gift! {gift_event.donation_body.user.nickname}!!",
|
|
96
|
+
gift_event.authorization,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@spoonbot.follow_event
|
|
101
|
+
async def follow(follow_event: FollowEvent):
|
|
102
|
+
await spoonbot.send_chat(
|
|
103
|
+
f"Thankyou follow! {follow_event.follow_body.user.nickname}!!",
|
|
104
|
+
follow_event.authorization,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@spoonbot.end_event
|
|
109
|
+
async def end(end_event: EndEvent):
|
|
110
|
+
print(f"bye {end_event.authorization.id}")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
## 必要な環境変数
|
|
116
|
+
|
|
117
|
+
必須の物は必ず指定する必要があります。ただし、環境変数キーまたはSpoonBot初期荷時引数のどちらかで指定すればよいです。
|
|
118
|
+
|
|
119
|
+
| 環境変数キー | SpoonBot初期化時引数 | 説明 | 必須 |
|
|
120
|
+
| ------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
|
|
121
|
+
| SPOON_CLIENT_ID | spoon_client_id | Spoon Developpersのコンソールページで発行したClient id。 | ✅ |
|
|
122
|
+
| SPOON_CLIENT_SECRET | spoon_client_secret | `sk_live_` から始まる、Spoon Developpersのコンソールページで発行したClient Secret。この値は漏洩するとbotを乗っ取られるので、適切に管理します。 | ✅ |
|
|
123
|
+
| SPOON_OAUTH_REDIRECT_URL | oauth_redirect_url | Spoon Developpersのコンソールページで指定したコールバックURL。コンソールで指定したものと1字1句同じである必要があります。本ライブラリを用いて構築した場合、pathは自動的に「/oauth/callback」となります。 | ✅ |
|
|
124
|
+
| SPOON_SECRET_KEY | secret_key | ランダムな32文字以上のシークレットキーを生成して指定します。これはSpoon Developpersで発行されるものではありません。sessionのキーなどに利用するので、水平スケールする場合は全てのpodで同一のIDにする必要があります。<br>生成方法の一例: ```openssl rand -base64 22``` | ✅ |
|
|
125
|
+
| SPOON_BOT_COUNTRY | country | どの国のSpoonで提供するかを指定します。Spoon Developpersでbotを発行したドメインの国と合わせる必要があります。 JP | KR | ✅ |
|
|
126
|
+
| SPOON_BOT_PORT | port | spoonbotを立ち上げるポートを指定します。デフォルトは8080です。 | |
|
|
127
|
+
| SPOON_BOT_NAME | bot_name | Oauth連携ページや、Bot招待ページにて表示される名前を指定します。デフォルトは「Spoon Bot」です。 | |
|
|
128
|
+
| | bot_operation_page | botに関する追加のページを実装し提供する場合、そのパスもしくはURLを指定してください。Bot招待ページに引数で指定したページへ遷移するボタンが出現します。 例えば、web上で入力した値を保存しておき、毎回ライブの最初に送信するといったbotを実装するとき、該当のwebページのパスまたはurlを指定するとよいでしょう。 | |
|
|
129
|
+
|
|
130
|
+
## 実行
|
|
131
|
+
|
|
132
|
+
以下のコマンドで実行し、web上に公開します。必要に応じて上段にNginxなどを設ければよいと思いますが、小規模であればその必要もないと思います。
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python main.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
または
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv run main.py
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 連携
|
|
145
|
+
|
|
146
|
+
Botを連携するには、まず以下のURLを案内します。
|
|
147
|
+
https://{your domain}/spoonbot/connect
|
|
148
|
+
|
|
149
|
+
<img width="400" height="492" alt="spoonbot-test aakun-da f5 si_spoonbot_connect(iPhone 16)" src="https://github.com/user-attachments/assets/58e98073-f30e-4afd-87cf-28e622828f61" />
|
|
150
|
+
|
|
151
|
+
「ボットを連携する」をクリックし、spoonのページで認証認可を完了すると、以下のページへ遷移します
|
|
152
|
+
|
|
153
|
+
<img width="400" height="492" alt="spoonbot-test aakun-da f5 si_spoonbot_success(iPhone 16)" src="https://github.com/user-attachments/assets/3b3becba-e6cf-4e75-9209-9741c97a11c4" />
|
|
154
|
+
|
|
155
|
+
「ボット招待ページへ」を押した後、先ほど連携したアカウントで、**Spoonライブ配信を開始してください。**
|
|
156
|
+
ライブ配信を開始したら、「今すぐBotを招待する」を押してください。
|
|
157
|
+
|
|
158
|
+
<img width="400" height="480" alt="spoonbot-test aakun-da f5 si_spoonbot_invite(iPhone 16)" src="https://github.com/user-attachments/assets/03a6f1bc-90a0-4a71-afb5-63eca8222bc0" />
|
|
159
|
+
|
|
160
|
+
**Spoon Developpersを申請したアカウントがBotとして配信に入室してきたら、連携成功です。**
|
|
161
|
+
|
|
162
|
+
<img width="552" height="135" alt="スクリーンショット 2026-11-10 210610" src="https://github.com/user-attachments/assets/1ad98723-bd5c-4b5a-bafd-a04c819d3f51" />
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## 最後に
|
|
167
|
+
|
|
168
|
+
- このラッパーライブラリは現在ベータ版です
|
|
169
|
+
- 本ライブラリは㈱Spoonlabs Japan が提供するオーディオライブプラットフォーム「Spoon」内で利用できるAPIである「[Spoon Developpers](https://developers.spooncast.net/jp)」を便利に利用できるようにしたものですが、本ライブラリおよび開発者は㈱Spoonlabs Japan とは一切関係ありません。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spoonbot"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = 'An unofficial wrapper library that makes it easier to use the official API of the live streaming service "Spoon"'
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pydantic>=2.13.5",
|
|
9
|
+
"oauthlib>=4.0.0",
|
|
10
|
+
"aiohttp>=3.14.3",
|
|
11
|
+
"aiosqlite>=0.22.1",
|
|
12
|
+
"aiomysql>=0.3.2",
|
|
13
|
+
"uuid-utils>=1.0.0",
|
|
14
|
+
"itsdangerous>=2.2.0",
|
|
15
|
+
"aiohttp-sse-client2>=0.3.0",
|
|
16
|
+
"PyMySQL<=1.2.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[project.authors]]
|
|
20
|
+
name = "YomogiBeta"
|
|
21
|
+
|
|
22
|
+
[project.license]
|
|
23
|
+
file = "LICENSE"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
fastapi = ["fastapi>=0.142.2"]
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["uv_build>=0.13.0,<0.14.0"]
|
|
30
|
+
build-backend = "uv_build"
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"pyright>=1.1.414",
|
|
35
|
+
"ruff>=0.16.10",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = [
|
|
40
|
+
"E",
|
|
41
|
+
"F",
|
|
42
|
+
"PLE",
|
|
43
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spoonbot"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "An unofficial wrapper library that makes it easier to use the official API of the live streaming service \"Spoon\""
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "YomogiBeta" }
|
|
8
|
+
]
|
|
9
|
+
license = { file = "LICENSE" }
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pydantic>=2.13.5",
|
|
13
|
+
"oauthlib>=4.0.0",
|
|
14
|
+
"aiohttp>=3.14.3",
|
|
15
|
+
"aiosqlite>=0.22.1",
|
|
16
|
+
"aiomysql>=0.3.2",
|
|
17
|
+
"uuid-utils>=1.0.0",
|
|
18
|
+
"itsdangerous>=2.2.0",
|
|
19
|
+
"aiohttp-sse-client2>=0.3.0",
|
|
20
|
+
|
|
21
|
+
# https://github.com/aio-libs/aiomysql/issues/1080
|
|
22
|
+
# https://github.com/aio-libs/aiomysql/pull/1081
|
|
23
|
+
"PyMySQL<=1.2.0"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
fastapi = [
|
|
28
|
+
"fastapi>=0.142.2"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.13.0,<0.14.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pyright>=1.1.414",
|
|
38
|
+
"ruff>=0.16.10",
|
|
39
|
+
]
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
select = ["E", "F", "PLE"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from spoonbot.db.config.MySQLConfig import MySQLConfig
|
|
2
|
+
from spoonbot.dto.event.ChatEvent import ChatEvent
|
|
3
|
+
from spoonbot.dto.event.ConnectEvent import ConnectEvent
|
|
4
|
+
from spoonbot.dto.event.DonationEvent import GiftEvent
|
|
5
|
+
from spoonbot.dto.event.EndEvent import EndEvent
|
|
6
|
+
from spoonbot.dto.event.FollowEvent import FollowEvent
|
|
7
|
+
from spoonbot.dto.event.JoinEvent import JoinEvent
|
|
8
|
+
from spoonbot.dto.event.LikeEvent import LikeEvent
|
|
9
|
+
from spoonbot.enums.Country import Country
|
|
10
|
+
from spoonbot.enums.SpoonPermission import SpoonPermission
|
|
11
|
+
from spoonbot.spoon_bot import SpoonBot
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"MySQLConfig",
|
|
15
|
+
"ChatEvent",
|
|
16
|
+
"ConnectEvent",
|
|
17
|
+
"GiftEvent",
|
|
18
|
+
"EndEvent",
|
|
19
|
+
"FollowEvent",
|
|
20
|
+
"JoinEvent",
|
|
21
|
+
"LikeEvent",
|
|
22
|
+
"Country",
|
|
23
|
+
"SpoonPermission",
|
|
24
|
+
"SpoonBot",
|
|
25
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from spoonbot.constants import (
|
|
4
|
+
API_BASE_URL_JP,
|
|
5
|
+
API_BASE_URL_KR,
|
|
6
|
+
OAUTH_AUTHORIZE_BASE_URL_JP,
|
|
7
|
+
OAUTH_AUTHORIZE_BASE_URL_KR,
|
|
8
|
+
)
|
|
9
|
+
from spoonbot.enums.Country import Country
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConstantsConfig:
|
|
13
|
+
def __init__(self, oauth_authorize_base_url: str, api_base_url: str) -> None:
|
|
14
|
+
self.oauth_authorize_base_url = oauth_authorize_base_url
|
|
15
|
+
self.api_base_url = api_base_url
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def create(cls, country: Country) -> Self:
|
|
19
|
+
if country == Country.KR:
|
|
20
|
+
return cls(OAUTH_AUTHORIZE_BASE_URL_KR, API_BASE_URL_KR)
|
|
21
|
+
return cls(OAUTH_AUTHORIZE_BASE_URL_JP, API_BASE_URL_JP)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
OAUTH_AUTHORIZE_BASE_URL_KR = "https://developers.spooncast.net/kr/oauth/authorize"
|
|
2
|
+
OAUTH_AUTHORIZE_BASE_URL_JP = "https://developers.spooncast.net/jp/oauth/authorize"
|
|
3
|
+
|
|
4
|
+
API_BASE_URL_KR = "https://kr-openapi.spooncast.net/v1/"
|
|
5
|
+
API_BASE_URL_JP = "https://jp-openapi.spooncast.net/v1/"
|
|
6
|
+
|
|
7
|
+
SPACE = " "
|
|
8
|
+
# https://developers.spooncast.net/docs/en/api-reference/tokens
|
|
9
|
+
DEFAULT_REFRESH_TOKEN_EXPIRES_IN = 60 * 60 * 24 * 30 # 30 days
|
|
10
|
+
DEFAULT_SESSION_EXPIRES_IN = (
|
|
11
|
+
DEFAULT_REFRESH_TOKEN_EXPIRES_IN - 60 * 15
|
|
12
|
+
) # 29 days 23 hour 45 minutes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized
|
|
2
|
+
from aiohttp.web_request import Request
|
|
3
|
+
from aiohttp.web_response import Response, json_response
|
|
4
|
+
from aiohttp.web_routedef import RouteTableDef
|
|
5
|
+
|
|
6
|
+
from spoonbot.spoon_bot import SpoonBot
|
|
7
|
+
|
|
8
|
+
routes = RouteTableDef()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SpoonBotController:
|
|
12
|
+
spoon_bot: SpoonBot
|
|
13
|
+
|
|
14
|
+
def __init__(self, spoon_bot: SpoonBot) -> None:
|
|
15
|
+
self.spoon_bot = spoon_bot
|
|
16
|
+
|
|
17
|
+
routes.get("/api/v1/csrf-token")(self.csrf_token_function())
|
|
18
|
+
routes.get("/api/v1/bot")(self.bot_information_function())
|
|
19
|
+
routes.post("/api/v1/bot/invite")(self.bot_invite_request_handler())
|
|
20
|
+
|
|
21
|
+
def csrf_token_function(self):
|
|
22
|
+
async def csrf_token_handler(request: Request) -> Response:
|
|
23
|
+
session_id = request.cookies.get("session_id")
|
|
24
|
+
if session_id is None:
|
|
25
|
+
raise HTTPUnauthorized()
|
|
26
|
+
csrf_token = await self.spoon_bot.regenerate_csrf_token(session_id)
|
|
27
|
+
return json_response({"csrf_token": csrf_token})
|
|
28
|
+
|
|
29
|
+
return csrf_token_handler
|
|
30
|
+
|
|
31
|
+
def bot_information_function(self):
|
|
32
|
+
async def bot_information_handler(_: Request) -> Response:
|
|
33
|
+
page = self.spoon_bot.get_bot_operation_page()
|
|
34
|
+
return json_response(
|
|
35
|
+
{"bot_name": self.spoon_bot.get_bot_name(), "page": page}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return bot_information_handler
|
|
39
|
+
|
|
40
|
+
def bot_invite_request_handler(self):
|
|
41
|
+
async def bot_invite_request(request: Request) -> Response:
|
|
42
|
+
session_id = request.cookies.get("session_id")
|
|
43
|
+
csrf_token = request.headers.get("X-CSRF-TOKEN")
|
|
44
|
+
if session_id is None:
|
|
45
|
+
raise HTTPUnauthorized()
|
|
46
|
+
if csrf_token is None:
|
|
47
|
+
raise HTTPForbidden(reason="Bad Request. Need X-CSRF-TOKEN")
|
|
48
|
+
result = await self.spoon_bot.start_stream_by_session(
|
|
49
|
+
session_id, csrf_token
|
|
50
|
+
)
|
|
51
|
+
return Response(status=204 if result else 409)
|
|
52
|
+
|
|
53
|
+
return bot_invite_request
|
|
54
|
+
|
|
55
|
+
def get_route(self) -> RouteTableDef:
|
|
56
|
+
return routes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from aiohttp.web_exceptions import HTTPNotFound
|
|
5
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
6
|
+
from aiohttp.web_request import Request
|
|
7
|
+
from aiohttp.web_response import StreamResponse
|
|
8
|
+
from aiohttp.web_routedef import RouteTableDef
|
|
9
|
+
|
|
10
|
+
routes = RouteTableDef()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_base_dir() -> Path:
|
|
14
|
+
# PyInstaller などで実行ファイル化されている場合
|
|
15
|
+
meipass = getattr(sys, "_MEIPASS", None)
|
|
16
|
+
if meipass is not None:
|
|
17
|
+
return Path(meipass)
|
|
18
|
+
|
|
19
|
+
if getattr(sys, "frozen", False):
|
|
20
|
+
return Path(sys.executable).resolve().parent
|
|
21
|
+
|
|
22
|
+
# src/controller/xxx.py
|
|
23
|
+
return Path(__file__).resolve().parents[3]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
PAGES = {
|
|
27
|
+
"connect": "connect.html",
|
|
28
|
+
"success": "success.html",
|
|
29
|
+
"fail": "fail.html",
|
|
30
|
+
"invite": "invite.html",
|
|
31
|
+
}
|
|
32
|
+
ASSETS_DIR = _get_base_dir() / "assets"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SpoonBotWebController:
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
for page in PAGES:
|
|
38
|
+
routes.get(f"/spoonbot/{page}")(self.serve_page)
|
|
39
|
+
routes.static("/assets/", ASSETS_DIR)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
async def serve_page(request: Request) -> StreamResponse:
|
|
43
|
+
filename = PAGES[request.path[len("/spoonbot/") :]]
|
|
44
|
+
filepath = ASSETS_DIR / filename
|
|
45
|
+
|
|
46
|
+
if not filepath.is_file():
|
|
47
|
+
raise HTTPNotFound()
|
|
48
|
+
|
|
49
|
+
return FileResponse(filepath)
|
|
50
|
+
|
|
51
|
+
def get_route(self) -> RouteTableDef:
|
|
52
|
+
return routes
|