This commit is contained in:
Choco
2023-03-17 12:37:35 +08:00
parent 59d864a9dc
commit 1e0a8314cb
7 changed files with 323 additions and 79 deletions

48
main.py
View File

@@ -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)

View File

@@ -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
View File

@@ -0,0 +1,2 @@
from .ipc import *
from .objects import *

2
web/ipc/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .server import IPCServer
from .client import IPCClient

41
web/ipc/client.py Normal file
View 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
View 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
View 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)