aiodot 1.0.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.
aiodot-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 karbaladev.ir
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.
aiodot-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiodot
3
+ Version: 1.0.0
4
+ Summary: Async Python client for MyDot.one social platform
5
+ Home-page: https://github.com/karbaladev/aiodot
6
+ Author: karbaladev.ir
7
+ Author-email: "karbaladev.ir" <saleh.mohammadi.az@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://karbaladev.ir
10
+ Project-URL: Repository, https://github.com/karbaladevir/aiodot
11
+ Project-URL: Issues, https://github.com/karbaladevir/aiodot/issues
12
+ Keywords: mydot,aiodot,mydot-bot,social-media,async,bot
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: aiohttp>=3.8.0
20
+ Requires-Dist: requests>=2.28.0
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+ Dynamic: requires-python
25
+
26
+ <p align="center">
27
+ <img src="https://abrehamrahi.ir/o/public/rNf0ej8l/" alt="aiodot logo" width="200">
28
+ </p>
29
+
30
+ <h1 align="center">aiodot 🚀</h1>
31
+ <p align="center">
32
+ <b>Async Python client for MyDot.one social platform</b><br>
33
+ Build bots, automation, and tools with ease.
34
+ </p>
35
+
36
+ <p align="center">
37
+ <a href="https://pypi.org/project/aiodot/">
38
+ <img src="https://badge.fury.io/py/aiodot.svg" alt="PyPI version">
39
+ </a>
40
+ <a href="https://pypi.org/project/aiodot/">
41
+ <img src="https://img.shields.io/pypi/pyversions/aiodot.svg" alt="Python">
42
+ </a>
43
+ <a href="https://github.com/karbaladev/aiodot/blob/main/LICENSE">
44
+ <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
45
+ </a>
46
+ </p>
47
+
48
+ ---
49
+
50
+ **`aiodot`** is an asynchronous Python library for [MyDot.one](https://mydot.one). Built with `aiohttp` — fast, async-first, session-based like aiogram.
51
+
52
+ ---
53
+
54
+ ## ✨ Features
55
+
56
+ - ⚡ **Async-first** — Built on aiohttp
57
+ - 💾 **Session persistence** — Login once, auto-loads next time
58
+ - 🔄 **Auto token refresh** — Handles 401 gracefully
59
+ - 📦 **50+ endpoints** — Full API coverage
60
+ - 🎯 **Type hints** — Full type annotations
61
+
62
+ ---
63
+
64
+ ## 📦 Installation
65
+
66
+ ```bash
67
+ pip install aiodot
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 💡 Quick Start
73
+
74
+ ```python
75
+ import asyncio
76
+ from aiodot import MyDotClient
77
+
78
+ TOKEN = "your-token" # F12 → Cookies → __Secure-access_token
79
+
80
+ async def main():
81
+ async with MyDotClient(token=TOKEN, session_file="user.session.json") as client:
82
+ me = await client.get_me()
83
+ print(f"@{me.username}")
84
+
85
+ dot = await client.create_dot("Hello aiodot! 🚀")
86
+ print(dot.url)
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 🤖 Echo Bot
94
+
95
+ ```python
96
+ import asyncio
97
+ from aiodot import MyDotClient
98
+
99
+ TOKEN = "your-token"
100
+
101
+ async def main():
102
+ processed = set()
103
+ async with MyDotClient(token=TOKEN, session_file="bot.session.json") as c:
104
+ print(f"🤖 @{(await c.get_me()).username} started!")
105
+ while True:
106
+ for n in (await c.get_notifications(10)).get("results", []):
107
+ if n.get("id") in processed: continue
108
+ processed.add(n.get("id"))
109
+ if n.get("type") == "mention":
110
+ d = n.get("dot", {})
111
+ if d.get("id"):
112
+ await c.reply(d["id"], "Hello! 🤖")
113
+ await asyncio.sleep(15)
114
+
115
+ asyncio.run(main())
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 📖 API Reference
121
+
122
+ | Category | Methods |
123
+ |----------|---------|
124
+ | **Profile** | `get_me()`, `update_profile()`, `set_visibility()` |
125
+ | **Dots** | `create_dot()`, `get_dot()`, `reply()`, `repost()`, `quote()`, `like()`, `bookmark()`, `edit_dot()`, `delete_dot()` |
126
+ | **Social** | `follow()`, `unfollow()`, `block()`, `unblock()`, `mute()`, `unmute()` |
127
+ | **Users** | `search_users()`, `get_user_followers()`, `get_user_following()`, `get_user_dots()` |
128
+ | **Feed** | `home_feed()`, `following_feed()`, `follow_suggestions()` |
129
+ | **Notifications** | `get_notifications()`, `mark_all_read()`, `get_notification_preferences()` |
130
+ | **Trending** | `get_trending_hashtags()`, `get_trending_media()` |
131
+
132
+ ---
133
+
134
+ ## ⚠️ Disclaimer
135
+
136
+ Unofficial library. Use responsibly per MyDot.one Terms of Service.
137
+
138
+ ---
139
+
140
+ ## 📡 Community
141
+
142
+ - 📢 **Telegram:** [@aiodotlib](https://t.me/aiodotlib)
143
+ - 💬 **Bale:** [ble.ir/aiodot](https://ble.ir/aiodot)
144
+ - 🌐 **Website:** [karbaladev.ir](https://karbaladev.ir)
145
+
146
+ ---
147
+
148
+ ## 📄 License
149
+
150
+ MIT © [karbaladev.ir](https://karbaladev.ir)
aiodot-1.0.0/README.md ADDED
@@ -0,0 +1,125 @@
1
+ <p align="center">
2
+ <img src="https://abrehamrahi.ir/o/public/rNf0ej8l/" alt="aiodot logo" width="200">
3
+ </p>
4
+
5
+ <h1 align="center">aiodot 🚀</h1>
6
+ <p align="center">
7
+ <b>Async Python client for MyDot.one social platform</b><br>
8
+ Build bots, automation, and tools with ease.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://pypi.org/project/aiodot/">
13
+ <img src="https://badge.fury.io/py/aiodot.svg" alt="PyPI version">
14
+ </a>
15
+ <a href="https://pypi.org/project/aiodot/">
16
+ <img src="https://img.shields.io/pypi/pyversions/aiodot.svg" alt="Python">
17
+ </a>
18
+ <a href="https://github.com/karbaladev/aiodot/blob/main/LICENSE">
19
+ <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
20
+ </a>
21
+ </p>
22
+
23
+ ---
24
+
25
+ **`aiodot`** is an asynchronous Python library for [MyDot.one](https://mydot.one). Built with `aiohttp` — fast, async-first, session-based like aiogram.
26
+
27
+ ---
28
+
29
+ ## ✨ Features
30
+
31
+ - ⚡ **Async-first** — Built on aiohttp
32
+ - 💾 **Session persistence** — Login once, auto-loads next time
33
+ - 🔄 **Auto token refresh** — Handles 401 gracefully
34
+ - 📦 **50+ endpoints** — Full API coverage
35
+ - 🎯 **Type hints** — Full type annotations
36
+
37
+ ---
38
+
39
+ ## 📦 Installation
40
+
41
+ ```bash
42
+ pip install aiodot
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 💡 Quick Start
48
+
49
+ ```python
50
+ import asyncio
51
+ from aiodot import MyDotClient
52
+
53
+ TOKEN = "your-token" # F12 → Cookies → __Secure-access_token
54
+
55
+ async def main():
56
+ async with MyDotClient(token=TOKEN, session_file="user.session.json") as client:
57
+ me = await client.get_me()
58
+ print(f"@{me.username}")
59
+
60
+ dot = await client.create_dot("Hello aiodot! 🚀")
61
+ print(dot.url)
62
+
63
+ asyncio.run(main())
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 🤖 Echo Bot
69
+
70
+ ```python
71
+ import asyncio
72
+ from aiodot import MyDotClient
73
+
74
+ TOKEN = "your-token"
75
+
76
+ async def main():
77
+ processed = set()
78
+ async with MyDotClient(token=TOKEN, session_file="bot.session.json") as c:
79
+ print(f"🤖 @{(await c.get_me()).username} started!")
80
+ while True:
81
+ for n in (await c.get_notifications(10)).get("results", []):
82
+ if n.get("id") in processed: continue
83
+ processed.add(n.get("id"))
84
+ if n.get("type") == "mention":
85
+ d = n.get("dot", {})
86
+ if d.get("id"):
87
+ await c.reply(d["id"], "Hello! 🤖")
88
+ await asyncio.sleep(15)
89
+
90
+ asyncio.run(main())
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 📖 API Reference
96
+
97
+ | Category | Methods |
98
+ |----------|---------|
99
+ | **Profile** | `get_me()`, `update_profile()`, `set_visibility()` |
100
+ | **Dots** | `create_dot()`, `get_dot()`, `reply()`, `repost()`, `quote()`, `like()`, `bookmark()`, `edit_dot()`, `delete_dot()` |
101
+ | **Social** | `follow()`, `unfollow()`, `block()`, `unblock()`, `mute()`, `unmute()` |
102
+ | **Users** | `search_users()`, `get_user_followers()`, `get_user_following()`, `get_user_dots()` |
103
+ | **Feed** | `home_feed()`, `following_feed()`, `follow_suggestions()` |
104
+ | **Notifications** | `get_notifications()`, `mark_all_read()`, `get_notification_preferences()` |
105
+ | **Trending** | `get_trending_hashtags()`, `get_trending_media()` |
106
+
107
+ ---
108
+
109
+ ## ⚠️ Disclaimer
110
+
111
+ Unofficial library. Use responsibly per MyDot.one Terms of Service.
112
+
113
+ ---
114
+
115
+ ## 📡 Community
116
+
117
+ - 📢 **Telegram:** [@aiodotlib](https://t.me/aiodotlib)
118
+ - 💬 **Bale:** [ble.ir/aiodot](https://ble.ir/aiodot)
119
+ - 🌐 **Website:** [karbaladev.ir](https://karbaladev.ir)
120
+
121
+ ---
122
+
123
+ ## 📄 License
124
+
125
+ MIT © [karbaladev.ir](https://karbaladev.ir)
@@ -0,0 +1,37 @@
1
+ # ==========================================
2
+ # aiodot/__init__.py
3
+ # ==========================================
4
+
5
+ """
6
+ aiodot - Python client for MyDot.one social platform.
7
+ Build bots, automation, and tools with ease.
8
+
9
+ Author: karbaladev.ir
10
+ License: MIT
11
+ Version: 1.0.0
12
+
13
+ Usage:
14
+ from aiodot import MyDotClient
15
+
16
+ client = MyDotClient(token="your_token")
17
+ me = client.me
18
+ dot = client.create_dot("Hello from aiodot!")
19
+ """
20
+
21
+ from .client import MyDotClient
22
+ from .auth import MyDotAuth, Session
23
+ from .models import Dot, User, ThreadView, Notification, PaginatedResponse
24
+
25
+ __version__ = "1.0.0"
26
+ __author__ = "karbaladev.ir"
27
+ __license__ = "MIT"
28
+ __all__ = [
29
+ "MyDotClient",
30
+ "MyDotAuth",
31
+ "Session",
32
+ "Dot",
33
+ "User",
34
+ "ThreadView",
35
+ "Notification",
36
+ "PaginatedResponse",
37
+ ]
@@ -0,0 +1,153 @@
1
+ # ==========================================
2
+ # aiodot/auth.py
3
+ # ==========================================
4
+
5
+ """Authentication module for MyDot.one. Token-based with session file."""
6
+
7
+ import json
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Optional, Dict, Any
11
+ from dataclasses import dataclass, asdict, field
12
+
13
+ import aiohttp
14
+ import requests
15
+
16
+
17
+ @dataclass
18
+ class Session:
19
+ """Session data saved to disk."""
20
+ token: str = ""
21
+ user_id: str = ""
22
+ username: str = ""
23
+ display_name: str = ""
24
+ phone: str = ""
25
+ created_at: float = field(default_factory=time.time)
26
+
27
+ def to_dict(self) -> Dict[str, Any]:
28
+ return asdict(self)
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: Dict[str, Any]) -> "Session":
32
+ valid = {k: v for k, v in data.items() if k in cls.__dataclass_fields__}
33
+ return cls(**valid)
34
+
35
+
36
+ class MyDotAuth:
37
+ """Auth handler for MyDot.one."""
38
+
39
+ BASE_URL = "https://api.mydot.one/mydot/api/v1"
40
+
41
+ def __init__(self, session_file: Optional[str] = None):
42
+ if session_file is None:
43
+ self.session_file = Path.home() / ".mydot" / "session.json"
44
+ else:
45
+ self.session_file = Path(session_file)
46
+
47
+ self.session_file.parent.mkdir(parents=True, exist_ok=True)
48
+ self.session_data = Session()
49
+ self._loaded = False
50
+
51
+ if self.session_file.exists():
52
+ self._load()
53
+
54
+ def _load(self) -> bool:
55
+ try:
56
+ with open(self.session_file, "r", encoding="utf-8") as f:
57
+ data = json.load(f)
58
+ self.session_data = Session.from_dict(data)
59
+ self._loaded = bool(self.session_data.token)
60
+ return True
61
+ except Exception:
62
+ return False
63
+
64
+ def save(self) -> None:
65
+ with open(self.session_file, "w", encoding="utf-8") as f:
66
+ json.dump(self.session_data.to_dict(), f, indent=2, ensure_ascii=False)
67
+
68
+ def clear(self) -> None:
69
+ self.session_data = Session()
70
+ if self.session_file.exists():
71
+ self.session_file.unlink()
72
+
73
+ # ============ Sync Login (called once during init) ============
74
+
75
+ def login_with_token_sync(self, token: str) -> bool:
76
+ """Sync token validation. Called once during MyDotClient init."""
77
+ self.session_data.token = token
78
+ resp = requests.get(
79
+ f"{self.BASE_URL}/auth/profile/",
80
+ cookies={"__Secure-access_token": token},
81
+ headers=self._get_headers_sync(),
82
+ )
83
+ if resp.ok:
84
+ p = resp.json()
85
+ self.session_data.user_id = p.get("user_id", "")
86
+ self.session_data.username = p.get("username", "")
87
+ self.session_data.display_name = p.get("display_name", "")
88
+ self._loaded = True
89
+ self.save()
90
+ print(f"✅ Token saved! @{self.username}")
91
+ return True
92
+ print("❌ Invalid token")
93
+ return False
94
+
95
+ # ============ Async Token Refresh ============
96
+
97
+ async def refresh(self) -> bool:
98
+ """Async token refresh using aiohttp."""
99
+ headers = self._get_headers_async()
100
+ headers["Cookie"] = f"__Secure-access_token={self.token}"
101
+ async with aiohttp.ClientSession() as s:
102
+ async with s.post(f"{self.BASE_URL}/auth/token/refresh/", headers=headers, json={}) as resp:
103
+ if resp.status == 200:
104
+ for key, cookie in resp.cookies.items():
105
+ if key == "__Secure-access_token":
106
+ self.session_data.token = cookie.value
107
+ self.save()
108
+ print("🔄 Token refreshed")
109
+ return True
110
+ return False
111
+
112
+ # ============ Headers ============
113
+
114
+ def _get_headers_sync(self) -> Dict[str, str]:
115
+ return {
116
+ "Accept": "application/json, text/plain, */*",
117
+ "Origin": "https://mydot.one",
118
+ "Referer": "https://mydot.one/",
119
+ "User-Agent": "Mozilla/5.0",
120
+ }
121
+
122
+ def _get_headers_async(self) -> Dict[str, str]:
123
+ return {
124
+ "Accept": "application/json, text/plain, */*",
125
+ "Origin": "https://mydot.one",
126
+ "Referer": "https://mydot.one/",
127
+ "User-Agent": "Mozilla/5.0",
128
+ }
129
+
130
+ # ============ Properties ============
131
+
132
+ @property
133
+ def token(self) -> Optional[str]:
134
+ return self.session_data.token
135
+
136
+ @property
137
+ def user_id(self) -> Optional[str]:
138
+ return self.session_data.user_id
139
+
140
+ @property
141
+ def username(self) -> Optional[str]:
142
+ return self.session_data.username
143
+
144
+ @property
145
+ def display_name(self) -> Optional[str]:
146
+ return self.session_data.display_name
147
+
148
+ @property
149
+ def is_logged_in(self) -> bool:
150
+ return self._loaded and bool(self.token)
151
+
152
+ def __repr__(self) -> str:
153
+ return f"MyDotAuth(@{self.username})" if self.username else "MyDotAuth(not logged in)"
@@ -0,0 +1,307 @@
1
+ # ==========================================
2
+ # aiodot/client.py
3
+ # ==========================================
4
+
5
+ """Main MyDot.one API Client. Async version built on aiohttp."""
6
+
7
+ from typing import Optional, Dict, List, Any
8
+ import aiohttp
9
+ from .models import Dot, User, ThreadView, Notification, PaginatedResponse
10
+ from .auth import MyDotAuth
11
+
12
+
13
+ class MyDotClient:
14
+ """Async API client for MyDot.one."""
15
+
16
+ BASE_URL = "https://api.mydot.one/mydot/api/v1"
17
+
18
+ def __init__(self, token: Optional[str] = None, session_file: Optional[str] = None, auth: Optional[MyDotAuth] = None):
19
+ if auth:
20
+ self.auth = auth
21
+ elif token:
22
+ self.auth = MyDotAuth(session_file=session_file)
23
+ self.auth.login_with_token_sync(token)
24
+ else:
25
+ self.auth = MyDotAuth(session_file=session_file)
26
+ self._session: Optional[aiohttp.ClientSession] = None
27
+
28
+ async def __aenter__(self):
29
+ self._session = aiohttp.ClientSession()
30
+ return self
31
+
32
+ async def __aexit__(self, *args):
33
+ if self._session:
34
+ await self._session.close()
35
+ self._session = None
36
+
37
+ @property
38
+ def http(self) -> aiohttp.ClientSession:
39
+ if self._session is None:
40
+ raise RuntimeError("Use 'async with MyDotClient(...) as client:' block.")
41
+ return self._session
42
+
43
+ def _headers(self) -> Dict[str, str]:
44
+ h = {
45
+ "Accept": "application/json, text/plain, */*",
46
+ "Origin": "https://mydot.one",
47
+ "Referer": "https://mydot.one/",
48
+ "User-Agent": "Mozilla/5.0",
49
+ }
50
+ if self.auth.token:
51
+ h["Cookie"] = f"__Secure-access_token={self.auth.token}"
52
+ return h
53
+
54
+ async def _get(self, path: str, params: Dict = None) -> Any:
55
+ url = f"{self.BASE_URL}{path}"
56
+ async with self.http.get(url, params=params, headers=self._headers()) as resp:
57
+ if resp.status == 401 and await self.auth.refresh():
58
+ async with self.http.get(url, params=params, headers=self._headers()) as r:
59
+ r.raise_for_status()
60
+ ct = r.content_type
61
+ return await r.json() if "json" in ct else await r.text()
62
+ resp.raise_for_status()
63
+ ct = resp.content_type
64
+ return await resp.json() if "json" in ct else await resp.text()
65
+
66
+ async def _post(self, path: str, json: Dict = None) -> Any:
67
+ url = f"{self.BASE_URL}{path}"
68
+ async with self.http.post(url, json=json, headers=self._headers()) as resp:
69
+ if resp.status == 401 and await self.auth.refresh():
70
+ async with self.http.post(url, json=json, headers=self._headers()) as r:
71
+ r.raise_for_status()
72
+ ct = r.content_type
73
+ return await r.json() if "json" in ct else await r.text()
74
+ resp.raise_for_status()
75
+ ct = resp.content_type
76
+ return await resp.json() if "json" in ct else await resp.text()
77
+
78
+ async def _patch(self, path: str, json: Dict = None) -> Any:
79
+ url = f"{self.BASE_URL}{path}"
80
+ async with self.http.patch(url, json=json, headers=self._headers()) as resp:
81
+ if resp.status == 401 and await self.auth.refresh():
82
+ async with self.http.patch(url, json=json, headers=self._headers()) as r:
83
+ r.raise_for_status()
84
+ ct = r.content_type
85
+ return await r.json() if "json" in ct else await r.text()
86
+ resp.raise_for_status()
87
+ ct = resp.content_type
88
+ return await resp.json() if "json" in ct else await resp.text()
89
+
90
+ async def _delete(self, path: str) -> Any:
91
+ url = f"{self.BASE_URL}{path}"
92
+ async with self.http.delete(url, headers=self._headers()) as resp:
93
+ if resp.status == 401 and await self.auth.refresh():
94
+ async with self.http.delete(url, headers=self._headers()) as r:
95
+ r.raise_for_status()
96
+ ct = r.content_type
97
+ return await r.json() if "json" in ct else await r.text()
98
+ resp.raise_for_status()
99
+ ct = resp.content_type
100
+ return await resp.json() if "json" in ct else await resp.text()
101
+
102
+ # ============ Profile (4) ============
103
+ async def get_me(self) -> User:
104
+ data = await self._get("/auth/profile/")
105
+ return User.from_dict(data)
106
+
107
+ async def update_profile(self, **kwargs) -> User:
108
+ data = await self._patch("/auth/profile/", json=kwargs)
109
+ return User.from_dict(data)
110
+
111
+ async def get_profile_visibility(self) -> Dict:
112
+ return await self._get("/auth/profile/visibility/")
113
+
114
+ async def set_profile_visibility(self, visibility: str) -> Dict:
115
+ return await self._patch("/auth/profile/visibility/", json={"profile_visibility": visibility})
116
+
117
+ # ============ User Actions (6) ============
118
+ async def follow(self, user_id: str) -> bool:
119
+ await self._post("/auth/follow/", json={"target_id": user_id})
120
+ return True
121
+
122
+ async def unfollow(self, user_id: str) -> bool:
123
+ await self._post("/auth/unfollow/", json={"target_id": user_id})
124
+ return True
125
+
126
+ async def block(self, user_id: str) -> bool:
127
+ await self._post("/auth/block/", json={"target_id": user_id})
128
+ return True
129
+
130
+ async def unblock(self, user_id: str) -> bool:
131
+ await self._post("/auth/unblock/", json={"target_id": user_id})
132
+ return True
133
+
134
+ async def mute(self, user_id: str) -> bool:
135
+ await self._post("/auth/mute/", json={"target_id": user_id})
136
+ return True
137
+
138
+ async def unmute(self, user_id: str) -> bool:
139
+ await self._post("/auth/unmute/", json={"target_id": user_id})
140
+ return True
141
+
142
+ # ============ User Info (3) ============
143
+ async def get_user_followers(self, username: str, page_size: int = 20) -> Dict:
144
+ return await self._get(f"/auth/{username}/followers/", params={"page_size": page_size})
145
+
146
+ async def get_user_following(self, username: str, page_size: int = 20) -> Dict:
147
+ return await self._get(f"/auth/{username}/following/", params={"page_size": page_size})
148
+
149
+ async def search_users(self, query: str) -> Any:
150
+ return await self._get("/auth/profile/search", params={"q": query})
151
+
152
+ # ============ Dots - Read (9) ============
153
+ async def get_dot(self, dot_id: str) -> Dot:
154
+ data = await self._get(f"/dots/{dot_id}/")
155
+ return Dot.from_dict(data)
156
+
157
+ async def get_thread_view(self, dot_id: str) -> ThreadView:
158
+ data = await self._get(f"/dots/{dot_id}/thread-view/")
159
+ return ThreadView.from_dict(data)
160
+
161
+ async def get_replies(self, dot_id: str, page: int = 1, limit: int = 20) -> Dict:
162
+ return await self._get(f"/dots/{dot_id}/replies/", params={"page": page, "limit": limit})
163
+
164
+ async def get_reposts(self, dot_id: str) -> Dict:
165
+ return await self._get(f"/dots/{dot_id}/reposts/")
166
+
167
+ async def get_quotes(self, dot_id: str) -> Dict:
168
+ return await self._get(f"/dots/{dot_id}/quotes/")
169
+
170
+ async def get_dot_likes(self, dot_id: str) -> Any:
171
+ return await self._get(f"/dots/{dot_id}/likes/")
172
+
173
+ async def get_user_dots(self, user_id: str, dot_type: str = "dot,quote,repost", limit: int = 20) -> Dict:
174
+ return await self._get(f"/dots/user/{user_id}/", params={"dot_type": dot_type, "limit": limit})
175
+
176
+ async def get_user_replies(self, user_id: str) -> Dict:
177
+ return await self._get(f"/dots/user/{user_id}/", params={"dot_type": "reply"})
178
+
179
+ async def get_user_likes(self, user_id: str) -> Dict:
180
+ return await self._get(f"/dots/user/{user_id}/activity/", params={"action_type": "like"})
181
+
182
+ # ============ Dots - Create (4) ============
183
+ async def create_dot(self, content: str, dot_type: str = "dot", reply_to: str = None,
184
+ repost_of: str = None, quote_of: str = None,
185
+ media_ids: List[str] = None, reply_permission: str = "everyone") -> Dot:
186
+ body = {k: v for k, v in {
187
+ "dot_type": dot_type, "content": content,
188
+ "reply_to": reply_to, "repost_of": repost_of, "quote_of": quote_of,
189
+ "media_ids": media_ids or [], "reply_permission": reply_permission
190
+ }.items() if v is not None}
191
+ data = await self._post("/dots/", json=body)
192
+ return Dot.from_dict(data)
193
+
194
+ async def reply(self, dot_id: str, content: str) -> Dot:
195
+ return await self.create_dot(content=content, dot_type="reply", reply_to=dot_id)
196
+
197
+ async def repost(self, dot_id: str, media_ids: List[str] = None) -> Dot:
198
+ return await self.create_dot(content="", dot_type="repost", repost_of=dot_id, media_ids=media_ids)
199
+
200
+ async def quote(self, dot_id: str, content: str) -> Dot:
201
+ return await self.create_dot(content=content, dot_type="quote", quote_of=dot_id)
202
+
203
+ # ============ Dots - Actions (10) ============
204
+ async def like(self, dot_id: str) -> bool:
205
+ await self._post(f"/dots/{dot_id}/like/")
206
+ return True
207
+
208
+ async def unlike(self, dot_id: str) -> bool:
209
+ await self._delete(f"/dots/{dot_id}/like/")
210
+ return True
211
+
212
+ async def bookmark(self, dot_id: str) -> bool:
213
+ await self._post(f"/dots/{dot_id}/bookmark/")
214
+ return True
215
+
216
+ async def unbookmark(self, dot_id: str) -> bool:
217
+ await self._delete(f"/dots/{dot_id}/bookmark/")
218
+ return True
219
+
220
+ async def do_repost(self, dot_id: str) -> bool:
221
+ await self._post(f"/dots/{dot_id}/repost/")
222
+ return True
223
+
224
+ async def undo_repost(self, dot_id: str) -> bool:
225
+ await self._delete(f"/dots/{dot_id}/repost/")
226
+ return True
227
+
228
+ async def edit_dot(self, dot_id: str, content: str) -> Dot:
229
+ data = await self._patch(f"/dots/{dot_id}/", json={"content": content})
230
+ return Dot.from_dict(data)
231
+
232
+ async def delete_dot(self, dot_id: str) -> bool:
233
+ await self._delete(f"/dots/{dot_id}/")
234
+ return True
235
+
236
+ # ============ Feed (3) ============
237
+ async def home_feed(self, page_size: int = 40) -> Dict:
238
+ return await self._get("/feed/suggestions/timeline/", params={"page_size": page_size})
239
+
240
+ async def following_feed(self, **params) -> Dict:
241
+ return await self._get("/feed/following/", params=params)
242
+
243
+ async def follow_suggestions(self, cursor: str = None, page_size: int = 20) -> Dict:
244
+ p = {"page_size": page_size}
245
+ if cursor:
246
+ p["cursor"] = cursor
247
+ return await self._get("/feed/suggestions/follow/", params=p)
248
+
249
+ # ============ Notifications (4) ============
250
+ async def get_notifications(self, page_size: int = 50) -> Dict:
251
+ return await self._get("/notifications/", params={"page_size": page_size})
252
+
253
+ async def get_notification_preferences(self) -> Dict:
254
+ return await self._get("/notifications/preferences/")
255
+
256
+ async def update_notification_preferences(self, **kwargs) -> Dict:
257
+ return await self._patch("/notifications/preferences/", json=kwargs)
258
+
259
+ async def mark_all_notifications_read(self) -> bool:
260
+ await self._post("/notifications/read-all/")
261
+ return True
262
+
263
+ # ============ Bookmarks (1) ============
264
+ async def get_bookmarks(self, limit: int = 20) -> Dict:
265
+ return await self._get("/dots/bookmarks/", params={"limit": limit})
266
+
267
+ # ============ Trending (2) ============
268
+ async def get_trending_hashtags(self) -> Any:
269
+ return await self._get("/dots/hashtags/trending/")
270
+
271
+ async def get_trending_media(self, limit: int = 24) -> Dict:
272
+ return await self._get("/dots/media/trending/", params={"limit": limit})
273
+
274
+ # ============ Media Upload (1) ============
275
+ async def upload_media(self, file_path: str) -> Dict:
276
+ url = f"{self.BASE_URL}/posts/media/"
277
+ with open(file_path, "rb") as f:
278
+ form = aiohttp.FormData()
279
+ form.add_field("file", f)
280
+ async with self.http.post(url, data=form, headers=self._headers()) as resp:
281
+ resp.raise_for_status()
282
+ return await resp.json()
283
+
284
+ # ============ Other (6) ============
285
+ async def get_alerts(self) -> Any:
286
+ return await self._get("/utils/alerts/active/")
287
+
288
+ async def get_invites(self) -> Dict:
289
+ return await self._get("/auth/invites/")
290
+
291
+ async def get_wallets(self) -> Dict:
292
+ return await self._get("/auth/wallets/")
293
+
294
+ async def get_2fa_state(self) -> Dict:
295
+ return await self._get("/auth/2fa/state/")
296
+
297
+ async def get_star_settings(self) -> Dict:
298
+ return await self._get("/reward/default-stars/")
299
+
300
+ async def get_star_transactions(self) -> Dict:
301
+ return await self._get("/reward/stars-transaction/")
302
+
303
+ # ============ Convenience (1) ============
304
+ async def get_dot_url(self, short_id: str, username: str = None) -> str:
305
+ if username is None:
306
+ username = self.auth.username
307
+ return f"https://mydot.one/@{username}/status/{short_id}"
@@ -0,0 +1,185 @@
1
+ # ==========================================
2
+ # aiodot/models.py
3
+ # ==========================================
4
+
5
+ """Data models for MyDot API responses."""
6
+
7
+ from typing import Optional, Dict, List, Any
8
+ from dataclasses import dataclass, field
9
+
10
+
11
+ @dataclass
12
+ class User:
13
+ """MyDot user profile."""
14
+ user_id: str = ""
15
+ username: str = ""
16
+ display_name: str = ""
17
+ bio: str = ""
18
+ location: str = ""
19
+ website: str = ""
20
+ avatar_url: str = ""
21
+ header_url: str = ""
22
+ followers_count: int = 0
23
+ following_count: int = 0
24
+ posts_count: int = 0
25
+ is_verified: bool = False
26
+ is_dotone_staff: bool = False
27
+ account_type: str = "standard"
28
+ profile_visibility: str = "public"
29
+ created_at: str = ""
30
+ email: str = ""
31
+ phone_number: str = ""
32
+ kyc_status: str = "not_requested"
33
+
34
+ @classmethod
35
+ def from_dict(cls, data: Dict[str, Any]) -> "User":
36
+ return cls(
37
+ user_id=data.get("user_id", data.get("id", "")),
38
+ username=data.get("username", ""),
39
+ display_name=data.get("display_name", ""),
40
+ bio=data.get("bio", ""),
41
+ location=data.get("location", ""),
42
+ website=data.get("website", ""),
43
+ avatar_url=data.get("avatar_url", ""),
44
+ header_url=data.get("header_url", ""),
45
+ followers_count=data.get("followers_count", 0),
46
+ following_count=data.get("following_count", 0),
47
+ posts_count=data.get("posts_count", 0),
48
+ is_verified=data.get("is_verified", False),
49
+ is_dotone_staff=data.get("is_dotone_staff", False),
50
+ account_type=data.get("account_type", "standard"),
51
+ profile_visibility=data.get("profile_visibility", "public"),
52
+ created_at=data.get("created_at", ""),
53
+ email=data.get("email", ""),
54
+ phone_number=data.get("phone_number", ""),
55
+ kyc_status=data.get("kyc_status", "not_requested"),
56
+ )
57
+
58
+ @property
59
+ def profile_url(self) -> str:
60
+ return f"https://mydot.one/@{self.username}"
61
+
62
+
63
+ @dataclass
64
+ class Dot:
65
+ """MyDot dot (post/tweet)."""
66
+ id: str = ""
67
+ short_id: str = ""
68
+ content: str = ""
69
+ dot_type: str = "dot"
70
+ author: Dict[str, Any] = field(default_factory=dict)
71
+ reply_to: Optional[str] = None
72
+ repost_of: Optional[str] = None
73
+ quote_of: Optional[str] = None
74
+ likes_count: int = 0
75
+ replies_count: int = 0
76
+ reposts_count: int = 0
77
+ quotes_count: int = 0
78
+ bookmarks_count: int = 0
79
+ view_count: int = 0
80
+ stars: int = 0
81
+ is_liked: bool = False
82
+ is_reposted: bool = False
83
+ is_bookmarked: bool = False
84
+ created_at: Optional[str] = None
85
+ edited_at: Optional[str] = None
86
+ media: List[Dict] = field(default_factory=list)
87
+ entities: Dict = field(default_factory=dict)
88
+ sensitive_type: str = "normal"
89
+
90
+ @classmethod
91
+ def from_dict(cls, data: Dict[str, Any]) -> "Dot":
92
+ vc = data.get("viewer_context", {})
93
+ return cls(
94
+ id=data.get("id", ""),
95
+ short_id=data.get("short_id", ""),
96
+ content=data.get("content", ""),
97
+ dot_type=data.get("dot_type", "dot"),
98
+ author=data.get("author", {}),
99
+ reply_to=data.get("reply_to"),
100
+ repost_of=data.get("repost_of"),
101
+ quote_of=data.get("quote_of"),
102
+ likes_count=data.get("likes_count", 0),
103
+ replies_count=data.get("replies_count", 0),
104
+ reposts_count=data.get("reposts_count", 0),
105
+ quotes_count=data.get("quotes_count", 0),
106
+ bookmarks_count=data.get("bookmarks_count", 0),
107
+ view_count=data.get("view_count", 0),
108
+ stars=data.get("stars", 0),
109
+ is_liked=vc.get("is_liked", False),
110
+ is_reposted=vc.get("is_reposted", False),
111
+ is_bookmarked=vc.get("is_bookmarked", False),
112
+ created_at=data.get("created_at"),
113
+ edited_at=data.get("edited_at"),
114
+ media=data.get("media", []),
115
+ entities=data.get("entities", {}),
116
+ sensitive_type=data.get("sensitive_type", "normal"),
117
+ )
118
+
119
+ @property
120
+ def author_username(self) -> str:
121
+ return self.author.get("username", "")
122
+
123
+ @property
124
+ def url(self) -> str:
125
+ return f"https://mydot.one/@{self.author_username}/status/{self.short_id}"
126
+
127
+ @property
128
+ def is_reply(self) -> bool:
129
+ return self.dot_type == "reply"
130
+
131
+ @property
132
+ def is_repost(self) -> bool:
133
+ return self.dot_type == "repost"
134
+
135
+ @property
136
+ def is_quote(self) -> bool:
137
+ return self.dot_type == "quote"
138
+
139
+
140
+ @dataclass
141
+ class ThreadView:
142
+ """Thread view (parent, dot, children)."""
143
+ above: List[Dot] = field(default_factory=list)
144
+ dot: Optional[Dot] = None
145
+ below: Dict = field(default_factory=dict)
146
+
147
+ @classmethod
148
+ def from_dict(cls, data: Dict[str, Any]) -> "ThreadView":
149
+ return cls(
150
+ above=[Dot.from_dict(d) for d in data.get("above", [])],
151
+ dot=Dot.from_dict(data.get("dot", {})),
152
+ below=data.get("below", {}),
153
+ )
154
+
155
+
156
+ @dataclass
157
+ class Notification:
158
+ """MyDot notification."""
159
+ id: str = ""
160
+ type: str = ""
161
+ created_at: str = ""
162
+ is_read: bool = False
163
+ actor: Optional[Dict] = None
164
+ dot: Optional[Dot] = None
165
+
166
+ @classmethod
167
+ def from_dict(cls, data: Dict[str, Any]) -> "Notification":
168
+ dot = Dot.from_dict(data["dot"]) if data.get("dot") else None
169
+ return cls(
170
+ id=data.get("id", ""),
171
+ type=data.get("type", ""),
172
+ created_at=data.get("created_at", ""),
173
+ is_read=data.get("is_read", False),
174
+ actor=data.get("actor"),
175
+ dot=dot,
176
+ )
177
+
178
+
179
+ @dataclass
180
+ class PaginatedResponse:
181
+ """Generic paginated response."""
182
+ count: int = 0
183
+ next: Optional[str] = None
184
+ previous: Optional[str] = None
185
+ results: List[Any] = field(default_factory=list)
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiodot
3
+ Version: 1.0.0
4
+ Summary: Async Python client for MyDot.one social platform
5
+ Home-page: https://github.com/karbaladev/aiodot
6
+ Author: karbaladev.ir
7
+ Author-email: "karbaladev.ir" <saleh.mohammadi.az@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://karbaladev.ir
10
+ Project-URL: Repository, https://github.com/karbaladevir/aiodot
11
+ Project-URL: Issues, https://github.com/karbaladevir/aiodot/issues
12
+ Keywords: mydot,aiodot,mydot-bot,social-media,async,bot
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: aiohttp>=3.8.0
20
+ Requires-Dist: requests>=2.28.0
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+ Dynamic: requires-python
25
+
26
+ <p align="center">
27
+ <img src="https://abrehamrahi.ir/o/public/rNf0ej8l/" alt="aiodot logo" width="200">
28
+ </p>
29
+
30
+ <h1 align="center">aiodot 🚀</h1>
31
+ <p align="center">
32
+ <b>Async Python client for MyDot.one social platform</b><br>
33
+ Build bots, automation, and tools with ease.
34
+ </p>
35
+
36
+ <p align="center">
37
+ <a href="https://pypi.org/project/aiodot/">
38
+ <img src="https://badge.fury.io/py/aiodot.svg" alt="PyPI version">
39
+ </a>
40
+ <a href="https://pypi.org/project/aiodot/">
41
+ <img src="https://img.shields.io/pypi/pyversions/aiodot.svg" alt="Python">
42
+ </a>
43
+ <a href="https://github.com/karbaladev/aiodot/blob/main/LICENSE">
44
+ <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
45
+ </a>
46
+ </p>
47
+
48
+ ---
49
+
50
+ **`aiodot`** is an asynchronous Python library for [MyDot.one](https://mydot.one). Built with `aiohttp` — fast, async-first, session-based like aiogram.
51
+
52
+ ---
53
+
54
+ ## ✨ Features
55
+
56
+ - ⚡ **Async-first** — Built on aiohttp
57
+ - 💾 **Session persistence** — Login once, auto-loads next time
58
+ - 🔄 **Auto token refresh** — Handles 401 gracefully
59
+ - 📦 **50+ endpoints** — Full API coverage
60
+ - 🎯 **Type hints** — Full type annotations
61
+
62
+ ---
63
+
64
+ ## 📦 Installation
65
+
66
+ ```bash
67
+ pip install aiodot
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 💡 Quick Start
73
+
74
+ ```python
75
+ import asyncio
76
+ from aiodot import MyDotClient
77
+
78
+ TOKEN = "your-token" # F12 → Cookies → __Secure-access_token
79
+
80
+ async def main():
81
+ async with MyDotClient(token=TOKEN, session_file="user.session.json") as client:
82
+ me = await client.get_me()
83
+ print(f"@{me.username}")
84
+
85
+ dot = await client.create_dot("Hello aiodot! 🚀")
86
+ print(dot.url)
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 🤖 Echo Bot
94
+
95
+ ```python
96
+ import asyncio
97
+ from aiodot import MyDotClient
98
+
99
+ TOKEN = "your-token"
100
+
101
+ async def main():
102
+ processed = set()
103
+ async with MyDotClient(token=TOKEN, session_file="bot.session.json") as c:
104
+ print(f"🤖 @{(await c.get_me()).username} started!")
105
+ while True:
106
+ for n in (await c.get_notifications(10)).get("results", []):
107
+ if n.get("id") in processed: continue
108
+ processed.add(n.get("id"))
109
+ if n.get("type") == "mention":
110
+ d = n.get("dot", {})
111
+ if d.get("id"):
112
+ await c.reply(d["id"], "Hello! 🤖")
113
+ await asyncio.sleep(15)
114
+
115
+ asyncio.run(main())
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 📖 API Reference
121
+
122
+ | Category | Methods |
123
+ |----------|---------|
124
+ | **Profile** | `get_me()`, `update_profile()`, `set_visibility()` |
125
+ | **Dots** | `create_dot()`, `get_dot()`, `reply()`, `repost()`, `quote()`, `like()`, `bookmark()`, `edit_dot()`, `delete_dot()` |
126
+ | **Social** | `follow()`, `unfollow()`, `block()`, `unblock()`, `mute()`, `unmute()` |
127
+ | **Users** | `search_users()`, `get_user_followers()`, `get_user_following()`, `get_user_dots()` |
128
+ | **Feed** | `home_feed()`, `following_feed()`, `follow_suggestions()` |
129
+ | **Notifications** | `get_notifications()`, `mark_all_read()`, `get_notification_preferences()` |
130
+ | **Trending** | `get_trending_hashtags()`, `get_trending_media()` |
131
+
132
+ ---
133
+
134
+ ## ⚠️ Disclaimer
135
+
136
+ Unofficial library. Use responsibly per MyDot.one Terms of Service.
137
+
138
+ ---
139
+
140
+ ## 📡 Community
141
+
142
+ - 📢 **Telegram:** [@aiodotlib](https://t.me/aiodotlib)
143
+ - 💬 **Bale:** [ble.ir/aiodot](https://ble.ir/aiodot)
144
+ - 🌐 **Website:** [karbaladev.ir](https://karbaladev.ir)
145
+
146
+ ---
147
+
148
+ ## 📄 License
149
+
150
+ MIT © [karbaladev.ir](https://karbaladev.ir)
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ aiodot/__init__.py
6
+ aiodot/auth.py
7
+ aiodot/client.py
8
+ aiodot/models.py
9
+ aiodot.egg-info/PKG-INFO
10
+ aiodot.egg-info/SOURCES.txt
11
+ aiodot.egg-info/dependency_links.txt
12
+ aiodot.egg-info/requires.txt
13
+ aiodot.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ aiohttp>=3.8.0
2
+ requests>=2.28.0
@@ -0,0 +1 @@
1
+ aiodot
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aiodot"
7
+ version = "1.0.0"
8
+ description = "Async Python client for MyDot.one social platform"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [
12
+ {name = "karbaladev.ir", email = "saleh.mohammadi.az@gmail.com"}
13
+ ]
14
+ keywords = ["mydot", "aiodot", "mydot-bot", "social-media", "async", "bot"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ ]
20
+ requires-python = ">=3.8"
21
+ dependencies = [
22
+ "aiohttp>=3.8.0",
23
+ "requests>=2.28.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://karbaladev.ir"
28
+ Repository = "https://github.com/karbaladevir/aiodot"
29
+ Issues = "https://github.com/karbaladevir/aiodot/issues"
aiodot-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
aiodot-1.0.0/setup.py ADDED
@@ -0,0 +1,43 @@
1
+ """aiodot - Async Python client for MyDot.one. Install with: pip install aiodot"""
2
+
3
+ from setuptools import setup, find_packages
4
+
5
+ with open("README.md", "r", encoding="utf-8") as fh:
6
+ long_description = fh.read()
7
+
8
+ setup(
9
+ name="aiodot",
10
+ version="1.0.0",
11
+ author="karbaladev.ir",
12
+ author_email="saleh.mohammadi.az@gmail.com",
13
+ description="Async Python client for MyDot.one social platform",
14
+ long_description=long_description,
15
+ long_description_content_type="text/markdown",
16
+ url="https://github.com/karbaladev/aiodot",
17
+ project_urls={
18
+ "Website": "https://karbaladev.ir",
19
+ "Bug Tracker": "https://github.com/karbaladev/aiodot/issues",
20
+ "Source Code": "https://github.com/karbaladev/aiodot",
21
+ },
22
+ packages=find_packages(),
23
+ classifiers=[
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.8",
30
+ "Programming Language :: Python :: 3.9",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Framework :: AsyncIO",
36
+ ],
37
+ python_requires=">=3.8",
38
+ install_requires=[
39
+ "aiohttp>=3.8.0",
40
+ "requests>=2.28.0",
41
+ ],
42
+ keywords="mydot, aiodot, mydot-bot, social-media, async, bot, user-bot",
43
+ )