diff --git a/.flake8 b/.flake8 index 0a6b35b..be37891 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] -ignore = F403 +max-line-length = 88 +ignore = B005,W503 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..06815eb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +# Taken from https://github.com/szskill/omnia +# Shameless promo?! +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/psf/black + rev: 21.12b0 + hooks: + - id: black + + - repo: https://github.com/pycqa/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear + - flake8-comprehensions + - flake8-simplify diff --git a/.replit b/.replit index 0839a57..f5d5c28 100755 --- a/.replit +++ b/.replit @@ -1,2 +1 @@ run = "python3.8 -m pip install --disable-pip-version-check -r requirements.txt && python3.8 main.py" - diff --git a/README.md b/README.md index 31be490..5368744 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # Phebe -A discord bot made by Python for the [Pythonic Hangout server](https://discord.gg/Q8kU6sB45K) + +A Discord bot made by Python for the [Pythonic Hangout server](https://discord.gg/Q8kU6sB45K) + +## Setting up a development environment + +To set up an environment for development, just do `pre-commit install` and that +will install the pre-commit configurations in `.pre-commit-config.yaml`, which +will make sure all code is clean/valid. + +## Running + +To run, simply do `python main.py`. + +## Creators + +_on GitHub_ +greyblue9, was07, szskill, Akshat-unt, Sarthak2143 diff --git a/StayAlive.py b/StayAlive.py index c9f538d..9906ee4 100755 --- a/StayAlive.py +++ b/StayAlive.py @@ -50,7 +50,7 @@ def log_request(request): for k, v in inspect.getmembers(request): try: log.info("%s=%s", k, v) - except: + except Exception: log.info("%s=%s", k, type(v).__qualname__) diff --git a/base.py b/base.py index 9dd7e5d..0bf37c5 100755 --- a/base.py +++ b/base.py @@ -1,8 +1,8 @@ +import inspect import sys from typing import Union import disnake -from disnake import Role from disnake.channel import ChannelType from disnake.embeds import Embed from disnake.ext import commands @@ -12,7 +12,8 @@ from disnake.member import Member from disnake.message import Message from disnake.user import User -# for replit + +# For repl.it disnake commands Bot @@ -26,34 +27,17 @@ def member_for(user: Union[User, Member]) -> Member: - "Return the full Member object corresponding to `user`." - print(f"member_for({user})") + """Returns the full Member object corresponding to `user`.""" + if isinstance(user, Member): return user - from __main__ import bot - - return - print(f"member_for(user={user}): looking for member with ID {user.id}") - for g in bot.guilds: - ids = [int(str(m.id)) for m in g.members] - if int(str(user.id)) in ids: - print(f"{g }{ids}") - matching = [m for m in g.members if m.id == user.id] - if matching: - return matching[0] - raise RuntimeError(f"uid {user} not found.") - - def role_ames(user: Union[User, Member]) -> dict[str, Role]: - "Return a dict mapping role names to Role objects." - return {r.name: r for r in member_for(user).roles} def setup(bot: commands.Bot): - """ - Needed for dynamic modules to work with load_extension + """Needed for dynamic modules to work with load_extension without requiring each to have a boilerplate setup() - function - """ + function""" + import builtins if bot and "bot" not in vars(builtins): @@ -62,7 +46,11 @@ def setup(bot: commands.Bot): module_name = [k for k in sys.modules.keys() if k.startswith("commands")][-1] class_name = module_name.split(".")[-1] module = sys.modules.get(module_name) - cog_cls = getattr(module, class_name) + cog_cls = next( + v + for k, v in inspect.getmembers(module) + if k.lower() == class_name.lower() and inspect.isclass(v) + ) cog_obj = cog_cls(bot) print(f"Loading extension comand: {class_name} ...", end="") bot.add_cog(cog_obj) diff --git a/commands/Ban.py b/commands/Ban.py deleted file mode 100644 index 33cb214..0000000 --- a/commands/Ban.py +++ /dev/null @@ -1,41 +0,0 @@ -from base import * - - -class Ban(commands.Cog): - def __init__(self, bot: Bot): - super().__init__() - self.bot = bot - - @commands.command() - @commands.has_permissions(ban_members=True) - async def ban(self, ctx: Context, member: Member, *, reason="No reason provided."): - """Ban a User""" - try: - await member.send( - f"You got banned from {ctx.guild.name} by {ctx.author} for reason: {reason}" - ) - except: - await ctx.send("Can't send DM to the user") - finally: - await member.ban(reason=reason) - await ctx.send(f"**{member.name}** got banned successfully!") - - @commands.command() - @commands.has_permissions( - ban_members=True, - ) - async def unban( - self, ctx: Context, member: int = 0, *, reason="No reason provided." - ): - """Unban a User""" - banned_users = await ctx.guild.bans() - - for ban_entry in banned_users: - if member == ban_entry.user.id: - ctx.guild.unban(ban_entry.user) - await ctx.guild.unban( - int(member), - reason=f"Unbanned by {ctx.author.display_name} (id={ctx.author.id})", - ) - await ctx.send(f"**{member.name}** got unbanned successfully!") - return diff --git a/commands/ban.py b/commands/ban.py new file mode 100644 index 0000000..c702df5 --- /dev/null +++ b/commands/ban.py @@ -0,0 +1,51 @@ +from base import Bot, commands, Context, Member, setup # noqa: F401 + + +class Ban(commands.Cog): + """The cog for (un)banning people""" + + def __init__(self, bot: Bot) -> None: + self.bot = bot + + @commands.command() + @commands.has_permissions(ban_members=True) + async def ban( + self, ctx: Context, member: Member, *, reason="No reason provided." + ) -> None: + """Bans a member.""" + + try: + await member.send( + f"You got banned from {ctx.guild.name} by {ctx.author} for reason: " + + reason + ) + except Exception: + await ctx.send("Can't send DM to the user") + finally: + await member.ban(reason=reason) + await ctx.send(f"**{member.name}** got banned successfully!") + + @commands.command() + @commands.has_permissions( + ban_members=True, + ) + async def unban( + self, ctx: Context, member_id: int = 0, *, reason="No reason provided." + ) -> None: + """Unbans a member.""" + banned_users = await ctx.guild.bans() + + for ban_entry in banned_users: + if member_id == ban_entry.user.id: + await ctx.guild.unban(ban_entry.user) + await ctx.guild.unban( + member_id, + reason=( + f"Unbanned by {ctx.author.display_name}" + + f" (id={ctx.author.id})" + ), + ) + + return await ctx.send( + f"Member with ID **{member_id}** got unbanned successfully!" + ) diff --git a/commands/Fact.py b/commands/fact.py similarity index 76% rename from commands/Fact.py rename to commands/fact.py index 0bf1b00..78dd4aa 100644 --- a/commands/Fact.py +++ b/commands/fact.py @@ -1,17 +1,17 @@ from disnake import Color -from base import * + +from base import Bot, commands, Embed, Context, setup # noqa: F401 from factgen import random_fact class Fact(commands.Cog): """The cog for facts.""" - def __init__(self, bot: Bot): - super().__init__() + def __init__(self, bot: Bot) -> None: self.bot = bot @commands.command() - async def fact(self, ctx): + async def fact(self, ctx: Context) -> None: """Shows you a random fact.""" text, source = random_fact() @@ -22,5 +22,6 @@ async def fact(self, ctx): url=source, color=Color.yellow(), ) + embed.set_footer(text=f"Source: {source}") await ctx.reply("Here's your random fact!", embed=embed) diff --git a/commands/Help.py b/commands/help.py similarity index 74% rename from commands/Help.py rename to commands/help.py index dfbb866..549fc19 100644 --- a/commands/Help.py +++ b/commands/help.py @@ -1,20 +1,19 @@ -from base import * -from init import Config from logging import getLogger + +import disnake from disnake.ext.commands.help import Paginator from disnake.ext.commands.core import Command -log = getLogger(__name__) - +from base import commands, Embed, Bot, setup # noqa: F401 +from init import Config -def override(func): - return func +log = getLogger(__name__) -class MyHelpCommand(commands.DefaultHelpCommand): +class PhebeHelpCommand(commands.DefaultHelpCommand): """The cog for the help command.""" - def __init__(self, **options): + def __init__(self, **options) -> None: self.clean_prefix = options.pop("prefix") self.width = options.pop("width", 80) self.indent = options.pop("indent", 2) @@ -22,37 +21,38 @@ def __init__(self, **options): self.commands_heading = options.pop("commands_heading", "Commands:") self.no_category = options.pop("no_category", "Python") self.paginator = options.pop("paginator", Paginator()) + super().__init__(**options) - self.context.bot = bot - @override - def get_command_signature(self, command): + self.context.bot = bot # noqa: F821 + + def get_command_signature(self, command) -> None: return ( f"{self.clean_prefix}" f"{command.qualified_name} " f"{command.signature}" ) - async def send_bot_help(self, mapping): + async def send_bot_help(self, mapping) -> None: embed = disnake.Embed(title="Help Panel") - for cog, commands in mapping.items(): - commands = await self.filter_commands(commands, sort=True) - command_signatures = [self.get_command_signature(c) for c in commands] + for cog, cmds in mapping.items(): + cmds = await self.filter_commands(commands, sort=True) + command_signatures = [self.get_command_signature(c) for c in cmds] if command_signatures: cog_name = getattr(cog, "qualified_name", "No Category") txt = "" - for c in commands: + for c in cmds: txt += "\n`" + self.get_command_signature(c) + "`" embed.add_field(name=cog_name, value=txt) channel = self.get_destination() await channel.send(embed=embed) - async def send_cog_help(self, cog): + async def send_cog_help(self, cog) -> None: commands: list[Command] = cog.get_commands() print(commands) return await super().send_cog_help(cog) - async def send_command_help(self, command): + async def send_command_help(self, command) -> None: embed = disnake.Embed(title=self.get_command_signature(command)) embed.add_field(name="Help", value=command.help) alias = command.aliases @@ -62,7 +62,7 @@ async def send_command_help(self, command): channel = self.get_destination() await channel.send(embed=embed) - async def send_group_help(self, group): + async def send_group_help(self, group) -> None: commands: list[Command] = group.commands print(commands) @@ -74,16 +74,14 @@ async def send_group_help(self, group): class Help(commands.Cog): - @override - def __init__(self, bot: Bot): + def __init__(self, bot: Bot) -> None: log.info("loading %s cog", type(self).__name__) log.info("original help cmd: %s", bot.help_command) self._original_help_command = bot.help_command - new_help_command = MyHelpCommand(prefix=Config.prefix) + new_help_command = PhebeHelpCommand(prefix=Config.prefix) log.info("new help cmd: %s", new_help_command) bot.help_command = new_help_command bot.help_command.cog = self - @override - def cog_unload(self): + def cog_unload(self) -> None: self.bot.help_command = self._original_help_command diff --git a/commands/Python.py b/commands/python.py similarity index 81% rename from commands/Python.py rename to commands/python.py index 9233bf0..52acecf 100644 --- a/commands/Python.py +++ b/commands/python.py @@ -1,16 +1,12 @@ import re -from contextlib import redirect_stderr, redirect_stdout -from io import StringIO from re import DOTALL, subn -from tempfile import NamedTemporaryFile -from traceback import print_exc -from types import CodeType import requests +import disnake from disnake import Color import getdoc -from base import * +from base import Bot, commands, setup # noqa: F401 from init import Formatted @@ -22,14 +18,14 @@ def __init__(self, bot: Bot) -> None: self.bot = bot type(self).__cog_name__ = "Python" - @commands.command(help="Run/Evaluate Python code", aliases=["eval"]) - async def e(self, ctx, *, source: str = ""): + @commands.command(aliases=["eval"]) + async def e(self, ctx, *, source: str = "") -> None: + """Runs/evaluates Python code.""" + if ctx.author.id == 883987539961192508: # 00001H - await ctx.reply("Sorry, Eval is not available, **for you.**") + await ctx.reply("Sorry, eval is not available **for you.**") return - result: Any - code: CodeType color: Color source = source.strip() source = subn(r"(^|\n)``?`?(?:py|)", "\\1", source, DOTALL)[0] @@ -71,7 +67,8 @@ async def e(self, ctx, *, source: str = ""): outcome = "successfully" if rs == 0 else "unsuccessfully" if not stderr and not stdout: output.append( - f"Program {outcome} completed with exit status {rs} and produced no output." + f"Program {outcome} completed with exit status {rs} and produced no" + + " output." ) else: output.append(f"Program {outcome} completed with exit status {rs}.") @@ -84,15 +81,18 @@ async def e(self, ctx, *, source: str = ""): ) ) - @commands.command(help="Get the Documentation of a python object", aliases=["doc"]) - async def d(self, ctx, symbol: str = "", nparas: int = 1): - """Get the Documentation of a python object""" + @commands.command(aliases=["doc"]) + async def d(self, ctx, symbol: str = "", nparas: int = 1) -> None: + """Gets the documentation of a Python object.""" + symbol = symbol.strip("`") if symbol == "Creds": await ctx.send( embed=disnake.Embed( title="Credits", - description="Made by `Greyblue92`, `Was'` and `Pancake`, `sz_skill`", + description=( + "Made by `Greyblue92`, `Was'` and `Pancake`, `sz_skill`" + ), ) ) elif symbol: @@ -107,8 +107,10 @@ async def d(self, ctx, symbol: str = "", nparas: int = 1): ) ) - @commands.command(help="Get the documentation of a python object", alias="pypi") - async def pypi(self, ctx, name=None): + @commands.command() + async def pypi(self, ctx, name=None) -> None: + """Retrieves information about a PyPI package.""" + if name is None: await ctx.send("Package name required.") return @@ -134,14 +136,15 @@ async def pypi(self, ctx, name=None): ) -def format(text, lines=6): +def format(text, lines=6) -> str: + """What does this do? I'm not even gonna bother writing a proper docstring for + this.""" + res = "" text = re.compile(r"\s*\s*", re.DOTALL).subn("\n\n", text)[0] text = text.replace("```python", "__start_py") - l = 0 - for line in text.splitlines(): + for line_no, line in enumerate(text.splitlines()): line = line.strip() - l += 1 if line.startswith("##"): res += f"*{line.lstrip('##').strip()}*" elif line.startswith("#"): @@ -159,7 +162,7 @@ def format(text, lines=6): else: res += line res += "\n" - if l >= lines and "__start_py" in res and "```" not in res: + if line_no >= lines and "__start_py" in res and "```" not in res: res += "```" break diff --git a/commands/Server.py b/commands/server.py similarity index 75% rename from commands/Server.py rename to commands/server.py index c660845..cc23b5e 100644 --- a/commands/Server.py +++ b/commands/server.py @@ -1,22 +1,31 @@ import socket from functools import lru_cache -from os import getcwd, getenv, uname -from os.path import expanduser -from pathlib import Path -from shlex import join +from os import uname from typing import Union -from base import * +import disnake + +from base import Bot, commands, Context, Embed, setup # noqa: F401 from init import Config +# Was this copied from PyDis? rules = [ "Be respectful, civil, and welcoming to others in the server.", "The language of this server is English, Use English to the best of your ability." "Be polite if someone speaks English imperfectly.", - "Do not misuse or spam/troll in any of the channels, Do not attempt to bypass any blocked words.", - "Do not advertise anything without permission. No Discord server invite links or codes.", - "Controversial topics such as religion or politics are not allowed (even in off topic).", - "ping someone only when there is legitimate reasoning behind them.", + ( + "Do not misuse or spam/troll in any of the channels, Do not attempt to bypass" + + " any blocked words." + ), + ( + "Do not advertise anything without permission. No Discord server invite links" + + " or codes." + ), + ( + "Controversial topics such as religion or politics are not allowed (even in off" + + " topic)." + ), + "Ping someone only when there is legitimate reasoning behind them.", "Follow the Discord Community Guidelines and Terms of Service.", ] @@ -50,7 +59,7 @@ def __init__(self, bot: Bot) -> None: self.bot = bot @commands.command() - async def rule(self, ctx: Context, number: int): + async def rule(self, ctx: Context, number: int) -> None: """Shows you the specified rule with that index.""" if not 0 < number < len(rules) + 1: @@ -63,12 +72,12 @@ async def rule(self, ctx: Context, number: int): ) @commands.command() - async def rules(self, ctx: Context): + async def rules(self, ctx: Context) -> None: """Shows you all of the rules.""" await ctx.send( embed=disnake.Embed( - title=f"Rules", + title="Rules", url=rules_channel, ) ) @@ -76,14 +85,14 @@ async def rules(self, ctx: Context): @commands.command(help="Get information about server", aliases=["serverinfo"]) async def server(self, ctx: Context) -> None: - """Shows you the info of the server.""" + """Shows you information about this server.""" guild = ctx.guild offline_members = 0 online_members = 0 - sysname, nodename, release, version, arch = uname() + sysname, _, _, _, arch = uname() for member in guild.members: if member.status == disnake.Status.offline: diff --git a/commands/Wiki.py b/commands/wiki.py similarity index 92% rename from commands/Wiki.py rename to commands/wiki.py index f27f6da..058ef3a 100755 --- a/commands/Wiki.py +++ b/commands/wiki.py @@ -2,10 +2,9 @@ from enum import Enum from functools import lru_cache from itertools import starmap -from typing import Any, Callable, NamedTuple, Optional, TypeVar, Union -from urllib.parse import ParseResult, quote, urljoin, urlparse +from typing import Callable, NamedTuple, Optional, TypeVar, Union +from urllib.parse import ParseResult, urljoin, urlparse -import bs4 import requests from bs4 import BeautifulSoup as BS from bs4 import Tag, TemplateString @@ -13,7 +12,7 @@ from furl import furl as URL from requests import get -from base import * +from base import commands, Bot, Embed, setup # noqa: F401 TFunc = TypeVar("TFunc", bound=Callable) @@ -22,6 +21,7 @@ def cache(func: TFunc) -> TFunc: return lru_cache(maxsize=0)(func) +# I just know this code was copied somewhere. - sz_skill class WikiProfile(Enum): """ Search profile to use. @@ -97,14 +97,14 @@ def search_wiki( "Referer": wiki_api_url.url, "Accept": "application/json", }, - params=dict( - action="opensearch", - format="json", - limit=max_results, - profile=profile.value, - redirects=redirects.value, - search=query, - ), + params={ + "action": "opensearch", + "format": "json", + "limit": max_results, + "profile": profile.value, + "redirects": redirects.value, + "search": query, + }, ) if resp.status_code != 200: raise Exception() @@ -132,24 +132,21 @@ def get_wiki_images( "Referer": wiki_api_url.url, "Accept": "application/json", }, - params=dict( - action="query", - format="json", - prop="images", - titles=result.title, - ), + params={ + "action": "query", + "format": "json", + "prop": "images", + "titles": result.title, + }, ) if resp.status_code != 200: raise Exception() im_results = [] - for pid, page in resp.json()["query"]["pages"].items(): - pageid = page["pageid"] - page_title = page["title"] + for _, page in resp.json()["query"]["pages"].items(): del page["ns"] images: list[dict[str, Union[int, str]]] = page["images"] for image in images: - ns = image["ns"] im_title = image["title"] del image["title"] scheme, _, filename = im_title.partition(":") @@ -301,7 +298,7 @@ def _do_wiki(self, name) -> Embed: result = best[0] else: result = results[0] - title, url = result.title, result.url + url = result.url response = requests.get(url.url) if b"may refer to" in response.content: @@ -309,7 +306,6 @@ def _do_wiki(self, name) -> Embed: links = conv.doc.select("ul > li a[title]") rel_url = links[0].attrs["href"] url = HtmlToDiscord.abs_url(conv.url, rel_url) - title = links[0].text.strip() response = requests.get(url) html = response.content diff --git a/factgen.py b/factgen.py index 5a6d5dc..af8fb52 100644 --- a/factgen.py +++ b/factgen.py @@ -4,8 +4,9 @@ from pathlib import Path -def random_fact(): +def random_fact() -> tuple[str, str]: factfile = Path("facts.json") + if not factfile.exists(): facts = requests.get( "https://randomwordgenerator.com/json/facts.json", @@ -13,14 +14,12 @@ def random_fact(): "referrerPolicy": "strict-origin-when-cross-origin", }, ).json()["data"] + factfile.write_text(json.dumps(facts)) + facts = json.loads(factfile.read_text()) fact = random.choice(facts) text = fact["fact"] source = fact["source_url"] - return text, source - -text, source = random_fact() -print(text) -print("From: " + source) + return (text, source) diff --git a/init.py b/init.py index c47ebc7..6badc21 100755 --- a/init.py +++ b/init.py @@ -1,3 +1,4 @@ +from logging import warn import os import sys from typing import Optional @@ -63,7 +64,7 @@ def load_var(self, key, default_val=sentinel): Config = Config() -class Formatted: +class Formatted: # noqa: SIM119 def __init__(self, fixed_part, formatted_paras, elem: Optional[Tag] = None): self.fixed_part = fixed_part self.formatted_paras = formatted_paras diff --git a/main.py b/main.py index 60ab0c1..084a73e 100755 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ """ Phebe -A discord bot for the Python Experts Server +A discord bot for the Pythonic Hangouts Server """ import logging import sys @@ -10,12 +10,13 @@ import random import threading import traceback -import StayAlive from pathlib import Path from threading import Thread + from disnake import Activity, ActivityType, Game -from disnake.ext import commands -from base import * + +import StayAlive +from base import Member, commands from init import Config logging.root.setLevel(logging.WARNING) @@ -28,91 +29,112 @@ class Phebe(commands.Cog): """ Official bot for the Pythonic Hangout server """ + def __init__(self, bot: commands.Bot): self.bot = bot @commands.Cog.listener() async def on_ready(self): - """show message when bot gets online""" - print("\u001b[34m" + - f"[+] Bot is running! \n[+] Ping: {self.bot.latency*1000} ms" + - "\u001b[0m") + """Shows a message when the bot gets online.""" + print( + "\u001b[34m" + + f"[+] Bot is running! \n[+] Ping: {self.bot.latency*1000} ms" + + "\u001b[0m" + ) self.bot.loop.create_task(self.status_task()) - ## XXX TODO: Migrate to commands.WordFilter + # XXX TODO: Migrate to commands.WordFilter @commands.Cog.listener() async def on_message(self, message): author: Member = message.author if author.bot: return + roles_by_name = [role.name for role in author.roles] + if "Moderation Team" in roles_by_name: return + for word in banned_words: if word in message.content.lower(): await message.delete() - await message.author.send(embed=disnake.Embed( - title="Warning", - description= - f"**{author.mention}: Your message got deleted by saying** *{word}* __that is a banned word.__", - )) + await message.author.send( + embed=disnake.Embed( + title="Warning", + description=( + f"**{author.mention}: Your message got deleted by saying**" + + f" *{word}* __that is a banned word.__", + ), + ) + ) - ## XXX TODO: Migrate to commands.Status + # XXX TODO: Migrate to commands.Status async def status_task(self): if Config.prefix == ".": return for activity in ( - Game(name=".help"), - Activity(type=ActivityType.watching, - name="Members in Servers"), - Activity(type=ActivityType.listening, - name="Moderation team command."), + Game(name=".help"), + Activity(type=ActivityType.watching, name="Members in Servers"), + Activity(type=ActivityType.listening, name="Moderation team command."), ): await self.bot.change_presence(activity=activity) await asyncio.sleep(30) - ## XXX TODO: Migrate to commands.Ping + # XXX TODO: Migrate to commands.Ping @commands.command() async def ping(self, ctx): - """Show latency in mili seconds""" - await ctx.send(embed=disnake.Embed( - title="Pong!", - description= - f"🟢 **Bot is active**\n\nšŸ•‘ **Latency: **{round(self.bot.latency*1000, 3)} ms", - )) - - ## XXX TODO: Migrate to commands.Warn + """Show latency in milliseconds""" + + await ctx.send( + embed=disnake.Embed( + title="Pong!", + description=( + "🟢 **Bot is active**\n\nšŸ•‘ **Latency:** " + + f"{round(self.bot.latency*1000, 3)} ms" + ), + ) + ) + + # XXX TODO: Migrate to commands.Warn @commands.command() async def warn(self, ctx, member: disnake.Member): - """Warn a User""" + """Warns a member.""" await ctx.send(f"{member: disnake.Member} has been warned") - ## XXX TODO: Migrate to commands.Timeout + # XXX TODO: Migrate to commands.Timeout @commands.command() async def timeout(self, ctx, time, member: disnake.Member = None): - """Timeout a User""" + """Timeouts a member.""" await member.timeout(duration=time) - ## XXX TODO: Migrate to commands.PFP + # XXX TODO: Migrate to commands.PFP @commands.command() async def pfp(self, ctx, member: disnake.Member = None): - """Show profile picture of a user, or see yours""" + """Shows the profile picture of a user, or yours""" embed = disnake.Embed( - title= - f"Profile Picture of {ctx.author.display_name if member is None else member.display_name}" + title=( + "Profile Picture of " + + f"{ctx.author.display_name if not member else member.display_name}" + ), ) - embed.set_image( - url=ctx.author.avatar if member is None else member.avatar) + embed.set_image(url=ctx.author.avatar if member is None else member.avatar) await ctx.send(embed=embed) - ## XXX TODO: Migrate to commands.Flip + # XXX TODO: Migrate to commands.Flip @commands.command() async def flip(self, ctx): - """Flip a vertual coin and get the result""" - heads_url = "https://cdn-icons.flaticon.com/png/512/5700/premium/5700963.png?token=exp=1643128433~hmac=831aba311ab86e1ef73059e55178e712" - tails_url = "https://cdn-icons.flaticon.com/png/512/2173/premium/2173470.png?token=exp=1643127144~hmac=a622b3080fe202709c7745ac894df97b" + """Flips a virtual coin and gets the result.""" + + heads_url = ( + "https://cdn-icons.flaticon.com/png/512/5700/premium/5700963.png?token=exp=" + + "1643128433~hmac=831aba311ab86e1ef73059e55178e712" + ) + tails_url = ( + "https://cdn-icons.flaticon.com/png/512/2173/premium/2173470.png?token=exp=" + + "1643127144~hmac=a622b3080fe202709c7745ac894df97b" + ) res = random.randint(1, 2) @@ -125,27 +147,32 @@ async def flip(self, ctx): await ctx.reply(embed=embed) - ## XXX TODO: Migrate to commands.Roll + # XXX TODO: Migrate to commands.Roll @commands.command() async def roll(self, ctx): """roll a virtual dice and get the result""" comp = random.randint(1, 6) - await ctx.reply(embed=disnake.Embed(title="Rolled a dice", - description=f"Result is {comp}")) + await ctx.reply( + embed=disnake.Embed(title="Rolled a dice", description=f"Result is {comp}") + ) - ## XXX TODO: Migrate to commands.Format + # XXX TODO: Migrate to commands.Format @commands.command() async def format(self, ctx): - await ctx.send(embed=disnake.Embed( - title="Code formatting", - ddescription=""" - To properly format Python code in Discord, write your code like this: + await ctx.send( + embed=disnake.Embed( + title="Code formatting", + ddescription=""" +To properly format Python code in Discord, write your code like this: \\`\\`\\`py -print("Hello world")\n\\`\\`\\`\n\n **These are backticks, not quotes**. They are often under the Escape (esc) key on most keyboard orientations, they could be towards the right side of the keyboard if you are using eastern european/balkan language keyboards. +print("Hello world")\n\\`\\`\\`\n\n **These are backticks, not quotes**. They are of- +ten under the Escape (esc) key on most keyboard orientations, they could be towards the- +right side of the keyboard if you are using eastern european/balkan language keyboards. """, - )) + ) + ) if __name__ == "__main__": @@ -161,7 +188,7 @@ async def format(self, ctx): intents=intents, help_command=None, ) - except: + except Exception: intents.presences = False bot: commands.Bot = commands.Bot( command_prefix=Config.prefix, @@ -179,12 +206,10 @@ async def format(self, ctx): try: bot.load_extension(name) except BaseException as exc: - import traceback - print( "\x0a".join( - traceback.format_exception(type(exc), exc, - exc.__traceback__)), + traceback.format_exception(type(exc), exc, exc.__traceback__) + ), file=sys.stderr, ) diff --git a/sandbox.py b/sandbox.py index 5982cfa..14d797b 100644 --- a/sandbox.py +++ b/sandbox.py @@ -1,21 +1,19 @@ +import contextlib +import subprocess +import types +import sys +from collections.abc import Mapping + + class FakeStream(__import__("io").StringIO): def fileno(self): return 1 def _t(): - import types, sys - from collections.abc import Mapping - __sys_mod = sys.modules - _ModDict__sys_mod = __sys_mod - - import subprocess def run(src): - # sio_stdout, sio_stderr = (StringIO(), StringIO()) - sio_stdout, sio_stderr = (FakeStream(), FakeStream()) - p = subprocess.run( ["timeout", "0.5s", "env", "-i", sys.executable, __file__], input=src, @@ -35,8 +33,6 @@ def run(src): traceback.print_exc() rs = p.returncode - output = sio_stdout.getvalue() - errors = sio_stderr.getvalue() return (rs, p.stdout, p.stderr) run.sys = sys @@ -126,9 +122,12 @@ def audit_hook(event, arg, *rest): if event == "os.system": if arg[0] == b"(pager) 2>/dev/null": return - elif event == "subprocess.Popen": - if arg[0] == "/bin/sh" and arg[1] == ["/bin/sh", "-c", "pager"]: - return + elif ( + event == "subprocess.Popen" + and arg[0] == "/bin/sh" + and arg[1] == ["/bin/sh", "-c", "pager"] + ): + return # print(f"{event=!r}, {arg=!r}") if event in ("sys.unraisablehook", "sys.excepthook"): @@ -177,8 +176,6 @@ def audit_hook(event, arg, *rest): global __name__ if __name__ == "__main__": - import builtins, sys - sys.addaudithook(audit_hook) code = compile(sys.stdin.read(), "", "exec") import builtins @@ -186,23 +183,12 @@ def audit_hook(event, arg, *rest): # del __import__ sys_modules = ModDict() - try: - del sys.modules["os"] - except: - pass - try: - del sys.modules["sys"] - except: - pass + + with contextlib.suppress(Exception): + for module_to_delete in ("os", "sys"): + del sys.modules[module_to_delete] + sys.modules = sys_modules - try: - del sys - except: - pass - try: - del os - except: - pass exec(code, {**builtins.__dict__, "__name__": "__maim__"}) raise SystemExit(0) @@ -211,4 +197,3 @@ def audit_hook(event, arg, *rest): (_ := _t(), globals().__delitem__("_")) -