2023-11-04 18:13:42 +00:00
|
|
|
from config.constants import discordClientID, isUnix, processID
|
2022-05-14 09:43:02 +00:00
|
|
|
from typing import Any, Optional
|
2022-05-12 07:14:23 +00:00
|
|
|
from utils.logging import logger
|
2022-05-10 20:23:12 +00:00
|
|
|
import asyncio
|
|
|
|
import json
|
2022-05-14 09:43:02 +00:00
|
|
|
import models.discord
|
2022-05-10 20:23:12 +00:00
|
|
|
import os
|
|
|
|
import struct
|
|
|
|
import time
|
|
|
|
|
2023-11-04 18:13:42 +00:00
|
|
|
class DiscordIpcService:
|
2022-05-10 20:23:12 +00:00
|
|
|
|
2023-11-05 10:24:36 +00:00
|
|
|
def __init__(self, ipcPipeNumber: Optional[int]):
|
|
|
|
ipcPipeNumber = ipcPipeNumber or -1
|
|
|
|
ipcPipeNumbers = range(10) if ipcPipeNumber == -1 else [ipcPipeNumber]
|
|
|
|
ipcPipeBase = ("/run/app" if os.path.isdir("/run/app") else os.environ.get("XDG_RUNTIME_DIR", os.environ.get("TMPDIR", os.environ.get("TMP", os.environ.get("TEMP", "/tmp"))))) if isUnix else r"\\?\pipe"
|
2024-01-04 06:02:39 +00:00
|
|
|
self.ipcPipes: list[str] = []
|
|
|
|
for ipcPipeNumber in ipcPipeNumbers:
|
|
|
|
pipeFilename = f"discord-ipc-{ipcPipeNumber}"
|
|
|
|
self.ipcPipes.append(os.path.join(ipcPipeBase, pipeFilename))
|
|
|
|
if ipcPipeBase == os.environ.get("XDG_RUNTIME_DIR"):
|
|
|
|
self.ipcPipes.append(os.path.join(ipcPipeBase, "app", "com.discordapp.Discord", pipeFilename))
|
|
|
|
self.ipcPipes.append(os.path.join(ipcPipeBase, ".flatpak", "com.discordapp.Discord", "xdg-run", pipeFilename))
|
2023-11-05 10:24:36 +00:00
|
|
|
self.loop: Optional[asyncio.AbstractEventLoop] = None
|
|
|
|
self.pipeReader: Optional[asyncio.StreamReader] = None
|
|
|
|
self.pipeWriter: Optional[asyncio.StreamWriter] = None
|
2022-05-10 20:23:12 +00:00
|
|
|
self.connected = False
|
|
|
|
|
2022-05-14 09:43:02 +00:00
|
|
|
async def handshake(self) -> None:
|
2023-11-05 10:24:36 +00:00
|
|
|
if not self.loop:
|
|
|
|
return
|
|
|
|
for ipcPipe in self.ipcPipes:
|
|
|
|
try:
|
|
|
|
if isUnix:
|
|
|
|
self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(ipcPipe) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType]
|
|
|
|
else:
|
|
|
|
self.pipeReader = asyncio.StreamReader()
|
|
|
|
self.pipeWriter = (await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader), ipcPipe))[0] # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType]
|
|
|
|
self.write(0, { "v": 1, "client_id": discordClientID })
|
|
|
|
if await self.read():
|
|
|
|
self.connected = True
|
|
|
|
logger.info(f"Connected to Discord IPC pipe {ipcPipe}")
|
|
|
|
break
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
|
|
|
except:
|
|
|
|
logger.exception(f"An unexpected error occured while connecting to Discord IPC pipe {ipcPipe}")
|
|
|
|
if not self.connected:
|
|
|
|
logger.error(f"Discord IPC pipe not found (attempted pipes: {', '.join(self.ipcPipes)})")
|
2022-05-10 20:23:12 +00:00
|
|
|
|
2022-05-14 09:43:02 +00:00
|
|
|
async def read(self) -> Optional[Any]:
|
2023-11-05 10:24:36 +00:00
|
|
|
if not self.pipeReader:
|
|
|
|
return
|
2022-05-10 20:23:12 +00:00
|
|
|
try:
|
|
|
|
dataBytes = await self.pipeReader.read(1024)
|
|
|
|
data = json.loads(dataBytes[8:].decode("utf-8"))
|
|
|
|
logger.debug("[READ] %s", data)
|
|
|
|
return data
|
|
|
|
except:
|
2023-11-04 18:13:42 +00:00
|
|
|
logger.exception("An unexpected error occured during an IPC read operation")
|
2022-05-10 21:57:06 +00:00
|
|
|
self.connected = False
|
2022-05-10 20:23:12 +00:00
|
|
|
|
2022-05-14 09:43:02 +00:00
|
|
|
def write(self, op: int, payload: Any) -> None:
|
2023-11-05 10:24:36 +00:00
|
|
|
if not self.pipeWriter:
|
|
|
|
return
|
2022-05-10 20:23:12 +00:00
|
|
|
try:
|
|
|
|
logger.debug("[WRITE] %s", payload)
|
|
|
|
payload = json.dumps(payload)
|
|
|
|
self.pipeWriter.write(struct.pack("<ii", op, len(payload)) + payload.encode("utf-8"))
|
|
|
|
except:
|
2023-11-04 18:13:42 +00:00
|
|
|
logger.exception("An unexpected error occured during an IPC write operation")
|
2022-05-10 21:57:06 +00:00
|
|
|
self.connected = False
|
2022-05-10 20:23:12 +00:00
|
|
|
|
2023-11-04 18:13:42 +00:00
|
|
|
def connect(self) -> None:
|
|
|
|
if self.connected:
|
2023-11-05 10:24:36 +00:00
|
|
|
logger.warning("Attempt to connect to Discord IPC pipe while already connected")
|
2023-11-04 18:13:42 +00:00
|
|
|
return
|
2023-11-05 10:24:36 +00:00
|
|
|
logger.info("Connecting to Discord IPC pipe")
|
2023-11-04 18:13:42 +00:00
|
|
|
self.loop = asyncio.new_event_loop()
|
|
|
|
self.loop.run_until_complete(self.handshake())
|
|
|
|
|
2022-05-14 09:43:02 +00:00
|
|
|
def disconnect(self) -> None:
|
2022-05-10 21:57:06 +00:00
|
|
|
if not self.connected:
|
2023-11-05 10:24:36 +00:00
|
|
|
logger.warning("Attempt to disconnect from Discord IPC pipe while not connected")
|
|
|
|
return
|
|
|
|
if not self.loop or not self.pipeWriter or not self.pipeReader:
|
2022-05-10 21:57:06 +00:00
|
|
|
return
|
2023-11-05 10:24:36 +00:00
|
|
|
logger.info("Disconnecting from Discord IPC pipe")
|
2022-05-10 21:57:06 +00:00
|
|
|
try:
|
|
|
|
self.pipeWriter.close()
|
|
|
|
except:
|
2023-11-04 18:13:42 +00:00
|
|
|
logger.exception("An unexpected error occured while closing the IPC pipe writer")
|
2022-05-10 21:57:06 +00:00
|
|
|
try:
|
|
|
|
self.loop.run_until_complete(self.pipeReader.read())
|
|
|
|
except:
|
2023-11-04 18:13:42 +00:00
|
|
|
logger.exception("An unexpected error occured while closing the IPC pipe reader")
|
2022-05-10 20:23:12 +00:00
|
|
|
try:
|
|
|
|
self.loop.close()
|
|
|
|
except:
|
2023-11-04 18:13:42 +00:00
|
|
|
logger.exception("An unexpected error occured while closing the asyncio event loop")
|
2022-05-10 20:23:12 +00:00
|
|
|
self.connected = False
|
|
|
|
|
2022-05-14 09:43:02 +00:00
|
|
|
def setActivity(self, activity: models.discord.Activity) -> None:
|
2023-11-05 10:24:36 +00:00
|
|
|
if not self.connected:
|
|
|
|
logger.warning("Attempt to set activity while not connected to Discord IPC pipe")
|
|
|
|
return
|
|
|
|
if not self.loop:
|
|
|
|
return
|
2022-05-10 20:23:12 +00:00
|
|
|
logger.info("Activity update: %s", activity)
|
|
|
|
payload = {
|
|
|
|
"cmd": "SET_ACTIVITY",
|
|
|
|
"args": {
|
|
|
|
"pid": processID,
|
|
|
|
"activity": activity,
|
|
|
|
},
|
2022-05-10 21:57:06 +00:00
|
|
|
"nonce": "{0:.2f}".format(time.time()),
|
2022-05-10 20:23:12 +00:00
|
|
|
}
|
|
|
|
self.write(1, payload)
|
|
|
|
self.loop.run_until_complete(self.read())
|