modifyself 0.3.7__tar.gz → 0.3.8__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.
Files changed (51) hide show
  1. {modifyself-0.3.7 → modifyself-0.3.8}/PKG-INFO +1 -2
  2. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/__init__.py +1 -1
  3. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/headers.py +14 -12
  4. modifyself-0.3.8/modifyself/utils.py +137 -0
  5. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself.egg-info/PKG-INFO +1 -2
  6. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself.egg-info/requires.txt +0 -1
  7. {modifyself-0.3.7 → modifyself-0.3.8}/pyproject.toml +1 -2
  8. modifyself-0.3.7/modifyself/utils.py +0 -177
  9. {modifyself-0.3.7 → modifyself-0.3.8}/LICENSE +0 -0
  10. {modifyself-0.3.7 → modifyself-0.3.8}/README.md +0 -0
  11. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/activity.py +0 -0
  12. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/client.py +0 -0
  13. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/commands/__init__.py +0 -0
  14. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/commands/checks.py +0 -0
  15. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/commands/cog.py +0 -0
  16. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/commands/context.py +0 -0
  17. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/commands/converters.py +0 -0
  18. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/commands/core.py +0 -0
  19. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/components.py +0 -0
  20. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/core/bitfield.py +0 -0
  21. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/core/enums.py +0 -0
  22. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/core/mixins.py +0 -0
  23. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/core/snowflake.py +0 -0
  24. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/errors.py +0 -0
  25. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/gateway/__init__.py +0 -0
  26. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/gateway/dispatcher.py +0 -0
  27. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/gateway/heartbeat.py +0 -0
  28. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/gateway/websocket.py +0 -0
  29. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/http/__init__.py +0 -0
  30. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/http/client.py +0 -0
  31. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/http/ratelimit.py +0 -0
  32. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/http/route.py +0 -0
  33. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/interactions.py +0 -0
  34. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/__init__.py +0 -0
  35. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/base.py +0 -0
  36. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/billing.py +0 -0
  37. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/channel.py +0 -0
  38. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/guild.py +0 -0
  39. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/member.py +0 -0
  40. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/message.py +0 -0
  41. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/relationship.py +0 -0
  42. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/settings.py +0 -0
  43. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/user.py +0 -0
  44. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/models/webhook.py +0 -0
  45. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/state.py +0 -0
  46. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/types.py +0 -0
  47. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself/voice.py +0 -0
  48. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself.egg-info/SOURCES.txt +0 -0
  49. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself.egg-info/dependency_links.txt +0 -0
  50. {modifyself-0.3.7 → modifyself-0.3.8}/modifyself.egg-info/top_level.txt +0 -0
  51. {modifyself-0.3.7 → modifyself-0.3.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modifyself
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: A clean, pythonic Discord self-bot library
5
5
  Author-email: modifyself <modifyself@example.com>
6
6
  License-Expression: MIT
@@ -15,7 +15,6 @@ Requires-Dist: wreq>=0.1.0
15
15
  Requires-Dist: aiohttp>=3.8.0
16
16
  Requires-Dist: plyer>=0.9
17
17
  Requires-Dist: pillow>=10.0
18
- Requires-Dist: requests>=2.28
19
18
  Requires-Dist: PyNaCl>=1.5.0
20
19
  Requires-Dist: opuslib>=3.0.0
21
20
  Dynamic: license-file
@@ -2,7 +2,7 @@
2
2
  modifyself ? a clean, pythonic Discord self-bot library.
3
3
  """
4
4
 
5
- __version__ = "0.3.7"
5
+ __version__ = "0.3.8"
6
6
 
7
7
  from .client import Client
8
8
  from .commands.core import command
@@ -9,8 +9,9 @@ import hashlib
9
9
  from dataclasses import dataclass
10
10
  from typing import Dict, Optional, Tuple, List, Any, Union
11
11
 
12
+ import urllib.request as _urllib_req
13
+
12
14
  from wreq import Emulation
13
- import requests as _requests
14
15
 
15
16
  _GREASE_BRANDS = [
16
17
  ('"Not A(Brand"', "99"),
@@ -72,8 +73,6 @@ class HeaderSpoofer:
72
73
  self._rng = random.Random(self._seed)
73
74
  self._emulation = emulation
74
75
  self.profile = self._make_profile()
75
- self.session = _requests.Session()
76
- self.session.headers.update({"User-Agent": self.profile.user_agent})
77
76
  self._async_client: Optional[Any] = None
78
77
  self.build_number = self._get_build_number()
79
78
  self._launch_id = str(uuid.uuid4())
@@ -105,11 +104,12 @@ class HeaderSpoofer:
105
104
 
106
105
  cache_file = os.path.join(os.path.dirname(__file__), ".chrome_version.json")
107
106
  try:
108
- r = self.session.get(
107
+ _req = _urllib_req.Request(
109
108
  "https://versionhistory.googleapis.com/v1/chrome/platforms/win/channels/stable/versions/all/releases?filter=endtime=none",
110
- timeout=5,
109
+ headers={"User-Agent": "Mozilla/5.0"},
111
110
  )
112
- data = r.json()
111
+ with _urllib_req.urlopen(_req, timeout=5) as _resp:
112
+ data = json.loads(_resp.read())
113
113
  latest = max(data["releases"], key=lambda x: [int(p) for p in x["version"].split(".")])
114
114
  major = latest["version"].split(".")[0]
115
115
  HeaderSpoofer._chrome_cache = {"major": major}
@@ -162,18 +162,20 @@ class HeaderSpoofer:
162
162
  build_cache_file = os.path.join(os.path.dirname(__file__), ".build_cache.json")
163
163
 
164
164
  try:
165
- r = self.session.get(
165
+ _req1 = _urllib_req.Request(
166
166
  "https://discord.com/login",
167
167
  headers={"User-Agent": self.profile.user_agent},
168
- timeout=30,
169
168
  )
170
- scripts = re.findall(r'<script[^>]+src="(/assets/[^"]+\.js)"', r.text)
169
+ with _urllib_req.urlopen(_req1, timeout=30) as _resp1:
170
+ _html = _resp1.read().decode("utf-8", errors="replace")
171
+ scripts = re.findall(r'<script[^>]+src="(/assets/[^"]+\.js)"', _html)
171
172
  for path in reversed(scripts):
172
- js = self.session.get(
173
+ _req2 = _urllib_req.Request(
173
174
  f"https://discord.com{path}",
174
175
  headers={"User-Agent": self.profile.user_agent},
175
- timeout=30,
176
176
  )
177
+ with _urllib_req.urlopen(_req2, timeout=30) as _resp2:
178
+ _js_text = _resp2.read().decode("utf-8", errors="replace")
177
179
  for pat in [
178
180
  r'buildNumber\s*[=:]\s*"?(\d{5,7})"?',
179
181
  r'"buildNumber","(\d{5,7})"',
@@ -181,7 +183,7 @@ class HeaderSpoofer:
181
183
  r'build_number["\s:=]+(\d{5,7})',
182
184
  r'"(\d{6,7})"\s*,\s*"stable"',
183
185
  ]:
184
- m = re.search(pat, js.text)
186
+ m = re.search(pat, _js_text)
185
187
  if m:
186
188
  build = int(m.group(1))
187
189
  HeaderSpoofer._build_cache = build
@@ -0,0 +1,137 @@
1
+ """
2
+ Pure utility functions. No Discord-specific state here.
3
+ """
4
+
5
+ import re
6
+ import os
7
+ import sys
8
+ import tempfile
9
+ from datetime import datetime, timezone
10
+ from typing import Optional
11
+
12
+
13
+
14
+ MARKDOWN_ESCAPE_RE = re.compile(r"([*_{\[\]()~`>\#+\-=|.!])")
15
+
16
+
17
+ def escape_markdown(text: str) -> str:
18
+ """Escape Discord markdown characters."""
19
+ return MARKDOWN_ESCAPE_RE.sub(r"\\\1", text)
20
+
21
+
22
+ def parse_time(timestamp: str) -> datetime:
23
+ """Parse an ISO 8601 timestamp from Discord."""
24
+ if timestamp.endswith("Z"):
25
+ timestamp = timestamp[:-1] + "+00:00"
26
+ return datetime.fromisoformat(timestamp)
27
+
28
+
29
+ def snowflake_time(snowflake: int) -> datetime:
30
+ """Extract the creation time from a snowflake ID."""
31
+ from .core.snowflake import Snowflake
32
+ return datetime.fromtimestamp(
33
+ ((snowflake >> 22) + Snowflake.EPOCH) / 1000,
34
+ tz=timezone.utc,
35
+ )
36
+
37
+
38
+ def chunk_list(lst: list, size: int):
39
+ """Yield successive chunks of a list."""
40
+ for i in range(0, len(lst), size):
41
+ yield lst[i : i + size]
42
+
43
+
44
+ def find(predicate, iterable):
45
+ """Return the first item in iterable matching predicate."""
46
+ for item in iterable:
47
+ if predicate(item):
48
+ return item
49
+ return None
50
+
51
+
52
+ def get(iterable, **attrs):
53
+ """Return the first item in iterable with matching attributes."""
54
+ for item in iterable:
55
+ if all(getattr(item, k, None) == v for k, v in attrs.items()):
56
+ return item
57
+ return None
58
+
59
+
60
+ def oauth_url(client_id: int, *, permissions: int = 0, guild_id: int | None = None):
61
+ """Generate an OAuth2 authorization URL."""
62
+ url = f"https://discord.com/oauth2/authorize?client_id={client_id}&scope=bot"
63
+ if permissions:
64
+ url += f"&permissions={permissions}"
65
+ if guild_id:
66
+ url += f"&guild_id={guild_id}"
67
+ return url
68
+
69
+
70
+ def send_notification(
71
+ title: str,
72
+ message: str,
73
+ image_url: Optional[str] = None,
74
+ timeout: int = 5,
75
+ app_name: str = "modifyself"
76
+ ) -> bool:
77
+ """Send a system desktop notification with an optional image icon."""
78
+ try:
79
+ from plyer import notification as _notify
80
+ except ImportError:
81
+ print("[modifyself] plyer not installed. Install with: pip install plyer pillow")
82
+ return False
83
+
84
+ try:
85
+ icon_path = None
86
+
87
+ if image_url:
88
+ try:
89
+ import urllib.request as _ureq
90
+ with _ureq.urlopen(image_url, timeout=10) as _resp:
91
+ img_data = _resp.read()
92
+ is_windows = sys.platform.startswith("win")
93
+ suffix = ".ico" if is_windows else ".png"
94
+ icon_temp = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
95
+ icon_temp.write(img_data)
96
+ icon_temp.close()
97
+ icon_path = icon_temp.name
98
+ if is_windows:
99
+ try:
100
+ from PIL import Image
101
+ with Image.open(icon_path) as img:
102
+ if img.format != "ICO":
103
+ ico_path = icon_path.replace(".ico", "_converted.ico")
104
+ img.save(ico_path, format="ICO", sizes=[(32, 32), (48, 48), (64, 64)])
105
+ try:
106
+ os.unlink(icon_path)
107
+ except Exception:
108
+ pass
109
+ icon_path = ico_path
110
+ except Exception as e:
111
+ print(f"[modifyself] ICO conversion warning: {e}")
112
+ except Exception as e:
113
+ print(f"[modifyself] Error downloading notification image: {e}")
114
+
115
+ _notify.notify(
116
+ title=title,
117
+ message=message,
118
+ app_name=app_name,
119
+ app_icon=icon_path,
120
+ timeout=timeout,
121
+ )
122
+
123
+ if icon_path and os.path.exists(icon_path):
124
+ try:
125
+ os.unlink(icon_path)
126
+ except Exception:
127
+ pass
128
+
129
+ return True
130
+
131
+ except Exception as e:
132
+ print(f"[modifyself] Failed to send notification: {e}")
133
+ return False
134
+
135
+ # Default notification images - using GitHub raw URL
136
+ START_IMAGE = None
137
+ ERROR_IMAGE = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modifyself
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: A clean, pythonic Discord self-bot library
5
5
  Author-email: modifyself <modifyself@example.com>
6
6
  License-Expression: MIT
@@ -15,7 +15,6 @@ Requires-Dist: wreq>=0.1.0
15
15
  Requires-Dist: aiohttp>=3.8.0
16
16
  Requires-Dist: plyer>=0.9
17
17
  Requires-Dist: pillow>=10.0
18
- Requires-Dist: requests>=2.28
19
18
  Requires-Dist: PyNaCl>=1.5.0
20
19
  Requires-Dist: opuslib>=3.0.0
21
20
  Dynamic: license-file
@@ -3,6 +3,5 @@ wreq>=0.1.0
3
3
  aiohttp>=3.8.0
4
4
  plyer>=0.9
5
5
  pillow>=10.0
6
- requests>=2.28
7
6
  PyNaCl>=1.5.0
8
7
  opuslib>=3.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modifyself"
7
- version = "0.3.7"
7
+ version = "0.3.8"
8
8
  description = "A clean, pythonic Discord self-bot library"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -18,7 +18,6 @@ dependencies = [
18
18
  "aiohttp>=3.8.0",
19
19
  "plyer>=0.9",
20
20
  "pillow>=10.0",
21
- "requests>=2.28",
22
21
  "PyNaCl>=1.5.0",
23
22
  "opuslib>=3.0.0",
24
23
  ]
@@ -1,177 +0,0 @@
1
- """
2
- Pure utility functions. No Discord-specific state here.
3
- """
4
-
5
- import re
6
- import os
7
- import sys
8
- import tempfile
9
- from datetime import datetime, timezone
10
- from typing import Optional
11
-
12
- # Notification imports
13
- try:
14
- from plyer import notification
15
- import requests
16
- from PIL import Image
17
- NOTIFICATION_AVAILABLE = True
18
- except ImportError:
19
- NOTIFICATION_AVAILABLE = False
20
-
21
-
22
- MARKDOWN_ESCAPE_RE = re.compile(r"([*_{\[\]()~`>\#+\-=|.!])")
23
-
24
-
25
- def escape_markdown(text: str) -> str:
26
- """Escape Discord markdown characters."""
27
- return MARKDOWN_ESCAPE_RE.sub(r"\\\1", text)
28
-
29
-
30
- def parse_time(timestamp: str) -> datetime:
31
- """Parse an ISO 8601 timestamp from Discord."""
32
- if timestamp.endswith("Z"):
33
- timestamp = timestamp[:-1] + "+00:00"
34
- return datetime.fromisoformat(timestamp)
35
-
36
-
37
- def snowflake_time(snowflake: int) -> datetime:
38
- """Extract the creation time from a snowflake ID."""
39
- from .core.snowflake import Snowflake
40
- return datetime.fromtimestamp(
41
- ((snowflake >> 22) + Snowflake.EPOCH) / 1000,
42
- tz=timezone.utc,
43
- )
44
-
45
-
46
- def chunk_list(lst: list, size: int):
47
- """Yield successive chunks of a list."""
48
- for i in range(0, len(lst), size):
49
- yield lst[i : i + size]
50
-
51
-
52
- def find(predicate, iterable):
53
- """Return the first item in iterable matching predicate."""
54
- for item in iterable:
55
- if predicate(item):
56
- return item
57
- return None
58
-
59
-
60
- def get(iterable, **attrs):
61
- """Return the first item in iterable with matching attributes."""
62
- for item in iterable:
63
- if all(getattr(item, k, None) == v for k, v in attrs.items()):
64
- return item
65
- return None
66
-
67
-
68
- def oauth_url(client_id: int, *, permissions: int = 0, guild_id: int | None = None):
69
- """Generate an OAuth2 authorization URL."""
70
- url = f"https://discord.com/oauth2/authorize?client_id={client_id}&scope=bot"
71
- if permissions:
72
- url += f"&permissions={permissions}"
73
- if guild_id:
74
- url += f"&guild_id={guild_id}"
75
- return url
76
-
77
-
78
- def send_notification(
79
- title: str,
80
- message: str,
81
- image_url: Optional[str] = None,
82
- timeout: int = 5,
83
- app_name: str = "modifyself"
84
- ) -> bool:
85
- """
86
- Send a system notification with an optional image.
87
-
88
- Args:
89
- title: Notification title
90
- message: Notification message
91
- image_url: Optional URL to an image to show as the app icon
92
- timeout: How long to show the notification (seconds)
93
- app_name: Name of the app
94
-
95
- Returns:
96
- True if notification was sent, False otherwise
97
- """
98
- if not NOTIFICATION_AVAILABLE:
99
- try:
100
- import plyer
101
- except ImportError:
102
- print("[modifyself] plyer not installed. Install with: pip install plyer pillow requests")
103
- return False
104
- except Exception as e:
105
- print(f"[modifyself] Could not initialize notifications: {e}")
106
- return False
107
-
108
- try:
109
- icon_path = None
110
-
111
- # Download image if provided
112
- if image_url:
113
- try:
114
- response = requests.get(image_url, timeout=10, stream=True)
115
- if response.status_code == 200:
116
- # Always use .ico extension for Windows
117
- is_windows = sys.platform.startswith('win')
118
- suffix = '.ico' if is_windows else '.png'
119
-
120
- # Create temp file with correct extension
121
- icon_temp = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
122
- icon_temp.write(response.content)
123
- icon_temp.close() # CRITICAL: Close the file before using it
124
- icon_path = icon_temp.name
125
-
126
- # On Windows, ensure it's a valid ICO
127
- if is_windows:
128
- try:
129
- # Open the file to verify it's an image
130
- with Image.open(icon_path) as img:
131
- # If it's not already ICO, convert it
132
- if img.format != 'ICO':
133
- ico_path = icon_path.replace('.ico', '_converted.ico')
134
- # Save as ICO with common sizes
135
- img.save(ico_path, format='ICO', sizes=[(32, 32), (48, 48), (64, 64)])
136
- # Close the image
137
- img.close()
138
- # Delete original
139
- try:
140
- os.unlink(icon_path)
141
- except:
142
- pass
143
- icon_path = ico_path
144
- except Exception as e:
145
- print(f"[modifyself] ICO conversion warning: {e}")
146
- # Keep the original if conversion fails
147
- pass
148
- else:
149
- print(f"[modifyself] Could not download notification image (status {response.status_code})")
150
- except Exception as e:
151
- print(f"[modifyself] Error downloading notification image: {e}")
152
-
153
- # Send the notification
154
- notification.notify(
155
- title=title,
156
- message=message,
157
- app_name=app_name,
158
- app_icon=icon_path,
159
- timeout=timeout
160
- )
161
-
162
- # Clean up temp file
163
- if icon_path and os.path.exists(icon_path):
164
- try:
165
- os.unlink(icon_path)
166
- except Exception:
167
- pass
168
-
169
- return True
170
-
171
- except Exception as e:
172
- print(f"[modifyself] Failed to send notification: {e}")
173
- return False
174
-
175
- # Default notification images - using GitHub raw URL
176
- START_IMAGE = None
177
- ERROR_IMAGE = None
File without changes
File without changes
File without changes