diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0ad1db6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.testing.pytestArgs": [ + "Zoe" + ], + "python.testing.unittestEnabled": false, + "python.testing.nosetestsEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/Zoe/cogs/greetings.py b/Zoe/cogs/greetings.py new file mode 100644 index 0000000..a1e4c98 --- /dev/null +++ b/Zoe/cogs/greetings.py @@ -0,0 +1,20 @@ +import discord +from discord.ext import commands + +class Greetings(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def hey(self, ctx, *, name=None): + if name: + if name.lower() == "esteban julio ricardo montoya de la rosa ramírez": + await ctx.send("Hey thief") + else: + await ctx.send("Hey " + name + " :)") + else: + await ctx.send("Hey " + ctx.message.author.name + " :)") + +def setup(bot): + bot.add_cog(Greetings(bot)) \ No newline at end of file diff --git a/Zoe/cogs/inventory.py b/Zoe/cogs/inventory.py new file mode 100644 index 0000000..5d7fc43 --- /dev/null +++ b/Zoe/cogs/inventory.py @@ -0,0 +1,185 @@ +import sqlite3 +import discord +from discord.ext import commands +import datetime + + +# for reference :) +# import datetime +# datetime.datetime.now().strftime("%d-%m-%Y") +# outputs date in dd-mm-YYYY, a string + + +class InventoryCog(commands.Cog): + + def __init__(self, bot): + self.bot = bot + self.con = sqlite3.connect('inventory.db') + self.cur = self.con.cursor() + + # Create tables + self.cur.execute('''CREATE TABLE IF NOT EXISTS Items ( + ItemID integer PRIMARY KEY AUTOINCREMENT, + ItemName varchar(255), + ItemDesc varchar(255), + Amount integer + );''' + ) + + + self.cur.execute('''CREATE TABLE IF NOT EXISTS Checkout ( + CheckoutID integer PRIMARY KEY AUTOINCREMENT, + User varchar(255), + TakenAmount integer, + TakenDate varchar(10), + ItemID integer, + CONSTRAINT fk_extensions + FOREIGN KEY (ItemID) + REFERENCES Items (ItemID) + );''' + ) + self.con.commit() + + # Inserting values into ITEMS + self.cur.executescript('''INSERT INTO Items ( ItemName, ItemDesc, Amount) VALUES + ( "Headphones", "Pair of headphones", 10); + INSERT INTO Items (ItemName, ItemDesc, Amount) VALUES + ("Mice", "Mice not sure what you expect", 5); + INSERT INTO Items (ItemName, ItemDesc, Amount) VALUES + ("Keyboard", "Goes click click click", 6); + INSERT INTO Items (ItemName, ItemDesc, Amount) VALUES + ("Computer", "Has input + output and sometimes runs", 10); + INSERT INTO Items (ItemName, ItemDesc, Amount) VALUES + ("Mousemat", "Flat thing to put mouse on", 4);''') + self.con.commit() + + # Inserting values into CHECKOUT + self.cur.executescript('''INSERT INTO Checkout ( User, TakenAmount, TakenDate, ItemID) VALUES + ( "A", 2, "14-07-21", 1); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("B", 8, "14-07-21", 1); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("B", 2, "13-07-21", 2); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("D", 1, "14-07-21", 2); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("A", 3, "14-07-21", 3); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("C", 6, "13-07-21", 4); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("A", 1, "14-07-21", 4); + INSERT INTO Checkout (User, TakenAmount, TakenDate, ItemID) VALUES + ("D", 4, "14-07-21", 5); + ''') + + self.con.commit() + + + #Checkout item (almost completed) + ''' + Arg 1: item id + Arg 2: amount + ''' + @commands.command() + async def checkout(self, cxt,*args): + if cxt.author == self.bot.user: + return + else: + itemId = int(args[0]) + amount = int(args[1]) + + #Should check to see if the item exists + total = self.cur.execute('SELECT Amount FROM Items WHERE ItemID = (?)', (str(itemId))) + + #Check to see if the item exists + if self.cur.fetchone() == None: + await cxt.send("The guild doesn't currently have that item") + + elif (0 < amount): + await cxt.send("You're trying to checkout negative/no items?!") + + elif(amount <= total): + checkedOut = self.cur.execute('''INSERT INTO Checkout (User, TakenAmount,ItemID) + (?,?,?) + ''', (cxt.message.author.id, amount, itemId)) + updateAmount = self.cur.execute('''UPDATE Items + SET Amount = (?) + WHERE ItemID = (?)''', (total - amount, itemId)) + + await cxt.send("User "+ cxt.author + " successfully checked out " + amount + " of item " + itemId) + + else: + await cxt.send("The guild doesn't currently have that item in stock") + + + # SHOULD work??? + #Return item + @commands.command() + async def return_item(self, cxt,*args): + if cxt.author == self.bot.user: + return + else: + itemId = int(args[0]) + amount = int(args[1]) + + # Gets the CheckoutID and amount that was checked out + checkoutId = self.cur.execute('SELECT CheckoutID FROM Checkout WHERE User = (?) AND ItemID = (?)', (cxt.message.author.id, itemId)) + takenAmount = self.cur.execute('SELECT TakenAmount FROM Checkout WHERE User = (?)', (cxt.message.author.id)) + + if (amount == 0): + await cxt.send("Why are you returning 0 items? Is this a joke?") + + # user is returning something + else: + if (takenAmount == amount): + self.cur.execute('DELETE FROM Checkout WHERE CheckoutID = (?)', (checkoutId)) + await cxt.send("All items returned. Checkout entry {0} deleted from table.".format(checkoutId)) + + elif (0 < takenAmount < amount): + self.cur.execute('UPDATE Checkout SET TakenAmount = (?) WHERE CheckoutID = (?)', (takenAmount - amount), (checkoutId)) + await cxt.send("Some items returned. Checkout entry {0} updated.".format(checkoutId)) + + # if user is returning a negative value + else: + await cxt.send("If you want to borrow more items, please use !checkout.") + + + + #View checked out items (completed) + @commands.command() + async def view_checkouts(self, cxt): + if cxt.author == self.bot.user: + return + else: + allCheckedOut = self.cur.execute('SELECT * FROM Checkout') + for row in allCheckedOut: + await cxt.send(row) + + + #View instock + @commands.command() + async def view_stock(self, cxt,*args): + if cxt.author == self.bot.user: + return + else: + inStock = self.cur.execute('SELECT * FROM Items WHERE Amount != 0') + for row in inStock: + await cxt.send(row) + + + #Search society items + @commands.command() + async def search(self, cxt,*args): + if cxt.author == self.bot.user: + return + else: + searchString = args[0] + result = self.cur.execute('SELECT * FROM Items WHERE ItemName = (?)', (searchString)) + if (self.cur.fetchone() == None): + await cxt.send("The guild does not have an item with that name") + else: + for row in result: + await cxt.send(row) + +def setup(bot): + bot.add_cog(InventoryCog(bot)) \ No newline at end of file diff --git a/Zoe/cogs/random.py b/Zoe/cogs/random.py new file mode 100644 index 0000000..f8bf186 --- /dev/null +++ b/Zoe/cogs/random.py @@ -0,0 +1,19 @@ +import discord +from discord.ext import commands + +import random + +class Random(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def random(self, ctx, number=None): + if number: + await ctx.send(random.randint(0, int(number))) + else: + await ctx.send(random.randint(0, 100)) + +def setup(bot): + bot.add_cog(Random(bot)) \ No newline at end of file diff --git a/Zoe/cogs/sort.py b/Zoe/cogs/sort.py new file mode 100644 index 0000000..3a095a8 --- /dev/null +++ b/Zoe/cogs/sort.py @@ -0,0 +1,29 @@ +import discord +from discord.ext import commands + +class Sort(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def sort(self, ctx, *args): + argsList = [] + for item in args: + argsList.append(item) + argsList.sort() + + if argsList: + output = "{} arguments: ".format(len(args)) + for item in argsList: + if item == argsList[0]: + output += item + else: + output += " " + item + else: + output = "No arguments found." + + await ctx.send(output) + +def setup(bot): + bot.add_cog(Sort(bot)) \ No newline at end of file diff --git a/Zoe/cogs/twitter.py b/Zoe/cogs/twitter.py new file mode 100644 index 0000000..9be0afe --- /dev/null +++ b/Zoe/cogs/twitter.py @@ -0,0 +1,54 @@ +import os +import requests +import json + +import discord +from discord.ext import commands + +class Twitter(commands.Cog): + + def __init__(self, bot): + self.bot = bot + self.apiKey = os.environ['KOALA_API_KEY'] + self.apiSecret = os.environ['KOALA_SECRET_KEY'] + self.accessToken = os.environ['KOALA_ACCESS'] + self.accessSecret = os.environ['KOALA_ACCESS_SECRET'] + self.bearerToken = os.environ['KOALA_BEARER'] + + # def create_url(): + # return "https://api.twitter.com/2/users/1415763208477020163/tweets" + + # def get_params(): + # return {"tweets.fields":"created_at"} + + def bearer_oauth(self, r): + r.headers["Authorization"] = f"Bearer {self.bearerToken}" + r.headers["User-Agent"] = "v2UserTweetsPython" + return r + + @commands.command() + async def get_tweet(self, ctx, user): + + try: + userIDResponse = requests.request("GET", "https://api.twitter.com/2/users/by/username/{}".format(user), auth=self.bearer_oauth, params="") + userResponseJson = userIDResponse.json() + userID = userResponseJson["data"]["id"] + + response = requests.request("GET", "https://api.twitter.com/2/users/{}/tweets".format(userID), auth=self.bearer_oauth, params="") + json_response = response.json() + + url = "https://twitter.com/i/web/status/{}".format(json_response["data"][0]["id"]) + description = json_response["data"][0]["text"] + + username = userResponseJson["data"]["username"] + + embed = discord.Embed(title="{}'s last tweet".format(username), description=description, url=url) + + await ctx.send(embed=embed) + + except: + await ctx.send(":(") + + +def setup(bot): + bot.add_cog(Twitter(bot)) \ No newline at end of file diff --git a/Zoe/main.py b/Zoe/main.py new file mode 100644 index 0000000..0d1acd6 --- /dev/null +++ b/Zoe/main.py @@ -0,0 +1,44 @@ +import os +import discord +from discord.ext import commands + +from dotenv import load_dotenv + +bot = commands.Bot(command_prefix='!') + +@bot.event +async def on_ready(): + print(f"Bot user {bot.user} is ready.") + bot.load_extension("cogs.greetings") + bot.load_extension("cogs.sort") + bot.load_extension("cogs.random") + bot.load_extension("cogs.twitter") + + +@bot.event +async def on_message(msg): + if msg.author == bot.user: + return + + if msg.content == "Ping": + await msg.channel.send("Pong!") + + if msg.content == "easter": + await msg.channel.send("egg") + + if msg.content.lower() == "no one calls esteban julio ricardo montoya de la rosa ramírez a thief!": + await msg.channel.send("NO ONE'S GOT THE TIME") + + await bot.process_commands(msg) + + +#command errors +@bot.event +async def on_command_error(ctx, error): + if isinstance(error, commands.MissingRequiredArgument): + await ctx.send("Missing argument.") + + +load_dotenv() +BOT_TOKEN=os.environ['TOKEN'] +bot.run(BOT_TOKEN) \ No newline at end of file diff --git a/Zoe/requirements.txt b/Zoe/requirements.txt new file mode 100644 index 0000000..9ca5d0d --- /dev/null +++ b/Zoe/requirements.txt @@ -0,0 +1,5 @@ +discord.py==1.7.3 +dpytest==0.5.3 +pytest==6.2.4 +python-dotenv==0.18.0 +TwitterAPI==2.7.5 \ No newline at end of file diff --git a/Zoe/tests/test_greetings.py b/Zoe/tests/test_greetings.py new file mode 100644 index 0000000..a7c7e38 --- /dev/null +++ b/Zoe/tests/test_greetings.py @@ -0,0 +1,33 @@ +import asyncio +import pytest +import discord +import discord.ext.test as dpytest +import discord.ext.commands as commands + +from cogs import greetings + +@pytest.fixture(autouse=True) +async def bot(event_loop): + intents = discord.Intents.default() + intents.members = True + intents.messages = True + b = commands.Bot("!", loop=event_loop, intents=intents) + dpytest.configure(b) + return b + +@pytest.fixture(autouse=True) +def greetings_cog(bot: commands.Bot): + greetings_cog = greetings.Greetings(bot) + bot.add_cog(greetings_cog) + dpytest.configure(bot) + return greetings_cog + +@pytest.mark.asyncio +async def test_with_name(): + await dpytest.message("!hey john") + assert dpytest.verify().message().content("Hey john :)") + +@pytest.mark.asyncio +async def test_without_name(): + await dpytest.message("!hey") + assert dpytest.verify().message().content("Hey TestUser0 :)") \ No newline at end of file diff --git a/Zoe/tests/test_random.py b/Zoe/tests/test_random.py new file mode 100644 index 0000000..9ca1d78 --- /dev/null +++ b/Zoe/tests/test_random.py @@ -0,0 +1,35 @@ +import asyncio +import pytest +import discord +import discord.ext.test as dpytest +import discord.ext.commands as commands + +from cogs import random + +@pytest.fixture(autouse=True) +async def bot(event_loop): + intents = discord.Intents.default() + intents.members = True + intents.messages = True + b = commands.Bot("!", loop=event_loop, intents=intents) + dpytest.configure(b) + await dpytest.empty_queue() + return b + +@pytest.fixture(autouse=True) +def random_cog(bot: commands.Bot): + random_cog = random.Random(bot) + bot.add_cog(random_cog) + dpytest.configure(bot) + return random_cog + +@pytest.mark.asyncio +async def test_with_param(): + await dpytest.message("!random 0") + assert dpytest.verify().message().content("0") + +@pytest.mark.asyncio +async def test_without_param(): + await dpytest.message("!random") + msg = dpytest.sent_queue.peek() + assert int(msg.content) <= 100 \ No newline at end of file diff --git a/Zoe/tests/test_sort.py b/Zoe/tests/test_sort.py new file mode 100644 index 0000000..8c72a72 --- /dev/null +++ b/Zoe/tests/test_sort.py @@ -0,0 +1,35 @@ +import asyncio +import pytest +import discord +import discord.ext.test as dpytest +import discord.ext.commands as commands + +from cogs import sort + +@pytest.fixture(autouse=True) +async def bot(event_loop): + intents = discord.Intents.default() + intents.members = True + intents.messages = True + b = commands.Bot("!", loop=event_loop, intents=intents) + dpytest.configure(b) + await dpytest.empty_queue() + return b + +@pytest.fixture(autouse=True) +def sort_cog(bot: commands.Bot): + sort_cog = sort.Sort(bot) + bot.add_cog(sort_cog) + dpytest.configure(bot) + return sort_cog + +@pytest.mark.asyncio +async def test_sort(): + await dpytest.message("!sort house dog") + msg = dpytest.sent_queue.peek() + assert dpytest.verify().message().content("2 arguments: dog house"), msg.content + +@pytest.mark.asyncio +async def test_sort_no_params(): + await dpytest.message("!sort") + assert dpytest.verify().message().content("No arguments found.") \ No newline at end of file