diff --git a/selfcord/api/events.py b/selfcord/api/events.py index f0cc504..862a15a 100644 --- a/selfcord/api/events.py +++ b/selfcord/api/events.py @@ -182,7 +182,7 @@ async def handle_ready_supplemental(self, data: dict): await self.bot.inbuilt_commands() await self.bot.emit("ready_supplemental") - await asyncio.sleep(2) + await asyncio.sleep(4) for guild in self.bot.user.guilds: if guild.member_count >= 1000: diff --git a/selfcord/api/gateway.py b/selfcord/api/gateway.py index 9d09210..fafb634 100644 --- a/selfcord/api/gateway.py +++ b/selfcord/api/gateway.py @@ -7,10 +7,11 @@ import websockets from aioconsole import aprint import ujson - +from websockets.client import connect +from websockets.exceptions import ConnectionClosed, ConnectionClosedError, ConnectionClosedOK if TYPE_CHECKING: from ..bot import Bot - from websockets import Connect + from websockets import connection from ..models import Capabilities, Guild, Messageable @@ -49,16 +50,36 @@ def __init__(self, bot: Bot, decompress: bool = True) -> None: "wss://gateway.discord.gg/?encoding=json&v=9" ) + async def linux_run(self, cmd): + proc = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stout, stderr = await proc.communicate() + + + async def send_json(self, payload: dict): + sleep = 2 if self.ws: try: await self.ws.send(ujson.dumps(payload)) - except Exception as e: - await aprint(f"Closing because fail send. Attempting reconnect\n{e}") + except ConnectionClosed as e: + if e.rcvd is not None: + if e.rcvd.code == 4008: + sleep += 10 + await aprint(f"RECEIVE: {e.rcvd.code} --- {e.rcvd.reason}") + if e.sent is not None: + if e.sent.code == 4008: + sleep += 10 + await aprint(f"SENT: {e.sent.code} --- {e.sent.reason}") + await aprint(f"Closing because fail send. Attempting reconnect {self.bot.user.username}\n{e}") + # await self.linux_run(f"notify-send 'RECONNECT HAPPENING NOW CHECK CONSOLE'") await self.close() - await asyncio.sleep(2) - await self.connect(f"{self.URL}") + await asyncio.sleep(sleep) + await self.connect(f"{self.bot.resume_url}?encoding=json&v=9&compress=zlib-stream") async def load_async(self, item): loop = asyncio.get_event_loop() @@ -103,14 +124,15 @@ async def recv_json(self): self.heartbeat_ack() elif op == self.RECONNECT: - await aprint("Attempting reconnect????") + await aprint(f"Attempting reconnect???? {self.bot.user.username}") + # await self.linux_run(f"notify-send 'RECONNECT HAPPENING NOW CHECK CONSOLE {data} {op}'") await self.close() await asyncio.sleep(3) await self.connect(f"{self.bot.resume_url}?encoding=json&v=9&compress=zlib-stream") await self.send_json({ - "op": 6, + "op": self.RESUME, "d": {"token": self.token, "session_id": self.bot.session_id, "seq": seq}, }) @@ -121,7 +143,7 @@ async def recv_json(self): asyncio.create_task(method(data)) async def connect(self, url: str): - self.ws = await websockets.connect( + self.ws = await connect( url, origin="https://discord.com", max_size=None, extra_headers={"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0"}, read_limit=1000000, max_queue=100, write_limit=1000000, @@ -130,17 +152,28 @@ async def connect(self, url: str): self.zlib = decompressobj(15) async def start(self, token: str): + sleep = 2 await self.connect(self.URL) self.token = token while self.alive: try: await self.recv_json() - except Exception as e: - await aprint(f"Closing because fail recv. Attempting reconnect\n{e}") + except ConnectionClosed as e: + if e.rcvd is not None: + if e.rcvd.code == 4008: + sleep += 5 + + await aprint(f"RECEIVE: {e.rcvd.code} --- {e.rcvd.reason}") + if e.sent is not None: + if e.sent.code == 4008: + sleep += 5 + await aprint(f"SENT: {e.sent.code} --- {e.sent.reason}") + await aprint(f"Closing because fail recv. Attempting reconnect {self.bot.user.username}\n{e}") + # await self.linux_run(f"notify-send 'RECONNECT HAPPENING NOW CHECK CONSOLE'") await self.close() - await asyncio.sleep(2) - await self.connect(f"{self.URL}") + await asyncio.sleep(sleep) + await self.connect(f"{self.bot.resume_url}?encoding=json&v=9&compress=zlib-stream") async def cache_guild(self, guild: Guild, channel): payload = { diff --git a/selfcord/models/channels.py b/selfcord/models/channels.py index 0122e57..57d2648 100644 --- a/selfcord/models/channels.py +++ b/selfcord/models/channels.py @@ -7,6 +7,7 @@ import datetime import time from .permissions import Permission +import ujson if TYPE_CHECKING: from .users import User @@ -14,6 +15,7 @@ from ..api import HttpClient + class PermissionOverwrite: def __init__(self, payload: dict, bot: Bot): self.bot: Bot = bot @@ -26,6 +28,83 @@ def update(self, payload: dict): self.allow: Permission = Permission(payload["allow"], self.bot) self.deny: Permission = Permission(payload["deny"], self.bot) + + + +class SlashCommand: + def __init__(self, payload: dict, bot: Bot) -> None: + self.bot = bot + self.http = bot.http + self.update(payload) + + def update(self, payload): + + self.id = payload.get("id") + self.type = payload.get("type") + self.application_id = payload.get("application_id") + self.guild_id = payload.get("guild_id") + self.version = payload.get("version") + self.name = payload.get("name") + self.description = payload.get("description") + self.description_default = payload.get("description_default") + self.options = [SubCommandOption(option, self) for option in payload.get("options", [])] + self.my_options = [] + self.integration_types = payload.get("integration_types") + self.raw_data = payload + self.required = payload.get("required", False) + + def add_value(self, name: str, value): + new = {} + + if type(value) == list: + for option in self.options: + if option.name == name: + new['name'] = name + new['type'] = option.type + new['options'] = value + return self.my_options.append(new) + + for option in self.options: + if option.name == name: + new['name'] = name + new['value'] = value + new['type'] = option.type + return self.my_options.append(new) + + def get_option(self, name: str): + for option in self.options: + if option.name == name: + return option + +class SubCommandOption(SlashCommand): + def __init__(self, payload: dict, main: SlashCommand) -> None: + self.cmd = main + self.update(payload) + + + def update(self, payload): + self.opt_options = payload.get("options", []) + self.name = payload.get("name") + self.description = payload.get("description") + self.required = payload.get("required", False) + self.type = payload.get("type") + self.my_options = [] + + def add_value(self, name: str, value): + new = {} + for option in self.opt_options: + if option.get("name", "") == name: + new['name'] = name + new['value'] = value + new['type'] = option['type'] + self.my_options.append(new) + + + def reconstruct(self): + self.cmd.add_value(self.name, self.my_options) + + + class Channel: def __init__(self, payload: dict, bot: Bot): self.bot: Bot = bot @@ -113,6 +192,49 @@ async def delayed_delete(self, message, time): await asyncio.sleep(time) await message.delete() + async def get_slash_commands(self): + if self.guild_id is not None: + json = await self.http.request( + "GET", f"/guilds/{self.guild_id}/application-command-index" + ) + if json is not None: + cmds = [] + for cmd in json['application_commands']: + if cmd.get("guild_id") is None: + cmd.update({"guild_id": self.guild_id}) + cmds.append(SlashCommand(cmd, self.bot)) + + return cmds + + async def trigger_slash(self, cmd: SlashCommand): + if self.guild_id is not None: + # print(cmd.my_options) + + json = { + "type": 2, + "application_id": cmd.application_id, + "guild_id": self.guild_id, + "channel_id": self.id, + "session_id": self.bot.session_id, + "data": { + "version": cmd.version, # version not appending + "id": cmd.id, + "name": cmd.name, + "type": cmd.type, + "options": cmd.my_options, + "application_command": cmd.raw_data + } + } + json = ujson.dumps(json) + boundary = f"----WebkitFormBoundaryNiggerNiggerSexy" + data = f'--{boundary}\r\nContent-Disposition: form-data; name="payload_json"\r\n\r\n{json}\r\n--{boundary}--' + + json = await self.http.request( + "POST", "/interactions", + headers={"content-type": f"multipart/form-data; boundary={boundary}"}, + data=data + ) + async def send( self, content: str, files: Optional[list[str]] = None, delete_after: Optional[int] = None, tts: bool = False ) -> Optional[Message]: