micup 0.0.1__tar.gz → 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.
- micup-0.1.0/PKG-INFO +55 -0
- micup-0.1.0/README.md +46 -0
- micup-0.1.0/micup/__init__.py +12 -0
- micup-0.1.0/micup/client.py +384 -0
- micup-0.1.0/micup/constants.py +13 -0
- micup-0.1.0/micup/errors.py +14 -0
- micup-0.1.0/micup/models.py +50 -0
- micup-0.1.0/micup/permissions.py +30 -0
- micup-0.1.0/micup/utils.py +7 -0
- micup-0.1.0/micup/voice.py +190 -0
- micup-0.1.0/micup.egg-info/PKG-INFO +55 -0
- micup-0.1.0/micup.egg-info/SOURCES.txt +15 -0
- {micup-0.0.1 → micup-0.1.0}/micup.egg-info/requires.txt +1 -0
- micup-0.1.0/pyproject.toml +17 -0
- micup-0.0.1/PKG-INFO +0 -28
- micup-0.0.1/README.md +0 -15
- micup-0.0.1/micup/init.py +0 -3
- micup-0.0.1/micup.egg-info/PKG-INFO +0 -28
- micup-0.0.1/micup.egg-info/SOURCES.txt +0 -8
- micup-0.0.1/pyproject.toml +0 -21
- {micup-0.0.1 → micup-0.1.0}/micup.egg-info/dependency_links.txt +0 -0
- {micup-0.0.1 → micup-0.1.0}/micup.egg-info/top_level.txt +0 -0
- {micup-0.0.1 → micup-0.1.0}/setup.cfg +0 -0
micup-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: micup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Micup platformu için Python kütüphanesi.
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: aiohttp>=3.9
|
|
8
|
+
Requires-Dist: livekit
|
|
9
|
+
|
|
10
|
+
# micup
|
|
11
|
+
|
|
12
|
+
Micup platformu için Python kütüphanesi. Bot yazmak için mesaj, komut, olay, yetki ve ses yönetimini tek yerden sağlar.
|
|
13
|
+
|
|
14
|
+
## Kurulum
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
pip install micup
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Hızlı başlangıç
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from micup import Bot
|
|
24
|
+
|
|
25
|
+
bot = Bot() # token MICUP_TOKEN ortam değişkeninden okunur
|
|
26
|
+
|
|
27
|
+
@bot.command("!ping")
|
|
28
|
+
async def ping(msg):
|
|
29
|
+
return "pong"
|
|
30
|
+
|
|
31
|
+
bot.run()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Token'ı kodun içine yazma. PowerShell'de:
|
|
35
|
+
|
|
36
|
+
```powershell
|
|
37
|
+
$env:MICUP_TOKEN = "uygulama_id.gizli_anahtar"
|
|
38
|
+
python bot.py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Özellikler
|
|
42
|
+
|
|
43
|
+
- Gateway bağlantısı, kopunca otomatik yeniden bağlanma (resume destekli)
|
|
44
|
+
- Komutlar ve olaylar: `@bot.command(...)`, `@bot.on(...)`
|
|
45
|
+
- Mesaj işlemleri: gönder, düzenle, sil, toplu sil, listele
|
|
46
|
+
- Ham REST erişimi: `bot.rest(...)`
|
|
47
|
+
- Ses: kanala katılma, ayrılma, durum okuma (LiveKit medya bağlantısı dahil)
|
|
48
|
+
- Durum: `bot.set_presence(...)`
|
|
49
|
+
- Yetki bitleri ve snowflake yardımcıları
|
|
50
|
+
|
|
51
|
+
Ayrıntılı kullanım için dokümantasyona bak.
|
|
52
|
+
|
|
53
|
+
## Lisans
|
|
54
|
+
|
|
55
|
+
Henüz belirlenmedi.
|
micup-0.1.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# micup
|
|
2
|
+
|
|
3
|
+
Micup platformu için Python kütüphanesi. Bot yazmak için mesaj, komut, olay, yetki ve ses yönetimini tek yerden sağlar.
|
|
4
|
+
|
|
5
|
+
## Kurulum
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install micup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Hızlı başlangıç
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from micup import Bot
|
|
15
|
+
|
|
16
|
+
bot = Bot() # token MICUP_TOKEN ortam değişkeninden okunur
|
|
17
|
+
|
|
18
|
+
@bot.command("!ping")
|
|
19
|
+
async def ping(msg):
|
|
20
|
+
return "pong"
|
|
21
|
+
|
|
22
|
+
bot.run()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Token'ı kodun içine yazma. PowerShell'de:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
$env:MICUP_TOKEN = "uygulama_id.gizli_anahtar"
|
|
29
|
+
python bot.py
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Özellikler
|
|
33
|
+
|
|
34
|
+
- Gateway bağlantısı, kopunca otomatik yeniden bağlanma (resume destekli)
|
|
35
|
+
- Komutlar ve olaylar: `@bot.command(...)`, `@bot.on(...)`
|
|
36
|
+
- Mesaj işlemleri: gönder, düzenle, sil, toplu sil, listele
|
|
37
|
+
- Ham REST erişimi: `bot.rest(...)`
|
|
38
|
+
- Ses: kanala katılma, ayrılma, durum okuma (LiveKit medya bağlantısı dahil)
|
|
39
|
+
- Durum: `bot.set_presence(...)`
|
|
40
|
+
- Yetki bitleri ve snowflake yardımcıları
|
|
41
|
+
|
|
42
|
+
Ayrıntılı kullanım için dokümantasyona bak.
|
|
43
|
+
|
|
44
|
+
## Lisans
|
|
45
|
+
|
|
46
|
+
Henüz belirlenmedi.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .client import Bot
|
|
2
|
+
from .errors import MicupAPIError, MicupError
|
|
3
|
+
from .models import Message, User
|
|
4
|
+
from .permissions import Permissions
|
|
5
|
+
from .utils import snowflake_to_datetime
|
|
6
|
+
from .voice import Voice
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Bot", "Message", "User", "Voice", "Permissions",
|
|
11
|
+
"MicupAPIError", "MicupError", "snowflake_to_datetime",
|
|
12
|
+
]
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import aiohttp
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
8
|
+
from .constants import DEFAULT_IGNORED_EVENTS, DEFAULT_ORIGIN, FATAL_CLOSE_CODES, RATE_LIMIT_CLOSE_CODE, RESET_CLOSE_CODES, log
|
|
9
|
+
from .errors import MicupAPIError
|
|
10
|
+
from .models import Message, User
|
|
11
|
+
from .voice import Voice
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Bot:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
token: Optional[str] = None,
|
|
18
|
+
*,
|
|
19
|
+
ignored_events: Optional[List[str]] = None,
|
|
20
|
+
origin: str = DEFAULT_ORIGIN,
|
|
21
|
+
presence: Optional[Dict[str, Any]] = None,
|
|
22
|
+
):
|
|
23
|
+
self.token = token or os.environ.get("MICUP_TOKEN")
|
|
24
|
+
if not self.token:
|
|
25
|
+
raise ValueError("Token yok. Bot(token) ver ya da MICUP_TOKEN ortam değişkenini ayarla.")
|
|
26
|
+
|
|
27
|
+
self.origin = origin.rstrip("/")
|
|
28
|
+
self.ignored_events = list(DEFAULT_IGNORED_EVENTS if ignored_events is None else ignored_events)
|
|
29
|
+
self.presence = presence
|
|
30
|
+
|
|
31
|
+
self.user: Optional[User] = None
|
|
32
|
+
self.session_id: Optional[str] = None
|
|
33
|
+
self.sequence: Optional[int] = None
|
|
34
|
+
self.voice = Voice(self)
|
|
35
|
+
|
|
36
|
+
self._api_url: Optional[str] = None
|
|
37
|
+
self._gateway_url: Optional[str] = None
|
|
38
|
+
self._http: Optional[aiohttp.ClientSession] = None
|
|
39
|
+
self._ws: Optional[aiohttp.ClientWebSocketResponse] = None
|
|
40
|
+
self._heartbeat_interval: Optional[float] = None
|
|
41
|
+
self._running = False
|
|
42
|
+
self._ready = False
|
|
43
|
+
self._handlers: Dict[str, List[Callable]] = {}
|
|
44
|
+
self._commands: Dict[str, Callable] = {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def command(self, prefix: str):
|
|
48
|
+
def decorator(func: Callable):
|
|
49
|
+
self._commands[prefix] = func
|
|
50
|
+
return func
|
|
51
|
+
return decorator
|
|
52
|
+
|
|
53
|
+
def on(self, event: str):
|
|
54
|
+
def decorator(func: Callable):
|
|
55
|
+
self._handlers.setdefault(event.lower(), []).append(func)
|
|
56
|
+
return func
|
|
57
|
+
return decorator
|
|
58
|
+
|
|
59
|
+
def is_ready(self) -> bool:
|
|
60
|
+
return self._ready
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def _request(self, method: str, path: str, **kwargs) -> Any:
|
|
64
|
+
if self._http is None or self._api_url is None:
|
|
65
|
+
raise RuntimeError("Bot bağlı değil. Önce bot.run() ile başlat.")
|
|
66
|
+
headers = {"Authorization": f"Bot {self.token}"}
|
|
67
|
+
url = f"{self._api_url}{path}"
|
|
68
|
+
|
|
69
|
+
for _ in range(5):
|
|
70
|
+
async with self._http.request(method, url, headers=headers, **kwargs) as resp:
|
|
71
|
+
if resp.status == 204:
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
data = await resp.json(content_type=None)
|
|
75
|
+
except Exception:
|
|
76
|
+
data = None
|
|
77
|
+
|
|
78
|
+
if resp.status == 429:
|
|
79
|
+
body = data if isinstance(data, dict) else {}
|
|
80
|
+
wait = float(body.get("retry_after", 1))
|
|
81
|
+
log.warning("Rate limit: %.2f sn bekleniyor (%s)", wait, path)
|
|
82
|
+
await asyncio.sleep(wait + 0.05)
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if resp.status >= 400:
|
|
86
|
+
body = data if isinstance(data, dict) else {}
|
|
87
|
+
raise MicupAPIError(
|
|
88
|
+
resp.status,
|
|
89
|
+
body.get("code", ""),
|
|
90
|
+
body.get("message", resp.reason or ""),
|
|
91
|
+
body,
|
|
92
|
+
)
|
|
93
|
+
return data
|
|
94
|
+
|
|
95
|
+
raise MicupAPIError(429, "RATE_LIMITED", "Çok fazla yeniden deneme")
|
|
96
|
+
|
|
97
|
+
async def rest(self, method: str, path: str, body: Any = None) -> Any:
|
|
98
|
+
kwargs: Dict[str, Any] = {}
|
|
99
|
+
if body is not None:
|
|
100
|
+
kwargs["json"] = body
|
|
101
|
+
return await self._request(method.upper(), f"/v1{path}", **kwargs)
|
|
102
|
+
|
|
103
|
+
async def send_message(
|
|
104
|
+
self,
|
|
105
|
+
channel_id: str,
|
|
106
|
+
content: Optional[str] = None,
|
|
107
|
+
reply_to: Optional[str] = None,
|
|
108
|
+
**fields: Any,
|
|
109
|
+
) -> Message:
|
|
110
|
+
body: Dict[str, Any] = {k: v for k, v in fields.items() if v is not None}
|
|
111
|
+
if content is not None:
|
|
112
|
+
body["content"] = content
|
|
113
|
+
body.setdefault("allowed_mentions", {"parse": []})
|
|
114
|
+
if reply_to:
|
|
115
|
+
body["message_reference"] = {"message_id": reply_to, "channel_id": channel_id}
|
|
116
|
+
data = await self._request("POST", f"/v1/channels/{channel_id}/messages", json=body)
|
|
117
|
+
return Message(data, self)
|
|
118
|
+
|
|
119
|
+
async def edit_message(self, channel_id: str, message_id: str, content: str) -> Message:
|
|
120
|
+
data = await self._request(
|
|
121
|
+
"PATCH", f"/v1/channels/{channel_id}/messages/{message_id}", json={"content": content}
|
|
122
|
+
)
|
|
123
|
+
return Message(data, self)
|
|
124
|
+
|
|
125
|
+
async def delete_message(self, channel_id: str, message_id: str) -> None:
|
|
126
|
+
await self._request("DELETE", f"/v1/channels/{channel_id}/messages/{message_id}")
|
|
127
|
+
|
|
128
|
+
async def bulk_delete(self, channel_id: str, message_ids: List[str]) -> int:
|
|
129
|
+
ids = list(message_ids)
|
|
130
|
+
for i in range(0, len(ids), 100):
|
|
131
|
+
await self._request(
|
|
132
|
+
"POST",
|
|
133
|
+
f"/v1/channels/{channel_id}/messages/bulk-delete",
|
|
134
|
+
json={"message_ids": ids[i:i + 100]},
|
|
135
|
+
)
|
|
136
|
+
return len(ids)
|
|
137
|
+
|
|
138
|
+
async def get_messages(
|
|
139
|
+
self,
|
|
140
|
+
channel_id: str,
|
|
141
|
+
limit: int = 50,
|
|
142
|
+
before: Optional[str] = None,
|
|
143
|
+
after: Optional[str] = None,
|
|
144
|
+
around: Optional[str] = None,
|
|
145
|
+
) -> List[Message]:
|
|
146
|
+
params: Dict[str, Any] = {"limit": max(1, min(int(limit), 100))}
|
|
147
|
+
if before:
|
|
148
|
+
params["before"] = before
|
|
149
|
+
if after:
|
|
150
|
+
params["after"] = after
|
|
151
|
+
if around:
|
|
152
|
+
params["around"] = around
|
|
153
|
+
data = await self._request("GET", f"/v1/channels/{channel_id}/messages", params=params)
|
|
154
|
+
return [Message(m, self) for m in data or []]
|
|
155
|
+
|
|
156
|
+
async def get_channel(self, channel_id: str) -> Dict[str, Any]:
|
|
157
|
+
return await self._request("GET", f"/v1/channels/{channel_id}")
|
|
158
|
+
|
|
159
|
+
async def get_user(self, user_id: str) -> User:
|
|
160
|
+
return User(await self._request("GET", f"/v1/users/{user_id}"))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def set_presence(
|
|
164
|
+
self,
|
|
165
|
+
status: str,
|
|
166
|
+
*,
|
|
167
|
+
afk: bool = False,
|
|
168
|
+
mobile: bool = False,
|
|
169
|
+
custom_status: Optional[Any] = None,
|
|
170
|
+
) -> None:
|
|
171
|
+
valid = {"online", "idle", "dnd", "invisible", "offline"}
|
|
172
|
+
if status not in valid:
|
|
173
|
+
raise ValueError(f"Geçersiz durum: {status}. Şunlardan biri olmalı: {sorted(valid)}")
|
|
174
|
+
d: Dict[str, Any] = {"status": status, "afk": afk, "mobile": mobile}
|
|
175
|
+
if custom_status is not None:
|
|
176
|
+
d["custom_status"] = {"text": custom_status} if isinstance(custom_status, str) else custom_status
|
|
177
|
+
await self._send({"op": 3, "d": d})
|
|
178
|
+
|
|
179
|
+
async def _discover(self) -> None:
|
|
180
|
+
async with aiohttp.ClientSession() as session:
|
|
181
|
+
async with session.get(f"{self.origin}/.well-known/fluxer") as resp:
|
|
182
|
+
if resp.status != 200:
|
|
183
|
+
raise MicupAPIError(resp.status, "DISCOVERY_FAILED", "Keşif belgesi alınamadı")
|
|
184
|
+
data = await resp.json(content_type=None)
|
|
185
|
+
self._api_url = data["endpoints"]["api_public"].rstrip("/")
|
|
186
|
+
self._gateway_url = data["endpoints"]["gateway"]
|
|
187
|
+
|
|
188
|
+
async def _send(self, payload: Dict[str, Any]) -> None:
|
|
189
|
+
if self._ws is None:
|
|
190
|
+
raise RuntimeError("Gateway bağlantısı yok")
|
|
191
|
+
await self._ws.send_json(payload)
|
|
192
|
+
|
|
193
|
+
async def _heartbeat(self, ws: aiohttp.ClientWebSocketResponse) -> None:
|
|
194
|
+
try:
|
|
195
|
+
await asyncio.sleep(self._heartbeat_interval * random.random())
|
|
196
|
+
while True:
|
|
197
|
+
await ws.send_json({"op": 1, "d": self.sequence})
|
|
198
|
+
await asyncio.sleep(self._heartbeat_interval)
|
|
199
|
+
except Exception:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
async def _identify(self) -> None:
|
|
203
|
+
await self._send({
|
|
204
|
+
"op": 2,
|
|
205
|
+
"d": {
|
|
206
|
+
"token": self.token,
|
|
207
|
+
"properties": {"os": "linux", "browser": "micup.py", "device": "micup.py"},
|
|
208
|
+
"presence": self.presence or {"status": "online", "afk": False, "mobile": False},
|
|
209
|
+
"ignored_events": self.ignored_events,
|
|
210
|
+
"flags": 0,
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
async def _resume(self) -> None:
|
|
215
|
+
await self._send({
|
|
216
|
+
"op": 6,
|
|
217
|
+
"d": {"token": self.token, "session_id": self.session_id, "seq": self.sequence},
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
async def _connect(self) -> Optional[int]:
|
|
221
|
+
async with self._http.ws_connect(f"{self._gateway_url}?v=1&encoding=json", max_msg_size=0) as ws:
|
|
222
|
+
self._ws = ws
|
|
223
|
+
hello = await ws.receive_json()
|
|
224
|
+
if hello.get("op") != 10:
|
|
225
|
+
raise MicupAPIError(0, "INVALID_HELLO", "Hello (op 10) bekleniyordu")
|
|
226
|
+
self._heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000.0
|
|
227
|
+
|
|
228
|
+
hb = asyncio.create_task(self._heartbeat(ws))
|
|
229
|
+
try:
|
|
230
|
+
if self.session_id and self.sequence is not None:
|
|
231
|
+
await self._resume()
|
|
232
|
+
else:
|
|
233
|
+
await self._identify()
|
|
234
|
+
|
|
235
|
+
async for raw in ws:
|
|
236
|
+
if raw.type == aiohttp.WSMsgType.TEXT:
|
|
237
|
+
await self._handle(json.loads(raw.data))
|
|
238
|
+
elif raw.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR):
|
|
239
|
+
break
|
|
240
|
+
finally:
|
|
241
|
+
hb.cancel()
|
|
242
|
+
self._ws = None
|
|
243
|
+
|
|
244
|
+
close_code = ws.close_code
|
|
245
|
+
if close_code in RESET_CLOSE_CODES:
|
|
246
|
+
self.session_id = None
|
|
247
|
+
self.sequence = None
|
|
248
|
+
return close_code
|
|
249
|
+
|
|
250
|
+
async def _handle(self, msg: Dict[str, Any]) -> None:
|
|
251
|
+
op = msg.get("op")
|
|
252
|
+
if op == 0:
|
|
253
|
+
await self._dispatch(msg.get("t"), msg.get("d") or {}, msg.get("s"))
|
|
254
|
+
elif op == 1:
|
|
255
|
+
await self._send({"op": 1, "d": self.sequence})
|
|
256
|
+
elif op == 7:
|
|
257
|
+
await self._ws.close()
|
|
258
|
+
elif op == 9:
|
|
259
|
+
if not msg.get("d"):
|
|
260
|
+
self.session_id = None
|
|
261
|
+
self.sequence = None
|
|
262
|
+
await self._ws.close()
|
|
263
|
+
elif op == 11:
|
|
264
|
+
pass
|
|
265
|
+
else:
|
|
266
|
+
log.debug("Bilinmeyen opcode: %s", op)
|
|
267
|
+
|
|
268
|
+
async def _dispatch(self, event: Optional[str], data: Dict[str, Any], seq: Optional[int]) -> None:
|
|
269
|
+
if seq is not None:
|
|
270
|
+
self.sequence = seq
|
|
271
|
+
if not event:
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
if event == "READY":
|
|
275
|
+
self.session_id = data.get("session_id")
|
|
276
|
+
self.user = User(data.get("user") or {})
|
|
277
|
+
self._ready = True
|
|
278
|
+
log.info("Bağlandı: %s", self.user.username)
|
|
279
|
+
await self._emit("ready", self)
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
if event == "RESUMED":
|
|
283
|
+
self._ready = True
|
|
284
|
+
log.info("Oturum devam ettirildi")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
if event == "MESSAGE_CREATE":
|
|
288
|
+
msg = Message(data, self)
|
|
289
|
+
await self._emit("message_create", msg)
|
|
290
|
+
try:
|
|
291
|
+
await self._run_commands(msg)
|
|
292
|
+
except Exception:
|
|
293
|
+
log.exception("Komut işlenirken hata")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
if event == "GUILD_CREATE":
|
|
297
|
+
self.voice._load_guild(data)
|
|
298
|
+
elif event == "VOICE_STATE_UPDATE":
|
|
299
|
+
self.voice._on_state(data)
|
|
300
|
+
elif event == "VOICE_SERVER_UPDATE":
|
|
301
|
+
self.voice._on_server(data)
|
|
302
|
+
|
|
303
|
+
await self._emit(event.lower(), data)
|
|
304
|
+
|
|
305
|
+
async def _emit(self, name: str, *args: Any) -> None:
|
|
306
|
+
for handler in self._handlers.get(name, []):
|
|
307
|
+
await self._call(handler, *args)
|
|
308
|
+
|
|
309
|
+
async def _call(self, func: Callable, *args: Any) -> Any:
|
|
310
|
+
try:
|
|
311
|
+
result = func(*args)
|
|
312
|
+
if asyncio.iscoroutine(result):
|
|
313
|
+
result = await result
|
|
314
|
+
return result
|
|
315
|
+
except Exception:
|
|
316
|
+
log.exception("Handler hatası: %s", getattr(func, "__name__", func))
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
async def _run_commands(self, msg: Message) -> None:
|
|
320
|
+
if msg.author.bot or (self.user and msg.author.id == self.user.id):
|
|
321
|
+
return
|
|
322
|
+
for prefix in sorted(self._commands, key=len, reverse=True):
|
|
323
|
+
if msg.content == prefix or msg.content.startswith(prefix + " "):
|
|
324
|
+
result = await self._call(self._commands[prefix], msg)
|
|
325
|
+
if isinstance(result, str) and result:
|
|
326
|
+
await msg.reply(result)
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
async def start(self) -> None:
|
|
331
|
+
self._running = True
|
|
332
|
+
self._http = aiohttp.ClientSession()
|
|
333
|
+
delay = 1.0
|
|
334
|
+
try:
|
|
335
|
+
while self._running:
|
|
336
|
+
try:
|
|
337
|
+
await self._discover()
|
|
338
|
+
code = await self._connect()
|
|
339
|
+
self._ready = False
|
|
340
|
+
await self._emit("disconnect", code, "")
|
|
341
|
+
if not self._running:
|
|
342
|
+
break
|
|
343
|
+
if code in FATAL_CLOSE_CODES:
|
|
344
|
+
log.error("Gateway kalıcı olarak kapattı (%s). Bot durduruldu.", code)
|
|
345
|
+
break
|
|
346
|
+
if code == RATE_LIMIT_CLOSE_CODE:
|
|
347
|
+
log.warning("Çok sık bağlanıldı, 30 sn bekleniyor.")
|
|
348
|
+
await asyncio.sleep(30)
|
|
349
|
+
else:
|
|
350
|
+
await asyncio.sleep(1)
|
|
351
|
+
delay = 1.0
|
|
352
|
+
except asyncio.CancelledError:
|
|
353
|
+
raise
|
|
354
|
+
except Exception as e:
|
|
355
|
+
self._ready = False
|
|
356
|
+
if not self._running:
|
|
357
|
+
break
|
|
358
|
+
await self._emit("error", e)
|
|
359
|
+
log.warning("Bağlantı hatası: %s (%.0f sn sonra tekrar)", e, delay)
|
|
360
|
+
await asyncio.sleep(delay)
|
|
361
|
+
delay = min(delay * 2, 60)
|
|
362
|
+
finally:
|
|
363
|
+
self._running = False
|
|
364
|
+
self._ready = False
|
|
365
|
+
if self._http is not None:
|
|
366
|
+
await self._http.close()
|
|
367
|
+
self._http = None
|
|
368
|
+
|
|
369
|
+
async def close(self) -> None:
|
|
370
|
+
self._running = False
|
|
371
|
+
await self.voice.disconnect_all()
|
|
372
|
+
if self._ws is not None and not self._ws.closed:
|
|
373
|
+
await self._ws.close()
|
|
374
|
+
|
|
375
|
+
async def destroy(self) -> None:
|
|
376
|
+
await self.close()
|
|
377
|
+
|
|
378
|
+
def run(self) -> None:
|
|
379
|
+
if not logging.getLogger().handlers:
|
|
380
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
381
|
+
try:
|
|
382
|
+
asyncio.run(self.start())
|
|
383
|
+
except KeyboardInterrupt:
|
|
384
|
+
log.info("Bot durduruldu")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
DEFAULT_ORIGIN = "https://micup.gg"
|
|
5
|
+
SNOWFLAKE_EPOCH_MS = 1420070400000
|
|
6
|
+
|
|
7
|
+
DEFAULT_IGNORED_EVENTS = ["TYPING_START", "PRESENCE_UPDATE"]
|
|
8
|
+
|
|
9
|
+
FATAL_CLOSE_CODES = {4004, 4010, 4011, 4012}
|
|
10
|
+
RESET_CLOSE_CODES = {4001, 4002, 4003, 4005, 4007}
|
|
11
|
+
RATE_LIMIT_CLOSE_CODE = 4008
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger("micup")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MicupAPIError(Exception):
|
|
5
|
+
|
|
6
|
+
def __init__(self, status: int, code: str, message: str, body: Any = None):
|
|
7
|
+
super().__init__(f"HTTP {status} {code}: {message}")
|
|
8
|
+
self.status = status
|
|
9
|
+
self.code = code
|
|
10
|
+
self.message = message
|
|
11
|
+
self.body = body
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
MicupError = MicupAPIError
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from .utils import snowflake_to_datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class User:
|
|
6
|
+
|
|
7
|
+
def __init__(self, data: Dict[str, Any]):
|
|
8
|
+
self.raw = data
|
|
9
|
+
self.id: Optional[str] = data.get("id")
|
|
10
|
+
self.username: Optional[str] = data.get("username")
|
|
11
|
+
self.global_name: Optional[str] = data.get("global_name")
|
|
12
|
+
self.bot: bool = bool(data.get("bot", False))
|
|
13
|
+
|
|
14
|
+
def __repr__(self) -> str:
|
|
15
|
+
return f"<User {self.username} ({self.id})>"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Message:
|
|
19
|
+
|
|
20
|
+
def __init__(self, data: Dict[str, Any], bot: "Bot"):
|
|
21
|
+
self._bot = bot
|
|
22
|
+
self.raw = data
|
|
23
|
+
self.id: str = data["id"]
|
|
24
|
+
self.channel_id: str = data["channel_id"]
|
|
25
|
+
self.guild_id: Optional[str] = data.get("guild_id")
|
|
26
|
+
self.member: Optional[Dict[str, Any]] = data.get("member")
|
|
27
|
+
self.content: str = data.get("content", "")
|
|
28
|
+
self.author = User(data.get("author") or {})
|
|
29
|
+
self.pinned: bool = bool(data.get("pinned", False))
|
|
30
|
+
self.created_at = snowflake_to_datetime(self.id)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def is_bot(self) -> bool:
|
|
34
|
+
return self.author.bot
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def args(self) -> List[str]:
|
|
38
|
+
return self.content.split()[1:]
|
|
39
|
+
|
|
40
|
+
async def reply(self, content: Optional[str] = None, **fields: Any) -> "Message":
|
|
41
|
+
return await self._bot.send_message(self.channel_id, content, reply_to=self.id, **fields)
|
|
42
|
+
|
|
43
|
+
async def send(self, content: Optional[str] = None, **fields: Any) -> "Message":
|
|
44
|
+
return await self._bot.send_message(self.channel_id, content, **fields)
|
|
45
|
+
|
|
46
|
+
async def edit(self, content: str) -> "Message":
|
|
47
|
+
return await self._bot.edit_message(self.channel_id, self.id, content)
|
|
48
|
+
|
|
49
|
+
async def delete(self) -> None:
|
|
50
|
+
await self._bot.delete_message(self.channel_id, self.id)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Permissions:
|
|
5
|
+
|
|
6
|
+
ADMINISTRATOR = 1 << 3
|
|
7
|
+
MANAGE_CHANNELS = 1 << 4
|
|
8
|
+
ADD_REACTIONS = 1 << 6
|
|
9
|
+
VIEW_CHANNEL = 1 << 10
|
|
10
|
+
SEND_MESSAGES = 1 << 11
|
|
11
|
+
MANAGE_MESSAGES = 1 << 13
|
|
12
|
+
EMBED_LINKS = 1 << 14
|
|
13
|
+
ATTACH_FILES = 1 << 15
|
|
14
|
+
READ_MESSAGE_HISTORY = 1 << 16
|
|
15
|
+
MENTION_EVERYONE = 1 << 17
|
|
16
|
+
CONNECT = 1 << 20
|
|
17
|
+
SPEAK = 1 << 21
|
|
18
|
+
MUTE_MEMBERS = 1 << 22
|
|
19
|
+
DEAFEN_MEMBERS = 1 << 23
|
|
20
|
+
MOVE_MEMBERS = 1 << 24
|
|
21
|
+
MANAGE_ROLES = 1 << 28
|
|
22
|
+
STREAM = 1 << 37
|
|
23
|
+
PIN_MESSAGES = 1 << 51
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def has(mask: Any, perm: int) -> bool:
|
|
27
|
+
mask = int(mask)
|
|
28
|
+
if mask & Permissions.ADMINISTRATOR:
|
|
29
|
+
return True
|
|
30
|
+
return (mask & perm) == perm
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
from .constants import log
|
|
4
|
+
from .errors import MicupAPIError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Voice:
|
|
8
|
+
|
|
9
|
+
def __init__(self, bot: "Bot"):
|
|
10
|
+
self._bot = bot
|
|
11
|
+
self._states: Dict[str, Dict[str, Dict[str, Any]]] = {}
|
|
12
|
+
self._grants: Dict[str, asyncio.Future] = {}
|
|
13
|
+
self._mine: Dict[str, Dict[str, Any]] = {}
|
|
14
|
+
self._rooms: Dict[str, Any] = {}
|
|
15
|
+
|
|
16
|
+
async def _connect_media(self, guild_id: str, grant: Dict[str, Any]) -> None:
|
|
17
|
+
try:
|
|
18
|
+
from livekit import rtc
|
|
19
|
+
except ImportError:
|
|
20
|
+
raise MicupAPIError(0, "LIVEKIT_MISSING", "Ses medyası için 'pip install livekit' gerekli")
|
|
21
|
+
|
|
22
|
+
old = self._rooms.pop(guild_id, None)
|
|
23
|
+
if old is not None:
|
|
24
|
+
await old.disconnect()
|
|
25
|
+
|
|
26
|
+
room = rtc.Room()
|
|
27
|
+
await room.connect(grant["endpoint"], grant["token"])
|
|
28
|
+
self._rooms[guild_id] = room
|
|
29
|
+
log.info("LiveKit odasına bağlandı (guild %s)", guild_id)
|
|
30
|
+
|
|
31
|
+
async def disconnect_all(self) -> None:
|
|
32
|
+
for guild_id in list(self._rooms):
|
|
33
|
+
room = self._rooms.pop(guild_id)
|
|
34
|
+
try:
|
|
35
|
+
await room.disconnect()
|
|
36
|
+
except Exception as e:
|
|
37
|
+
log.warning("LiveKit kapatma hatası (%s): %s", guild_id, e)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _load_guild(self, guild: Dict[str, Any]) -> None:
|
|
41
|
+
gid = guild.get("id")
|
|
42
|
+
for st in guild.get("voice_states") or []:
|
|
43
|
+
self._on_state(dict(st, guild_id=st.get("guild_id") or gid))
|
|
44
|
+
|
|
45
|
+
def _on_state(self, st: Dict[str, Any]) -> None:
|
|
46
|
+
gid = st.get("guild_id")
|
|
47
|
+
if gid is None:
|
|
48
|
+
return
|
|
49
|
+
key = st.get("connection_id") or st.get("user_id")
|
|
50
|
+
if not key:
|
|
51
|
+
return
|
|
52
|
+
states = self._states.setdefault(gid, {})
|
|
53
|
+
is_me = self._bot.user is not None and st.get("user_id") == self._bot.user.id
|
|
54
|
+
if st.get("channel_id") is None:
|
|
55
|
+
states.pop(key, None)
|
|
56
|
+
if is_me:
|
|
57
|
+
self._mine.pop(gid, None)
|
|
58
|
+
else:
|
|
59
|
+
states[key] = st
|
|
60
|
+
if is_me and gid in self._mine and st.get("connection_id") == self._mine[gid].get("connection_id"):
|
|
61
|
+
self._mine[gid]["channel_id"] = st.get("channel_id")
|
|
62
|
+
|
|
63
|
+
def _on_server(self, data: Dict[str, Any]) -> None:
|
|
64
|
+
fut = self._grants.get(data.get("channel_id"))
|
|
65
|
+
if fut is not None and not fut.done():
|
|
66
|
+
fut.set_result(data)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _own_state(self, guild_id: str) -> Optional[Dict[str, Any]]:
|
|
70
|
+
if self._bot.user is None:
|
|
71
|
+
return None
|
|
72
|
+
for st in self._states.get(guild_id, {}).values():
|
|
73
|
+
if st.get("user_id") == self._bot.user.id:
|
|
74
|
+
return st
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_states(self, guild_id: str) -> List[Dict[str, Any]]:
|
|
79
|
+
return list(self._states.get(guild_id, {}).values())
|
|
80
|
+
|
|
81
|
+
async def join(
|
|
82
|
+
self,
|
|
83
|
+
guild_id: str,
|
|
84
|
+
channel_id: str,
|
|
85
|
+
*,
|
|
86
|
+
self_mute: bool = False,
|
|
87
|
+
self_deaf: bool = False,
|
|
88
|
+
self_video: bool = False,
|
|
89
|
+
timeout: float = 15.0,
|
|
90
|
+
state_timeout: float = 60.0,
|
|
91
|
+
media: bool = True,
|
|
92
|
+
) -> Dict[str, Any]:
|
|
93
|
+
fut = asyncio.get_running_loop().create_future()
|
|
94
|
+
self._grants[channel_id] = fut
|
|
95
|
+
try:
|
|
96
|
+
await self._bot._send({
|
|
97
|
+
"op": 4,
|
|
98
|
+
"d": {
|
|
99
|
+
"guild_id": guild_id,
|
|
100
|
+
"channel_id": channel_id,
|
|
101
|
+
"self_mute": self_mute,
|
|
102
|
+
"self_deaf": self_deaf,
|
|
103
|
+
"self_video": self_video,
|
|
104
|
+
"self_stream": False,
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
grant = await asyncio.wait_for(fut, timeout)
|
|
108
|
+
except asyncio.TimeoutError:
|
|
109
|
+
raise MicupAPIError(0, "VOICE_TIMEOUT", f"{channel_id} için ses bilgisi gelmedi")
|
|
110
|
+
finally:
|
|
111
|
+
self._grants.pop(channel_id, None)
|
|
112
|
+
|
|
113
|
+
if media:
|
|
114
|
+
await self._connect_media(guild_id, grant)
|
|
115
|
+
|
|
116
|
+
connection_id = grant.get("connection_id")
|
|
117
|
+
waited = 0.0
|
|
118
|
+
while connection_id not in self._states.get(guild_id, {}):
|
|
119
|
+
if waited >= state_timeout:
|
|
120
|
+
print(f"[micup] UYARI: {state_timeout:.0f} sn içinde kendi ses durumu gelmedi, grant ile devam ediliyor")
|
|
121
|
+
break
|
|
122
|
+
await asyncio.sleep(0.5)
|
|
123
|
+
waited += 0.5
|
|
124
|
+
own = self._own_state(guild_id) or {}
|
|
125
|
+
self._mine[guild_id] = {
|
|
126
|
+
"channel_id": channel_id,
|
|
127
|
+
"connection_id": connection_id,
|
|
128
|
+
"self_mute": self_mute,
|
|
129
|
+
"self_deaf": self_deaf,
|
|
130
|
+
"self_video": self_video,
|
|
131
|
+
"self_stream": False,
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
"guild_id": grant.get("guild_id", guild_id),
|
|
135
|
+
"channel_id": channel_id,
|
|
136
|
+
"connection_id": connection_id,
|
|
137
|
+
"session_id": own.get("session_id"),
|
|
138
|
+
"endpoint": grant.get("endpoint"),
|
|
139
|
+
"token": grant.get("token"),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
def _own_link(self, guild_id: str) -> Optional[Dict[str, Any]]:
|
|
143
|
+
if guild_id in self._mine:
|
|
144
|
+
return self._mine[guild_id]
|
|
145
|
+
st = self._own_state(guild_id)
|
|
146
|
+
if st and st.get("connection_id"):
|
|
147
|
+
return {
|
|
148
|
+
"channel_id": st.get("channel_id"),
|
|
149
|
+
"connection_id": st["connection_id"],
|
|
150
|
+
"self_mute": st.get("self_mute", False),
|
|
151
|
+
"self_deaf": st.get("self_deaf", False),
|
|
152
|
+
"self_video": st.get("self_video", False),
|
|
153
|
+
"self_stream": st.get("self_stream", False),
|
|
154
|
+
}
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
async def leave(self, guild_id: str) -> None:
|
|
158
|
+
link = self._own_link(guild_id)
|
|
159
|
+
if not link or not link.get("connection_id"):
|
|
160
|
+
raise MicupAPIError(0, "VOICE_NOT_CONNECTED", "Bu sunucuda bağlı bir ses bağlantın yok")
|
|
161
|
+
await self._bot._send({
|
|
162
|
+
"op": 4,
|
|
163
|
+
"d": {"guild_id": guild_id, "channel_id": None, "connection_id": link["connection_id"]},
|
|
164
|
+
})
|
|
165
|
+
self._mine.pop(guild_id, None)
|
|
166
|
+
room = self._rooms.pop(guild_id, None)
|
|
167
|
+
if room is not None:
|
|
168
|
+
await room.disconnect()
|
|
169
|
+
|
|
170
|
+
async def update(self, guild_id: str, *, self_mute: Optional[bool] = None, self_deaf: Optional[bool] = None,
|
|
171
|
+
self_video: Optional[bool] = None) -> None:
|
|
172
|
+
link = self._own_link(guild_id)
|
|
173
|
+
if not link or not link.get("connection_id"):
|
|
174
|
+
raise MicupAPIError(0, "VOICE_NOT_CONNECTED", "Bu sunucuda bağlı bir ses bağlantın yok")
|
|
175
|
+
new = {
|
|
176
|
+
"self_mute": link.get("self_mute", False) if self_mute is None else self_mute,
|
|
177
|
+
"self_deaf": link.get("self_deaf", False) if self_deaf is None else self_deaf,
|
|
178
|
+
"self_video": link.get("self_video", False) if self_video is None else self_video,
|
|
179
|
+
}
|
|
180
|
+
await self._bot._send({
|
|
181
|
+
"op": 4,
|
|
182
|
+
"d": {
|
|
183
|
+
"guild_id": guild_id,
|
|
184
|
+
"channel_id": link["channel_id"],
|
|
185
|
+
"connection_id": link["connection_id"],
|
|
186
|
+
**new,
|
|
187
|
+
"self_stream": link.get("self_stream", False),
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
link.update(new)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: micup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Micup platformu için Python kütüphanesi.
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: aiohttp>=3.9
|
|
8
|
+
Requires-Dist: livekit
|
|
9
|
+
|
|
10
|
+
# micup
|
|
11
|
+
|
|
12
|
+
Micup platformu için Python kütüphanesi. Bot yazmak için mesaj, komut, olay, yetki ve ses yönetimini tek yerden sağlar.
|
|
13
|
+
|
|
14
|
+
## Kurulum
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
pip install micup
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Hızlı başlangıç
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from micup import Bot
|
|
24
|
+
|
|
25
|
+
bot = Bot() # token MICUP_TOKEN ortam değişkeninden okunur
|
|
26
|
+
|
|
27
|
+
@bot.command("!ping")
|
|
28
|
+
async def ping(msg):
|
|
29
|
+
return "pong"
|
|
30
|
+
|
|
31
|
+
bot.run()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Token'ı kodun içine yazma. PowerShell'de:
|
|
35
|
+
|
|
36
|
+
```powershell
|
|
37
|
+
$env:MICUP_TOKEN = "uygulama_id.gizli_anahtar"
|
|
38
|
+
python bot.py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Özellikler
|
|
42
|
+
|
|
43
|
+
- Gateway bağlantısı, kopunca otomatik yeniden bağlanma (resume destekli)
|
|
44
|
+
- Komutlar ve olaylar: `@bot.command(...)`, `@bot.on(...)`
|
|
45
|
+
- Mesaj işlemleri: gönder, düzenle, sil, toplu sil, listele
|
|
46
|
+
- Ham REST erişimi: `bot.rest(...)`
|
|
47
|
+
- Ses: kanala katılma, ayrılma, durum okuma (LiveKit medya bağlantısı dahil)
|
|
48
|
+
- Durum: `bot.set_presence(...)`
|
|
49
|
+
- Yetki bitleri ve snowflake yardımcıları
|
|
50
|
+
|
|
51
|
+
Ayrıntılı kullanım için dokümantasyona bak.
|
|
52
|
+
|
|
53
|
+
## Lisans
|
|
54
|
+
|
|
55
|
+
Henüz belirlenmedi.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
micup/__init__.py
|
|
4
|
+
micup/client.py
|
|
5
|
+
micup/constants.py
|
|
6
|
+
micup/errors.py
|
|
7
|
+
micup/models.py
|
|
8
|
+
micup/permissions.py
|
|
9
|
+
micup/utils.py
|
|
10
|
+
micup/voice.py
|
|
11
|
+
micup.egg-info/PKG-INFO
|
|
12
|
+
micup.egg-info/SOURCES.txt
|
|
13
|
+
micup.egg-info/dependency_links.txt
|
|
14
|
+
micup.egg-info/requires.txt
|
|
15
|
+
micup.egg-info/top_level.txt
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "micup"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Micup platformu için Python kütüphanesi."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"aiohttp>=3.9",
|
|
13
|
+
"livekit",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
packages = ["micup"]
|
micup-0.0.1/PKG-INFO
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: micup
|
|
3
|
-
Version: 0.0.1
|
|
4
|
-
Summary: Python library for Micup bots (in development)
|
|
5
|
-
Author-email: Renbow <egekoc28@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Classifier: Development Status :: 1 - Planning
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Requires-Python: >=3.9
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: aiohttp>=3.9
|
|
13
|
-
|
|
14
|
-
# micup
|
|
15
|
-
|
|
16
|
-
Micup platformu için Python bot kütüphanesi. Şu an geliştirme aşamasında, henüz kullanılabilir bir sürüm yayınlanmadı.
|
|
17
|
-
|
|
18
|
-
Planlanan kullanım:
|
|
19
|
-
|
|
20
|
-
from micup import Bot
|
|
21
|
-
|
|
22
|
-
bot = Bot()
|
|
23
|
-
|
|
24
|
-
@bot.command("!ping")
|
|
25
|
-
async def ping(msg):
|
|
26
|
-
return "pong"
|
|
27
|
-
|
|
28
|
-
bot.run()
|
micup-0.0.1/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# micup
|
|
2
|
-
|
|
3
|
-
Micup platformu için Python bot kütüphanesi. Şu an geliştirme aşamasında, henüz kullanılabilir bir sürüm yayınlanmadı.
|
|
4
|
-
|
|
5
|
-
Planlanan kullanım:
|
|
6
|
-
|
|
7
|
-
from micup import Bot
|
|
8
|
-
|
|
9
|
-
bot = Bot()
|
|
10
|
-
|
|
11
|
-
@bot.command("!ping")
|
|
12
|
-
async def ping(msg):
|
|
13
|
-
return "pong"
|
|
14
|
-
|
|
15
|
-
bot.run()
|
micup-0.0.1/micup/init.py
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: micup
|
|
3
|
-
Version: 0.0.1
|
|
4
|
-
Summary: Python library for Micup bots (in development)
|
|
5
|
-
Author-email: Renbow <egekoc28@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Classifier: Development Status :: 1 - Planning
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Requires-Python: >=3.9
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: aiohttp>=3.9
|
|
13
|
-
|
|
14
|
-
# micup
|
|
15
|
-
|
|
16
|
-
Micup platformu için Python bot kütüphanesi. Şu an geliştirme aşamasında, henüz kullanılabilir bir sürüm yayınlanmadı.
|
|
17
|
-
|
|
18
|
-
Planlanan kullanım:
|
|
19
|
-
|
|
20
|
-
from micup import Bot
|
|
21
|
-
|
|
22
|
-
bot = Bot()
|
|
23
|
-
|
|
24
|
-
@bot.command("!ping")
|
|
25
|
-
async def ping(msg):
|
|
26
|
-
return "pong"
|
|
27
|
-
|
|
28
|
-
bot.run()
|
micup-0.0.1/pyproject.toml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=68", "wheel"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "micup"
|
|
7
|
-
version = "0.0.1"
|
|
8
|
-
description = "Python library for Micup bots (in development)"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.9"
|
|
11
|
-
license = {text = "MIT"}
|
|
12
|
-
authors = [{name = "Renbow", email = "egekoc28@gmail.com"}]
|
|
13
|
-
dependencies = ["aiohttp>=3.9"]
|
|
14
|
-
classifiers = [
|
|
15
|
-
"Development Status :: 1 - Planning",
|
|
16
|
-
"Programming Language :: Python :: 3",
|
|
17
|
-
"License :: OSI Approved :: MIT License",
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
[tool.setuptools]
|
|
21
|
-
packages = ["micup"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|