diff --git a/Suhail/cogs/clear.py b/Suhail/cogs/clear.py new file mode 100644 index 0000000..26a106f --- /dev/null +++ b/Suhail/cogs/clear.py @@ -0,0 +1,16 @@ +import discord +from discord.ext import commands + +class Clear(commands.Cog): + + def __init__(self, bot): self.bot = bot + + @commands.command() + async def clear(self, ctx, num: int): + try: + await ctx.channel.purge(limit=num) + except discord.Forbidden: + await ctx.send("Oops i can't do that rip") + +def setup(bot): + bot.add_cog(Clear(bot)) \ No newline at end of file diff --git a/Suhail/cogs/error.py b/Suhail/cogs/error.py new file mode 100644 index 0000000..7d2a439 --- /dev/null +++ b/Suhail/cogs/error.py @@ -0,0 +1,20 @@ +from inspect import trace +import discord +from discord.ext import commands +import traceback + +class Error(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + @commands.Cog.listener() + async def on_command_error(self, ctx, error): + if isinstance(error, commands.BadArgument): + await ctx.send("I only accept numbers smh") + else: + traceback.print_exception(type(error), error, error.__traceback__) + + +def setup(bot): + bot.add_cog(Error(bot)) diff --git a/Suhail/cogs/hi.py b/Suhail/cogs/hi.py new file mode 100644 index 0000000..a7d39a6 --- /dev/null +++ b/Suhail/cogs/hi.py @@ -0,0 +1,31 @@ +import discord +from discord.ext import commands + +class Hi(commands.Cog): + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command() + async def ping(self, ctx): + return await ctx.send(f"Pong! :ping_pong: Latency: {round(self.bot.latency*1000, 2)}ms") + + @commands.command() + async def hi(self, ctx, name = None): + name = name or ctx.author.display_name + await ctx.send(f"hello {name}!") + + @commands.command() + async def greet(self, ctx): + await ctx.author.send("Private Greeting oooooooooo") + + @commands.command() + async def sort(self, ctx, *args): + await ctx.send("\n".join(sorted(args))) + + @commands.command() + async def logo(self, ctx): + await ctx.send(file=discord.File("logo.png")) + +def setup(bot): + bot.add_cog(Hi(bot)) \ No newline at end of file diff --git a/Suhail/cogs/random.py b/Suhail/cogs/random.py new file mode 100644 index 0000000..6b56ac9 --- /dev/null +++ b/Suhail/cogs/random.py @@ -0,0 +1,33 @@ +import aiohttp +import discord +import random +from discord.ext import commands + +class Random(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def flip(self, ctx, count: int): + if count < 0: return await ctx.send("Can't flip a negative number of coins") + if count == 0: return await ctx.send("Well... you flipped nothing so... Nothing") + await ctx.send(" ".join(random.choice(["H", "T"]) for x in range(count))) + + @commands.command() + async def roll(self, ctx, count: int): + print("HERE") + if count < 0: return await ctx.send("Can't roll a negative number of dice") + if count == 0: return await ctx.send("Well... you rolled nothing so... Nothing") + + await ctx.send(" ".join(str(random.randint(1, 6)) for x in range(count))) + + @commands.command() + async def cat(self, ctx): + async with aiohttp.ClientSession() as session: + async with session.get("https://api.thecatapi.com/v1/images/search") as resp: + data = await resp.json() + return await ctx.send(data[0]["url"]) + +def setup(bot): + bot.add_cog(Random(bot)) \ No newline at end of file diff --git a/Suhail/cogs/twitter.py b/Suhail/cogs/twitter.py new file mode 100644 index 0000000..ed1f756 --- /dev/null +++ b/Suhail/cogs/twitter.py @@ -0,0 +1,93 @@ +import aiohttp +import os +from dateutil import parser +import discord +from discord.ext import commands + +class Twitter(commands.Cog): + + def __init__(self, bot): + self.bot = bot + self.token = os.environ["TWITTER_TOKEN"] + self.headers = { + "Authorization": f"Bearer {self.token}" + } + + @commands.group(invoke_without_command=True) + async def twitter(self, ctx: commands.Context): + return await ctx.send_help("twitter") + + @twitter.command() + async def lasttweet(self, ctx, username: str): + tdata, error = await self.get_user_by_name(username) + if error: + return await ctx.send(tdata["errors"][0]["detail"]) + tweet, metrics, timestamp = await self.get_latest_tweet(tdata["data"]["id"]) + + timestamp = parser.parse(timestamp) + + embed = discord.Embed( + title=f"{username}'s last tweet", + description = tweet, + color=0x00ff00, + timestamp = timestamp, + ) + + embed.add_field(name="Likes", value=metrics["like_count"]) + embed.add_field(name="RTs", value=metrics["retweet_count"]) + embed.add_field(name="Quote Tweets", value=metrics["quote_count"]) + embed.add_field(name="Replies", value=metrics["reply_count"]) + + embed.set_footer(text="Created at") + + + await ctx.send(embed=embed) + + @twitter.command() + async def userinfo(self, ctx, username: str): + + tdata, error = await self.get_user_by_name(username) + if error: + return await ctx.send(tdata["errors"][0]["detail"]) + + embed = discord.Embed( + title=f"@{username}", + timestamp = parser.parse(tdata["data"]["created_at"]), + color = 0x00ff00 + ) + embed.add_field(name="Name",value=tdata["data"]["name"], inline=False) + embed.add_field(name="Bio",value=tdata["data"]["description"], inline=False) + embed.add_field(name="Followers", value=tdata["data"]["public_metrics"]["followers_count"]) + embed.add_field(name="Following", value=tdata["data"]["public_metrics"]["following_count"]) + embed.add_field(name="Tweets", value=tdata["data"]["public_metrics"]["tweet_count"]) + embed.set_footer(text="Created at") + embed.set_thumbnail(url=tdata["data"]["profile_image_url"]) + + await ctx.send(embed=embed) + + + async def get_user_by_name(self, username: str): + url = f"https://api.twitter.com/2/users/by/username/{username}?user.fields=created_at,name,description,public_metrics,profile_image_url" + + async with aiohttp.ClientSession(raise_for_status=True) as session: + async with session.get(url, headers=self.headers) as resp: + data = await resp.json() + + return data, "errors" in data + + async def get_latest_tweet(self, tid: str): + url = f"https://api.twitter.com/2/users/{tid}/tweets?tweet.fields=public_metrics,created_at,text" + + async with aiohttp.ClientSession(raise_for_status=True) as session: + async with session.get(url, headers=self.headers) as resp: + data = await resp.json() + + last_tweet = data["meta"]["newest_id"] + tweet = [x for x in data["data"] if x["id"] == last_tweet] + + return tweet[0]["text"], tweet[0]["public_metrics"], tweet[0]["created_at"] + + + +def setup(bot): + bot.add_cog(Twitter(bot)) \ No newline at end of file diff --git a/Suhail/logo.png b/Suhail/logo.png new file mode 100644 index 0000000..bfef448 Binary files /dev/null and b/Suhail/logo.png differ diff --git a/Suhail/main.py b/Suhail/main.py new file mode 100644 index 0000000..8049e64 --- /dev/null +++ b/Suhail/main.py @@ -0,0 +1,25 @@ +import os +import discord +from discord.ext import commands +from dotenv import load_dotenv + +load_dotenv() + +class Bot(commands.Bot): + + def __init__(self, command_prefix): + super().__init__(command_prefix) + self.load_extension("cogs.hi") + self.load_extension("cogs.clear") + self.load_extension("cogs.error") + self.load_extension("cogs.random") + self.load_extension("cogs.twitter") + + async def on_ready(self): + print(f"READY! Loaded {self.user}") + + +bot = Bot(command_prefix="!") + +if __name__ == "__main__": + bot.run(os.environ["TOKEN"]) \ No newline at end of file diff --git a/Suhail/requirements.txt b/Suhail/requirements.txt new file mode 100644 index 0000000..8dd7936 --- /dev/null +++ b/Suhail/requirements.txt @@ -0,0 +1,2 @@ +discord.py==1.7.3 +python-dotenv \ No newline at end of file diff --git a/Suhail/tests/test_hi.py b/Suhail/tests/test_hi.py new file mode 100644 index 0000000..6f5dbb4 --- /dev/null +++ b/Suhail/tests/test_hi.py @@ -0,0 +1,45 @@ +import asyncio +import pytest +import discord +from discord.ext import commands, test as dpytest + +from cogs import hi +@pytest.fixture +async def bot(event_loop): + + intents = discord.Intents.default() + intents.members = True + + b = commands.Bot(command_prefix="!", loop=event_loop, intents=intents) + dpytest.configure(b) + return b + +@pytest.fixture(autouse=True) +def hi_cog(bot: commands.Bot): + hi_cog = hi.Hi(bot) + bot.add_cog(hi_cog) + dpytest.configure(bot) + return hi_cog + + +@pytest.mark.asyncio +async def test_hi(): + await dpytest.message("!hi suhail") + assert dpytest.verify().message().content("hello suhail!") + +@pytest.mark.asyncio +async def test_logo(): + await dpytest.message("!logo") + assert dpytest.verify().message().attachment("logo.png") + +@pytest.mark.asyncio +async def test_ping(): + await dpytest.message("!ping") + assert dpytest.verify().message().contains().content("Pong!") + + +@pytest.mark.parametrize("test_input,expected", [("insist establish arm brown understand", "arm\nbrown\nestablish\ninsist\nunderstand"), ("tension issue breakdown claim old", "breakdown\nclaim\nissue\nold\ntension")]) +@pytest.mark.asyncio +async def test_sort(test_input, expected): + await dpytest.message(f"!sort {test_input}") + assert dpytest.verify().message().content(expected)