Add IPC
This commit is contained in:
48
main.py
48
main.py
@@ -1,9 +1,12 @@
|
||||
import discord
|
||||
import sys, os, traceback, aiohttp
|
||||
import update
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import aiohttp
|
||||
import function as func
|
||||
|
||||
from discord.ext import commands
|
||||
from web import IPCServer
|
||||
from dotenv import load_dotenv
|
||||
from datetime import datetime
|
||||
from voicelink import VoicelinkException
|
||||
@@ -14,26 +17,30 @@ func.settings_setup()
|
||||
class Translator(discord.app_commands.Translator):
|
||||
async def load(self):
|
||||
print("Loaded Translator")
|
||||
|
||||
|
||||
async def unload(self):
|
||||
print("Unload Translator")
|
||||
|
||||
|
||||
async def translate(self, string: discord.app_commands.locale_str, locale: discord.Locale, context: discord.app_commands.TranslationContext):
|
||||
if str(locale) in func.local_langs:
|
||||
return func.local_langs[str(locale)].get(string.message, None)
|
||||
return None
|
||||
|
||||
class Vocard(commands.Bot):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.ipc = IPCServer(self, sercet_key="Vocard")
|
||||
|
||||
async def on_message(self, message: discord.Message, /) -> None:
|
||||
if message.author.bot or not message.guild:
|
||||
return False
|
||||
|
||||
|
||||
if self.user.mentioned_in(message) and not message.mention_everyone:
|
||||
await message.channel.send(f"My prefix is `{await self.command_prefix(self, message)}`")
|
||||
|
||||
await self.process_commands(message)
|
||||
|
||||
|
||||
async def setup_hook(self):
|
||||
func.langs_setup()
|
||||
for module in os.listdir(func.root_dir + '/cogs'):
|
||||
@@ -44,6 +51,8 @@ class Vocard(commands.Bot):
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
|
||||
await self.ipc.start()
|
||||
|
||||
await bot.tree.set_translator(Translator())
|
||||
await bot.tree.sync()
|
||||
|
||||
@@ -60,7 +69,7 @@ class Vocard(commands.Bot):
|
||||
error = getattr(exception, 'original', exception)
|
||||
if isinstance(error, (commands.CommandNotFound, aiohttp.client_exceptions.ClientOSError)):
|
||||
return
|
||||
|
||||
|
||||
elif isinstance(error, (commands.CommandOnCooldown, commands.MissingPermissions, commands.RangeError, commands.BadArgument)):
|
||||
pass
|
||||
|
||||
@@ -68,25 +77,25 @@ class Vocard(commands.Bot):
|
||||
command = f" Correct Usage: {ctx.prefix}" + (f"{ctx.command.parent.qualified_name} " if ctx.command.parent else "") + f"{ctx.command.name} {ctx.command.signature}"
|
||||
position = command.find(f"<{ctx.current_parameter.name}>") + 1
|
||||
error = f"```css\n[You are missing argument!]\n{command}\n" + " " * position + "^" * len(ctx.current_parameter.name) + "```"
|
||||
|
||||
|
||||
elif not issubclass(error.__class__, VoicelinkException):
|
||||
error = function.get_lang(ctx.guild.id, "unknownException") + function.invite_link
|
||||
if (guildId := ctx.guild.id) not in function.error_log:
|
||||
function.error_log[guildId] = {}
|
||||
function.error_log[guildId][round(datetime.timestamp(datetime.now()))] = str(traceback.format_exc())
|
||||
|
||||
error = func.get_lang(ctx.guild.id, "unknownException") + func.invite_link
|
||||
if (guildId := ctx.guild.id) not in func.error_log:
|
||||
func.error_log[guildId] = {}
|
||||
func.error_log[guildId][round(datetime.timestamp(datetime.now()))] = str(traceback.format_exc())
|
||||
|
||||
try:
|
||||
return await ctx.reply(error, ephemeral=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
class CommandCheck(discord.app_commands.CommandTree):
|
||||
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction, /) -> bool:
|
||||
if not interaction.guild:
|
||||
await interaction.response.send_message("This command can only be used in guilds!")
|
||||
return False
|
||||
|
||||
|
||||
return await super().interaction_check(interaction)
|
||||
|
||||
async def get_prefix(bot, message: discord.Message):
|
||||
@@ -118,10 +127,10 @@ async def app_command_error(interaction: discord.Interaction, error):
|
||||
elif isinstance(error, (discord.app_commands.CommandOnCooldown, discord.app_commands.errors.MissingPermissions)):
|
||||
pass
|
||||
elif not issubclass(error.__class__, VoicelinkException):
|
||||
error = function.get_lang(interaction.guild_id, "unknownException") + function.invite_link
|
||||
if (guildId := interaction.guild_id) not in function.error_log:
|
||||
function.error_log[guildId] = {}
|
||||
function.error_log[guildId][round(datetime.timestamp(datetime.now()))] = str(traceback.format_exc())
|
||||
error = func.get_lang(interaction.guild_id, "unknownException") + func.invite_link
|
||||
if (guildId := interaction.guild_id) not in func.error_log:
|
||||
func.error_log[guildId] = {}
|
||||
func.error_log[guildId][round(datetime.timestamp(datetime.now()))] = str(traceback.format_exc())
|
||||
try:
|
||||
if interaction.response.is_done():
|
||||
return await interaction.followup.send(error, ephemeral=True)
|
||||
@@ -131,5 +140,4 @@ async def app_command_error(interaction: discord.Interaction, error):
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
update.checkVersion(withMsg=True)
|
||||
bot.run(os.getenv("TOKEN"), log_handler=None)
|
||||
59
update.py
59
update.py
@@ -1,59 +0,0 @@
|
||||
import requests, zipfile, os, sys, shutil, traceback
|
||||
from io import BytesIO
|
||||
|
||||
root_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
install_pack_dir = os.path.join(root_dir, "Vocard.zip")
|
||||
__version__ = "v2.5.7"
|
||||
|
||||
def checkVersion(withMsg = False):
|
||||
resp = requests.get("https://api.github.com/repos/ChocoMeow/Vocard/releases/latest")
|
||||
version = resp.json().get("name", __version__)
|
||||
if withMsg:
|
||||
if version == __version__:
|
||||
print(f"Your bot is up-to-date! - {version}")
|
||||
else:
|
||||
print(f"Your bot is not up-to-date! This latest version is {version} and you are currently running version {__version__}\n. Run `python update.py --start` to update your bot!")
|
||||
return version
|
||||
|
||||
def downloadFile(version:str = None):
|
||||
if not version:
|
||||
version = checkVersion()
|
||||
print("Downloading Vocard version: " + version)
|
||||
response = requests.get("https://github.com/ChocoMeow/Vocard/archive/" + version + ".zip")
|
||||
print("Download Completed")
|
||||
unZip(response, version)
|
||||
|
||||
def unZip(response, version: str):
|
||||
print("Installing ...")
|
||||
zfile = zipfile.ZipFile(BytesIO(response.content))
|
||||
zfile.extractall(root_dir)
|
||||
|
||||
version = version.replace("v", "")
|
||||
source_dir = os.path.join(root_dir, f"Vocard-{version}")
|
||||
if os.path.exists(source_dir):
|
||||
for filename in os.listdir(root_dir):
|
||||
if filename in ["settings.json", ".env", f"Vocard-{version}"]:
|
||||
continue
|
||||
filename = os.path.join(root_dir, filename)
|
||||
if os.path.isdir(filename):
|
||||
shutil.rmtree(filename)
|
||||
else:
|
||||
os.remove(filename)
|
||||
for filename in os.listdir(source_dir):
|
||||
shutil.move(os.path.join(source_dir, filename), os.path.join(root_dir, filename))
|
||||
os.rmdir(source_dir)
|
||||
|
||||
def start():
|
||||
try:
|
||||
downloadFile()
|
||||
if os.path.exists(install_pack_dir):
|
||||
os.remove(install_pack_dir)
|
||||
print("Update Successfully! Run `python main.py` to start your bot")
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
|
||||
if "--start" in sys.argv:
|
||||
start()
|
||||
|
||||
if "--check" in sys.argv:
|
||||
checkVersion(withMsg = True)
|
||||
2
web/__init__.py
Normal file
2
web/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .ipc import *
|
||||
from .objects import *
|
||||
2
web/ipc/__init__.py
Normal file
2
web/ipc/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .server import IPCServer
|
||||
from .client import IPCClient
|
||||
41
web/ipc/client.py
Normal file
41
web/ipc/client.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import websockets, json
|
||||
from uuid import uuid4
|
||||
|
||||
class IPCClient:
|
||||
def __init__(self,
|
||||
host = "127.0.0.1",
|
||||
port = 8000,
|
||||
secret_key = None,
|
||||
callback = None):
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.websocket = None
|
||||
self.secret_key = secret_key
|
||||
self.callback = callback
|
||||
self.id = uuid4()
|
||||
|
||||
async def connect(self):
|
||||
self.websocket = await websockets.connect(f"ws://{self.host}:{self.port}", extra_headers={"Client-Id": str(self.id)})
|
||||
await self.start_receiver()
|
||||
|
||||
async def send(self, message, user):
|
||||
if not self.websocket:
|
||||
await self.connect()
|
||||
data = json.loads(message)
|
||||
|
||||
payload = {
|
||||
"sercet": self.secret_key,
|
||||
"data": data | {"user_id": user.id, "guild_id": user.guild_id}
|
||||
}
|
||||
|
||||
await self.websocket.send(json.dumps(payload))
|
||||
|
||||
async def receive(self):
|
||||
async for message in self.websocket:
|
||||
if self.callback:
|
||||
data = json.loads(message)
|
||||
self.callback(data)
|
||||
|
||||
async def start_receiver(self):
|
||||
await self.receive()
|
||||
183
web/ipc/methods.py
Normal file
183
web/ipc/methods.py
Normal file
@@ -0,0 +1,183 @@
|
||||
import json
|
||||
import voicelink
|
||||
|
||||
from discord import Member
|
||||
from discord.ext import commands
|
||||
|
||||
def missingPermission(user_id:int):
|
||||
payload = {"op": "missingPermission", "msg": "Only the DJ or admins may use this funciton!"}
|
||||
payload["user_id"] = user_id
|
||||
return payload
|
||||
|
||||
async def initPlayer(player, member: Member, data: dict):
|
||||
return {
|
||||
"op": "initPlayer",
|
||||
"guild_id": player.guild.id,
|
||||
"user_id": data.get("user_id"),
|
||||
"users": [{
|
||||
"user_id": member.id,
|
||||
"avatar_url": member.avatar.url,
|
||||
"name": member.name
|
||||
} for member in player.channel.members ],
|
||||
"tracks": [ track.toDict() for track in player.queue._queue ],
|
||||
"current_queue_position": player.queue._position if player.is_playing else player.queue._position + 1,
|
||||
"current_position": 0 or player.position if player.is_playing else 0,
|
||||
"is_playing": player.is_playing,
|
||||
"is_paused": player.is_paused
|
||||
}
|
||||
|
||||
async def skipTo(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
index = data.get("index", 1)
|
||||
if index > 1:
|
||||
player.queue.skipto(index)
|
||||
|
||||
if player.queue._repeat == 1:
|
||||
player.queue.set_repeat("off")
|
||||
await player.stop()
|
||||
|
||||
async def backTo(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
index = data.get("index", 1)
|
||||
if not player.is_playing:
|
||||
player.queue.backto(index)
|
||||
await player.do_next()
|
||||
else:
|
||||
player.queue.backto(index + 1)
|
||||
await player.stop()
|
||||
|
||||
async def moveTrack(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
c = player.queue._position - 1
|
||||
position = data.get("position")
|
||||
new_position = data.get("newPosition")
|
||||
|
||||
moveItem = player.queue._queue[position]
|
||||
player.queue._queue.remove(moveItem)
|
||||
player.queue._queue.insert(new_position, moveItem)
|
||||
return {
|
||||
"op": "moveTrack",
|
||||
"position": {
|
||||
"index": position - c,
|
||||
"track_id": moveItem.track_id
|
||||
},
|
||||
"newPosition": {
|
||||
"index": new_position - c
|
||||
},
|
||||
"guild_id": player.guild.id,
|
||||
"requester_id": member.id,
|
||||
"skip_users": [member.id]
|
||||
}
|
||||
|
||||
async def addTracks(player, member: Member, data: dict):
|
||||
raw_tracks = data.get("tracks", [])
|
||||
tracks = [voicelink.Track(
|
||||
track_id=track["track_id"],
|
||||
info=track["info"],
|
||||
requester=member
|
||||
) for track in raw_tracks]
|
||||
|
||||
await player.add_track(tracks)
|
||||
|
||||
if not player.is_playing:
|
||||
await player.do_next()
|
||||
|
||||
async def getTracks(player, member: Member, data: dict):
|
||||
query = data.get("query", None)
|
||||
if query:
|
||||
payload = {"op": "getTracks", "user_id": member.id}
|
||||
tracks = await player.get_tracks(query, requester=member)
|
||||
if not tracks:
|
||||
return payload
|
||||
|
||||
payload["tracks"] = [ track.toDict() for track in tracks ]
|
||||
return payload
|
||||
|
||||
async def shuffleTrack(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
await player.shuffle(data.get("type", "queue"), member)
|
||||
|
||||
async def updateRepeatMode(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
mode = data.get("mode", "off")
|
||||
if mode.lower() not in ['off', 'track', 'queue']:
|
||||
mode = "off"
|
||||
player.queue.set_repeat(mode)
|
||||
|
||||
async def updatePause(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
pause = data.get("pause", True)
|
||||
if pause:
|
||||
player.pause_votes.clear()
|
||||
else:
|
||||
player.resume_votes.clear()
|
||||
await player.set_pause(pause, member)
|
||||
|
||||
async def updatePosition(player, member: Member, data: dict):
|
||||
if not player.is_privileged(member):
|
||||
return missingPermission(member.id)
|
||||
|
||||
position = data.get("position");
|
||||
await player.seek(position, member);
|
||||
|
||||
methods = {
|
||||
"initPlayer": initPlayer,
|
||||
"skipTo": skipTo,
|
||||
"backTo": backTo,
|
||||
"moveTrack": moveTrack,
|
||||
"addTracks": addTracks,
|
||||
"getTracks": getTracks,
|
||||
"shuffleTrack": shuffleTrack,
|
||||
"updateRepeat": updateRepeatMode,
|
||||
"updatePause": updatePause,
|
||||
"updatePosition": updatePosition,
|
||||
}
|
||||
|
||||
async def process_methods(websocket, bot: commands.Bot, data: dict) -> None:
|
||||
method = methods.get(data.get("op", ""), None)
|
||||
if not method:
|
||||
return
|
||||
|
||||
guild = None
|
||||
member = None
|
||||
guild_id = data.get("guild_id", None)
|
||||
user_id = data.get("user_id", None)
|
||||
if guild_id is None:
|
||||
user = bot.get_user(user_id)
|
||||
if not user:
|
||||
return
|
||||
|
||||
for g in user.mutual_guilds:
|
||||
m = g.get_member(user.id)
|
||||
if m.voice and m.voice.channel:
|
||||
guild = g
|
||||
member = m
|
||||
|
||||
else:
|
||||
guild = bot.get_guild(guild_id)
|
||||
member = guild.get_member(user_id)
|
||||
|
||||
if not guild:
|
||||
return
|
||||
|
||||
player = guild.voice_client
|
||||
if not player or not Member:
|
||||
return
|
||||
try:
|
||||
resp: dict = await method(player, member, data)
|
||||
if resp:
|
||||
await websocket.send(json.dumps(resp))
|
||||
except:
|
||||
return
|
||||
67
web/ipc/server.py
Normal file
67
web/ipc/server.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import json
|
||||
|
||||
from websockets.server import serve, WebSocketServerProtocol
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
from discord.ext import commands
|
||||
|
||||
from typing import (
|
||||
Optional,
|
||||
Dict,
|
||||
Any
|
||||
)
|
||||
|
||||
class IPCServer:
|
||||
def __init__(
|
||||
self,
|
||||
bot: commands.Bot,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 8000,
|
||||
sercet_key: Optional[str] = None
|
||||
):
|
||||
self.bot = bot
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.sercet_key = sercet_key
|
||||
|
||||
self.user = {}
|
||||
self.connections = set()
|
||||
|
||||
def is_secure(self, data: dict) -> bool:
|
||||
if (key := data.get("sercet")):
|
||||
return str(key) == str(self.sercet_key)
|
||||
return bool(self.sercet_key is None)
|
||||
|
||||
async def start(self):
|
||||
try:
|
||||
print("Starting IPC")
|
||||
await serve(self.handle_ipc_connection, self.host, self.port)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
async def handle_ipc_connection(self, websocket: WebSocketServerProtocol):
|
||||
from .methods import process_methods
|
||||
|
||||
client_id = websocket.request_headers.get("Client-ID", None)
|
||||
if not client_id:
|
||||
return
|
||||
|
||||
self.connections.add(websocket)
|
||||
try:
|
||||
async for message in websocket:
|
||||
data: Dict[str, Any] = json.loads(message)
|
||||
if self.is_secure(data):
|
||||
action = data.get("data")
|
||||
await process_methods(websocket, self.bot, action)
|
||||
|
||||
except ConnectionClosed:
|
||||
pass
|
||||
|
||||
self.connections.remove(websocket)
|
||||
|
||||
async def send(self, payload):
|
||||
for conn in self.connections:
|
||||
try:
|
||||
await conn.send(json.dumps(payload))
|
||||
except:
|
||||
await conn.close()
|
||||
self.connections.remove(conn)
|
||||
Reference in New Issue
Block a user