parent
9139adef70
commit
459496d198
@ -1,2 +1,3 @@ |
|||||||
*.pyc |
*.pyc |
||||||
__pycache__ |
__pycache__ |
||||||
|
.vscode |
@ -1,2 +1,16 @@ |
|||||||
# modular-discord-bot-fw |
# modular-discord-bot-fw |
||||||
framework for discord bots that uses contained files for individual feature plugins that can be modified and reloaded on the fly |
framework for discord bots that uses contained files for individual feature plugins that can be modified and reloaded on the fly |
||||||
|
|
||||||
|
basic structure: |
||||||
|
* client class has implementations for handling opcodes |
||||||
|
* event response is up to the client (!!) |
||||||
|
* this lets us expose events for plugins to hook into, but keeps core API interaction defined in-class |
||||||
|
* any and all bot features should be useable via independent feature plugins |
||||||
|
* these are dynamically found and run, so they can be hotloaded, modified, etc. without disconnecting or restarting the whole bot session |
||||||
|
|
||||||
|
todo before it's basically finished: |
||||||
|
* basic rate limiting |
||||||
|
* plugin manager & hook integration |
||||||
|
* make sure broken sockets are properly addressed with a resume or reconnect |
||||||
|
* default plugins so the bot does _something_ out of the box |
||||||
|
* plugin reloading, user (command) authentication, etc. will also probably be written in plugin form |
@ -0,0 +1,48 @@ |
|||||||
|
import websockets, asyncio, json |
||||||
|
from random import randint |
||||||
|
import disc_api |
||||||
|
from grim_logger import glog, glog_level |
||||||
|
class client(): |
||||||
|
def __init__(self, token): |
||||||
|
self.logger = glog(level = glog_level.TRACE) |
||||||
|
self.ws = None |
||||||
|
self.last_sequence = None |
||||||
|
self.session_id = None |
||||||
|
self.token = token |
||||||
|
self.user = None |
||||||
|
async def shit(self): |
||||||
|
# https://discord.com/developers/docs/topics/gateway#connecting-to-the-gateway |
||||||
|
# you're supposed to ask an HTTP endpoint what the ws gateway is but the docs didnt tell me the domain after 1sec so HAHA NOPE |
||||||
|
async with websockets.connect("wss://gateway.discord.gg/?encoding=json&v=9") as self.ws:# they seem to be fond of making new versions randomly and still supporting old ones? |
||||||
|
while True: # they may or may not drop support for this soon # nevermind we're on v9 now |
||||||
|
data = json.loads(await self.ws.recv()) |
||||||
|
handler = getattr(self, "op_"+disc_api.gateway_opcodes[data["op"]], None) |
||||||
|
self.logger.write(data) |
||||||
|
self.last_sequence = data["s"] |
||||||
|
if handler: |
||||||
|
await handler(data) |
||||||
|
async def heartbeat(self, interval): |
||||||
|
while True: |
||||||
|
self.logger.write(interval/1000) |
||||||
|
await self.ws.send(json.dumps({"op":1, "d":self.last_sequence})) |
||||||
|
await asyncio.sleep(interval/1000) |
||||||
|
self.logger.write("sent heartbeat") |
||||||
|
async def op_hello(self, res): |
||||||
|
if not(self.last_sequence and self.session_id): |
||||||
|
interval = res["d"]["heartbeat_interval"] |
||||||
|
asyncio.get_event_loop().create_task(self.heartbeat(interval)) |
||||||
|
await asyncio.sleep(1) |
||||||
|
# https://discord.com/developers/docs/topics/gateway#identify-example-identify |
||||||
|
await self.ws.send(json.dumps({"op":2, "d":{"token":self.token, "intents": 513, "properties":{"$os":"linux", "browser":"mdbf", "device":"mdbf"}}})) |
||||||
|
else: |
||||||
|
# https://discord.com/developers/docs/topics/gateway#identify-example-identify |
||||||
|
await self.ws.send(json.dumps({"op":6, "d":{"token":self.token, "session_id":self.session_id, "seq":self.last_sequence}})) |
||||||
|
async def op_invalid_session(self, res): |
||||||
|
await asyncio.sleep(randint(1, 5)) # try again |
||||||
|
await self.ws.send(json.dumps({"op":2, "d":{"token":self.token, "intents": 513, "properties":{"$os":"linux", "browser":"mdbf", "device":"mdbf"}}})) |
||||||
|
async def op_dispatch(self, res): |
||||||
|
if res["t"] == "READY": |
||||||
|
self.session_id = res["d"]["session_id"] |
||||||
|
self.user = res["d"]["user"] |
||||||
|
def run(self): |
||||||
|
asyncio.run(self.shit()) |
@ -0,0 +1,26 @@ |
|||||||
|
from enum import Enum |
||||||
|
from inspect import stack |
||||||
|
from sys import stdout |
||||||
|
from datetime import datetime |
||||||
|
class glog_flags(Enum): |
||||||
|
METHOD_PRINT = 0 |
||||||
|
METHOD_FILE = 1 |
||||||
|
class glog_level(Enum): |
||||||
|
PLAIN = 0 |
||||||
|
TRACE = 1 |
||||||
|
class glog(): |
||||||
|
def __init__(self, flags = [glog_flags.METHOD_PRINT], filename = "bot.log", level = glog_level.PLAIN): |
||||||
|
if not flags: raise ValueError("no glog_flags given!") |
||||||
|
self.targets = [] |
||||||
|
if glog_flags.METHOD_PRINT in flags: |
||||||
|
self.targets.append(stdout) |
||||||
|
if glog_flags.METHOD_FILE in flags: |
||||||
|
self.targets.append(open(filename, "a")) |
||||||
|
self.level = level |
||||||
|
def __del__(self): |
||||||
|
for target in self.targets: |
||||||
|
if not(target == stdout): |
||||||
|
target.close() |
||||||
|
def write(self, message): |
||||||
|
for target in self.targets: |
||||||
|
target.write("({2})@[{0}]: {1}\n".format(stack()[1].function if self.level == glog_level.TRACE else "glog", message, datetime.now())) |
@ -1,15 +1,3 @@ |
|||||||
import websockets, asyncio, json |
from client import client |
||||||
import disc_api |
olive = client("OTIxMDg0ODA3Mjk4MDU2MjAz.YbtxEg.NxS1h63qKDeTn_4voc2Axa_QIFI") |
||||||
class client(): |
olive.run() |
||||||
def __init__(self): |
|
||||||
pass |
|
||||||
async def shit(): |
|
||||||
# https://discord.com/developers/docs/topics/gateway#connecting-to-the-gateway |
|
||||||
# you're supposed to ask an HTTP endpoint what the ws gateway is but the docs didnt tell me the domain after 1sec so HAHA NOPE |
|
||||||
async with websockets.connect("wss://gateway.discord.gg/?encoding=json&v=9") as ws: # they seem to be fond of making new versions randomly and still supporting old ones? |
|
||||||
while True: # they may or may not drop support for this soon |
|
||||||
data = (await ws.recv()) |
|
||||||
print(data) |
|
||||||
break |
|
||||||
def run(): |
|
||||||
asyncio.run(shit()) |
|
Loading…
Reference in new issue