Début de backend
Some checks failed
Gitsokyo/bot-base/pipeline/head There was a failure building this commit

This commit is contained in:
Louis Chauvet 2020-06-20 11:17:55 +02:00
parent 1a020b1fb1
commit 29d60f3cb9
Signed by: fomys
GPG Key ID: 1ECA046A9615ABA0
22 changed files with 494 additions and 25 deletions

View File

@ -6,6 +6,7 @@ name = "pypi"
[packages]
packaging = "*"
toml = "*"
websockets = "*"
"discord.py" = {version = "*",extras = ["voice",]}
[dev-packages]

4
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "267e2db73eae9033f8531715235a968fe4ddd2ca4f09ccc266d5f6da869acb23"
"sha256": "1988f2a813c0947b137f1ac7e09d95507de3ca9c6a57ebfbd333414839a281e5"
},
"pipfile-spec": 6,
"requires": {
@ -223,7 +223,7 @@
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
],
"markers": "python_full_version >= '3.6.1'",
"index": "pypi",
"version": "==8.1"
},
"yarl": {

View File

@ -0,0 +1,13 @@
from backends import abc
class Channel(abc.Channel):
id: str
async def _send(self, message):
print(f"send: {message}")
print(type(message))
await self.client.send_raw(f"PRIVMSG {self.id} :{message.content}\r\n".encode())
def from_irc_id(self, id):
self.id = id.decode()

51
src/backends/IRC/irc.py Normal file
View File

@ -0,0 +1,51 @@
import asyncio
from backends.IRC.message import Message
from backends.abc.client import Client
class IRCHandler(asyncio.Protocol):
def __init__(self, client):
self.transport = None
self.client = client
def connection_made(self, transport):
self.transport = transport
self.login()
def data_received(self, data):
print(data.decode())
if data.startswith(b"PING"):
self.transport.write(b"data".replace(b"PING", b"PONG"))
if b"PRIVMSG" in data:
message = Message(self.client)
message.from_irc(data)
self.client.dispatch("message", message)
def eof_received(self):
pass
def login(self):
self.transport.write(f"USER bot 0 * :BOTOX\r\n".encode())
self.transport.write(f"NICK {self.client.nick}\r\n".encode())
self.transport.write(f"JOIN #general\r\n".encode())
def send(self, data):
print(data)
self.transport.write(data)
class IRC(Client):
def __init__(self, server, port, password=None, nick="I_AM_A_BOT", user=None, mode=None, unused=None, realname=None, loop=asyncio.get_event_loop()):
self.server = server
self.port = port
self.password = password
self.nick = nick
self.connection_handler = IRCHandler(self)
self.loop = loop
async def run(self, loop=asyncio.get_event_loop()):
await loop.create_connection(lambda: self.connection_handler, self.server, self.port)
async def send_raw(self, data):
self.loop.run_in_executor(None, lambda: self.connection_handler.send(data))

View File

@ -0,0 +1,18 @@
from backends import abc
from backends.IRC.channel import Channel
from backends.abc import User
class Message(abc.Message):
def from_irc(self,data):
# :Fomys!~fomys@192.168.0.14 PRIVMSG #general :Pouet
message = data.split(b":")[-1]
self.content = message.decode()
channel = data.split(b" ")[2]
print(channel)
if channel.decode() == self.client.nick:
channel = data.split(b" ")[0][1:].split(b"!")[0]
self.channel = Channel(self.client)
self.channel.from_irc_id(channel)
self.author = User(self.client)

View File

@ -0,0 +1,5 @@
from .client import Client
from .message import Message
from .server import Server
from .channel import Channel
from .user import User

View File

@ -0,0 +1,15 @@
from backends.abc import Message
class Channel:
def __init__(self, client):
self.client = client
async def send(self, message):
if type(message) == str:
await self._send(Message(self.client, content=message))
return
await self._send(message)
async def _send(self, message):
pass

View File

@ -0,0 +1,12 @@
from backends.abc.status import Status
class Client:
async def run(self):
pass
async def set_status(self, status: Status):
pass
def set_dispatch_handler(self, handler):
self.dispatch = handler

View File

@ -0,0 +1,6 @@
from backends.abc import Message
class Messagable:
async def send(self, message: Message):
pass

View File

@ -0,0 +1,19 @@
from .server import Server
from .user import User
class Message:
server: Server
channel = None
author: User
content: str
timestamp: int
def __init__(self, client, server=None, channel=None, author=None, content=None, timestamp=None):
self.client = client
self.server = server
self.channel = channel
self.author = author
self.content = content
self.timestamp = timestamp

View File

@ -0,0 +1,2 @@
class Server:
pass

View File

@ -0,0 +1,36 @@
import typing
class StatusType:
ONLINE = 0
DO_NOT_DISTURB = 1
IDLE = 2
INVISIBLE = 3
OFFLINE = 4
class ActivityType:
GAME = 0
STREAMING = 1
LISTENING = 2
CUSTOM = 4
class Activity:
name: str
type: ActivityType
def __init__(self, name, activity_type):
self.name = name
self.type = activity_type
class Status:
activity: Activity
status: StatusType
afk: bool
def __init__(self, status, activity=None, afk=False):
self.activity = activity
self.status = status
self.afk = afk

5
src/backends/abc/user.py Normal file
View File

@ -0,0 +1,5 @@
class User:
bot: bool = False
def __init__(self, client):
self.client = client

View File

@ -0,0 +1,3 @@
from .discord import Discord
from .intents import Intents
from .gateway import Gateway

View File

@ -0,0 +1,20 @@
import json
import aiohttp
from .. import abc
class Channel(abc.Channel):
filled: bool = False
id: int
populated: bool
def from_discord_id(self, id_):
self.id = id_
self.populated = False
async def _send(self, message):
form = aiohttp.FormData()
form.add_field('payload_json', json.dumps({"content": message.content}))
await self.client.api_call(f"/channels/{self.id}/messages", method="POST", data=form)

View File

@ -0,0 +1,131 @@
import asyncio
import time
import aiohttp
from .message import Message
from ..abc import Client
from ..abc.status import Activity, Status, StatusType, ActivityType
from .intents import Intents
from .gateway import Gateway
class Discord(Client):
def __init__(self, token, intents=Intents.DEFAULTS, loop=asyncio.get_event_loop()):
self.token = token
self.api_root = "https://discord.com/api"
self.intents = intents
self.loop = loop
self.gateway_root = None
self.gateway = None
self.heartbeat_interval = None
self.heartbeat = None
self.dispatch = lambda *x, **y: None
self.last_seq = 0
async def api_call(self, path, method="GET", **kwargs):
headers = {
"Authorization": f"Bot {self.token}",
"User-Agent": "Bot"
}
async with aiohttp.ClientSession() as session:
async with session.request(method, self.api_root + path, headers=headers, **kwargs) as response:
try:
assert 200 <= response.status < 300
if response.status in [200, 201]:
return await response.json()
except Exception as e:
if response.status == 400:
raise Exception("Status = 400")
elif response.status == 403:
raise Exception("Status = 403")
else:
raise Exception("Chépa")
async def get_gateway_root(self):
return (await self.api_call("/gateway"))["url"]
async def run(self):
self.gateway_root = await self.get_gateway_root()
self.gateway = Gateway(self.gateway_root, loop=self.loop)
self.heartbeat = asyncio.create_task(self.__heartbeat())
async for data in self.gateway.run():
self.last_seq = data.get("s") or self.last_seq
await self.handle_receive(data)
async def handle_receive(self, data):
if data.get("op") == 0:
self.handle_event(data)
elif data.get("op") == 1:
await self.heartbeat_ack()
elif data.get("op") == 10:
self.heartbeat_interval = data.get("d", {}).get("heartbeat_interval", None)
await self.identify()
elif data.get("op") == 11:
# Ping ack
pass
else:
pass
async def set_status(self, status: Status):
await self.gateway.send({
"op": 3,
"d": self._to_discord_status(status)
})
async def identify(self):
await self.gateway.send({
"op": 2,
"d": {
"token": f"{self.token}",
"properties": {
"$os": "linux",
"$browser": "PBA",
"$device": "PBA"
},
"large_threshold": 250,
"guild_subscriptions": True,
"intents": self.intents
}
})
async def __heartbeat(self):
while True:
await asyncio.sleep((self.heartbeat_interval or 1000) / 1000)
if not self.gateway.closed:
await self.gateway.send({"op": 1, "d": self.last_seq})
async def heartbeat_ack(self):
await self.gateway.send({"op": 11})
def handle_event(self, data):
self.last_seq = data.get("s")
event_name = data.get("t")
print(f"Event: {event_name}")
if event_name == "MESSAGE_CREATE":
message = Message(self)
message.from_raw_discord(data.get("d"))
self.dispatch("message", message)
@staticmethod
def _to_discord_status(status):
data = {}
status_name = ""
if status.status == StatusType.ONLINE:
status_name = "online"
elif status.status == StatusType.DO_NOT_DISTURB:
status_name = "dnd"
elif status.status == StatusType.IDLE:
status_name = "idle"
elif status.status == StatusType.INVISIBLE:
status_name = "invisible"
elif status.status == StatusType.OFFLINE:
status_name = "offline"
data.update({"status": status_name})
data.update({"afk": status.afk})
if status.activity:
data.update(
{"game": {"name": status.activity.name, "type": status.activity.type}, "since": time.time()})
return data

View File

@ -0,0 +1,38 @@
import asyncio
import json
import traceback
import websockets
class Gateway:
websocket: websockets.WebSocketClientProtocol
def __init__(self, root, version=6, encoding="json", loop=asyncio.get_event_loop()):
self.root = root
self.version = version
self.encoding = encoding
self.loop = loop
self.websocket = None
@property
def url(self):
return f"{self.root}?v={self.version}&encoding={self.encoding}"
async def run(self):
self.websocket = await websockets.connect(self.url)
while True:
message = await self.websocket.recv()
data = json.loads(message)
yield data
async def send(self, content):
try:
if self.websocket is not None:
await self.websocket.send(json.dumps(content))
except Exception:
traceback.print_exc()
@property
def closed(self):
return self.websocket.closed

View File

@ -0,0 +1,24 @@
class Intents:
GUILDS = 1 << 0
GUILD_MEMBERS = 1 << 1
GUILD_BANS = 1 << 2
GUILD_EMOJIS = 1 << 3
GUILD_INTEGRATIONS = 1 << 4
GUILD_WEBHOOKS = 1 << 5
GUILD_INVITES = 1 << 6
GUILD_VOICE_STATES = 1 << 7
GUILD_PRESENCES = 1 << 8
GUILD_MESSAGES = 1 << 9
GUILD_MESSAGE_REACTIONS = 1 << 10
GUILD_MESSAGE_TYPING = 1 << 11
DIRECT_MESSAGES = 1 << 12
DIRECT_MESSAGE_REACTIONS = 1 << 13
DIRECT_MESSAGE_TYPING = 1 << 14
ALL = GUILDS | GUILD_MEMBERS | GUILD_BANS | GUILD_EMOJIS | GUILD_INTEGRATIONS | GUILD_WEBHOOKS | GUILD_INVITES | \
GUILD_VOICE_STATES | GUILD_PRESENCES | GUILD_MESSAGES | GUILD_MESSAGE_REACTIONS | GUILD_MESSAGE_TYPING | \
DIRECT_MESSAGES | DIRECT_MESSAGE_REACTIONS | DIRECT_MESSAGE_TYPING
DEFAULTS = GUILDS | GUILD_BANS | GUILD_EMOJIS | GUILD_INTEGRATIONS | GUILD_WEBHOOKS | GUILD_INVITES | \
GUILD_VOICE_STATES | GUILD_MESSAGES | GUILD_MESSAGE_REACTIONS | GUILD_MESSAGE_TYPING | \
DIRECT_MESSAGES | DIRECT_MESSAGE_REACTIONS | DIRECT_MESSAGE_TYPING

View File

@ -0,0 +1,52 @@
from pprint import pprint
from .channel import Channel
from .user import User
from .. import abc
class Message(abc.Message):
populated: bool = False
id: int
tts: bool
mention_everyone: bool
mentions: list
mention_roles: list
mention_channels: list
attachments: list
embeds: list
reactions: list
nonce: list
pinned: bool
webhook_id: int
type: int
activity: int
application: int
message_reference: int
flags: int
def from_raw_discord(self, data):
self.id = data.get("id")
self.author = User(self.client)
self.author.from_discord_raw(data.get("author"))
self.channel = Channel(self.client)
self.channel.from_discord_id(data.get("channel_id"))
self.content = data.get("content")
self.timestamp = data.get("timestamp")
self.tts = data.get("tts")
self.mention_everyone = data.get("mention_everyone")
self.mentions = data.get("mentions")
self.mention_roles = data.get("mention_roles")
self.mention_channels = data.get("mention_channels")
self.attachments = data.get("attachments")
self.embeds = data.get("embeds")
self.reactions = data.get("reactions")
self.nonce = data.get("nonce")
self.pinned = data.get("pinned")
self.webhook_id = data.get("webhook_id")
self.type = data.get("type")
self.activity = data.get("activity")
self.application = data.get("application")
self.message_reference = data.get("message_reference")
self.flags = data.get("flags")
self.populated = True

View File

@ -0,0 +1,12 @@
from .. import abc
class User(abc.User):
id: int
bot: bool
discriminator: str
def from_discord_raw(self, data):
self.id = data.get("id")
self.bot = data.get("bot")
self.discriminator = data.get("discriminator")

View File

@ -1,34 +1,28 @@
from __future__ import annotations
import importlib
import inspect
import asyncio
import logging
import os
import sys
import traceback
import discord
import toml
from packaging.specifiers import SpecifierSet, InvalidSpecifier
from bot_base.modules import ModuleManager
from config import Config, config_types
from config.config_types import factory
import errors
__version__ = "0.2.0"
class BotBase(discord.Client):
class BotBase():
log = None
def __init__(self, data_folder: str = "data", modules_folder: str = "modules", *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, data_folder: str = "datas", modules_folder: str = "modules", loop = asyncio.get_event_loop()):
# Create folders
os.makedirs(modules_folder, exist_ok=True)
os.makedirs(data_folder, exist_ok=True)
# Add module folder to search path
# TODO: Vérifier que ca ne casse rien
self.backends = []
# Setup logging
self.log = logging.getLogger('bot_base')
@ -46,14 +40,19 @@ class BotBase(discord.Client):
self.modules = ModuleManager(self)
self.loop = loop
self.modules.load_modules()
def is_ready(self):
return False
async def on_ready(self):
self.info("Bot ready.")
self.modules.load_modules()
def dispatch(self, event, *args, **kwargs):
"""Dispatch event"""
super().dispatch(event, *args, **kwargs)
for module in self.modules:
print(f"Dispatched: {event}\n{args}{kwargs}")
module.dispatch(event, *args, **kwargs)
async def on_error(self, event_method, *args, **kwargs):
@ -66,6 +65,7 @@ class BotBase(discord.Client):
self.dispatch("log_info", info, *args, **kwargs)
def error(self, e, *args, **kwargs):
print(e)
if self.log:
self.log.error(e, *args, **kwargs)
self.dispatch("log_error", e, *args, **kwargs)
@ -84,3 +84,12 @@ class BotBase(discord.Client):
path: config
})
return config
def register(self, backend):
self.backends.append(backend)
backend.set_dispatch_handler(self.dispatch)
def run(self):
for back in self.backends:
asyncio.ensure_future(back.run())
self.loop.run_forever()

View File

@ -4,10 +4,12 @@ import json
import logging
import os
from backends.IRC.irc import IRC
from backends.discord.discord import Discord
from bot_base.bot_base import BotBase
def setup_logging(default_path='data/log_config.json', default_level=logging.INFO, env_key='BOT_BASE_LOG_CONFIG'):
def setup_logging(default_path='datas/log_config.json', default_level=logging.INFO, env_key='BOT_BASE_LOG_CONFIG'):
"""Setup logging configuration
"""
path = default_path
@ -24,15 +26,10 @@ def setup_logging(default_path='data/log_config.json', default_level=logging.INF
def main():
setup_logging()
print(os.environ.get("LOCAL_MODULES", "modules"))
client = BotBase(max_messages=500000, data_folder="datas")
async def start_bot():
await client.start(os.environ.get("DISCORD_TOKEN"))
loop = asyncio.get_event_loop()
loop.create_task(start_bot())
loop.run_forever()
client = BotBase()
client.register(Discord("NDcwNzI4NjAzMDEzNzQyNjAy.XuSCkg.8A6DEqpDj9pghFDefp9PEHlASnc"))
client.register(IRC("192.168.0.1", 6667, "toto"))
client.run()
if __name__ == "__main__":