diff --git a/.gitignore b/.gitignore
index cbd2202..0d278f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,9 @@
*.pyc
/logs/
+
+
+__pycache__/
+
+jdk-13.0.2/
+.DS_Store
diff --git a/EmojiCollection.py b/EmojiCollection.py
new file mode 100644
index 0000000..97af70f
--- /dev/null
+++ b/EmojiCollection.py
@@ -0,0 +1,50 @@
+import emoji
+import selenium
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+class EmojiCollection():
+ def __init__(self):
+
+ options = Options()
+ # TODO this can't be OS specific
+ # options.binary_location = "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"
+ options.binary_location = "C:\Program Files\Google\Chrome\Application\chrome.exe"
+
+ self.driver = webdriver.Chrome(options=options)
+ self.driver.get("https://emojipedia.org")
+ # accepts cookies
+ acceptCookies = WebDriverWait(
+ self.driver,
+ 30).until(EC.presence_of_element_located((By.ID, "onetrust-accept-btn-handler"))).click()
+
+ def searchEngineReturned(self):
+ #click onto the link of the first emoji
+ clickFirstEmoji = WebDriverWait(self.driver, 30).until(
+ EC.element_to_be_clickable((By.XPATH, "/html/body/div[@class='container']/div[@class='content']/ol/*[1]/h2/a"))).click()
+ def getEmoji(self):
+ try:
+ nameOfEmoji = WebDriverWait(self.driver, 10).until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "span.shortcode"))).text
+ return emoji.emojize(nameOfEmoji)
+ except selenium.common.exceptions.TimeOutException:
+ tmp = emoji.emojize(":red_heart:")
+ return tmp
+ #enter the requested emoji to the search engine
+ def search(self, emoji):
+ #type a key word to the search engine
+ element = self.driver.find_element(By.ID, "id_q")
+ element.send_keys(emoji)
+ #the search engine starts searching
+ element.send_keys(Keys.RETURN)
+ self.searchEngineReturned()
+ return self.getEmoji()
+
+ def quit(self):
+ self.driver.quit()
+
diff --git a/Lavalink.jar b/Lavalink.jar
new file mode 100644
index 0000000..00e887b
Binary files /dev/null and b/Lavalink.jar differ
diff --git a/README.md b/README.md
index 10b2f31..7168725 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
DecodED 3
-> π Hello and welcome to DecodED 3! Over the next 5 weeks, we will be creating a discord bot with [Discord.py](https://discordpy.readthedocs.io/en/stable/index.html)
+> π Hello and welcome to DecodED 3! Over the next 5 weeks, we will be creating a discord bot
+> with [Discord.py](https://discordpy.readthedocs.io/en/stable/index.html)
---
@@ -25,9 +26,8 @@
## About DecodED
* DecodED is a series of workshops run every year by HackMelbourne to help students/teach students with introductory
- programming background to create some cool things with code. In past years, we created full-stack websites and a Space Invaders game. This year, we will be creating a Discord bot!
+ programming background tocreate some cool things with code. In past years, we created full-stack websites and a Space Invaders game. This year, we will be creating a Discord bot!
* We are also trying a new teaching style this year so let us know if you like it!
-
### Dates
Get the latest details on [Notion](https://uneven-boa-710.notion.site/DecodED-3-3336c8bbd49a4df6a00b8b34af1980c6) (the below table may be outdated)
@@ -41,14 +41,17 @@ TicTacToe bot | Alan Gilbert 103 and Zoom | Warren, Daniel | 2pm, Wednesday 14th
Music bot | Alan Gilbert 121 (Theatre) and Zoom | Aryan, Ryan | Wednesday 21st September
### Zoom Link: https://unimelb.zoom.us/j/88528442813?pwd=WlYrQ3pHcm5xMXpGQkZZSllZZTNvQT09
+
Password: 323737
## About this repository
+
* This repository contains:
* Participant Workbook
* each workbook is made up of multiple Parts which are then made up of multiple **β
Tasks** that the participant
should complete, with **π§© Hints** that help guide them in completing the Task, and **π‘ Extensions** that are
optional tasks that can be done if the participant has completed the Task ahead of schedule
+
* Workshop Recordings
* Code for the Discord Bot being created
@@ -66,6 +69,7 @@ Password: 323737
> Who doesnβt love a good meme? Join us and create the functionality to meme on your friends so hard that they wished
> they had their own meme bot.
+
>
> Hosted by Aly and Minh
diff --git a/Server.py b/Server.py
new file mode 100644
index 0000000..eb53788
--- /dev/null
+++ b/Server.py
@@ -0,0 +1,13 @@
+import http.server
+import socketserver
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+def runServer():
+ Handler = http.server.SimpleHTTPRequestHandler
+ port = os.environ.get('PORT')
+ with socketserver.TCPServer(("", int(port)), Handler) as httpd:
+ print(f'serving at port {port}')
+ httpd.serve_forever()
+
diff --git a/application.yml b/application.yml
new file mode 100644
index 0000000..5bf19d6
--- /dev/null
+++ b/application.yml
@@ -0,0 +1,49 @@
+server: # REST and WS server
+ port: 2333
+ address: localhost
+lavalink:
+ server:
+ password: "youshallnotpass"
+ sources:
+ youtube: true
+ bandcamp: true
+ soundcloud: true
+ twitch: true
+ vimeo: true
+ http: true
+ local: false
+ bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses
+ frameBufferDurationMs: 5000 # How many milliseconds of audio to keep buffered
+ youtubePlaylistLoadLimit: 6 # Number of pages at 100 each
+ playerUpdateInterval: 5 # How frequently to send player updates to clients, in seconds
+ youtubeSearchEnabled: true
+ soundcloudSearchEnabled: true
+ gc-warnings: true
+ #ratelimit:
+ #ipBlocks: ["1.0.0.0/8", "..."] # list of ip blocks
+ #excludedIps: ["...", "..."] # ips which should be explicit excluded from usage by lavalink
+ #strategy: "RotateOnBan" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch
+ #searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing
+ #retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times
+
+metrics:
+ prometheus:
+ enabled: false
+ endpoint: /metrics
+
+sentry:
+ dsn: ""
+ environment: ""
+# tags:
+# some_key: some_value
+# another_key: another_value
+
+logging:
+ file:
+ max-history: 30
+ max-size: 1GB
+ path: ./logs/
+
+ level:
+ root: INFO
+ lavalink: INFO
diff --git a/bot.py b/bot.py
new file mode 100644
index 0000000..bb79eb8
--- /dev/null
+++ b/bot.py
@@ -0,0 +1,35 @@
+import json
+import os
+
+import discord
+from discord.ext import commands
+from dotenv import load_dotenv
+
+load_dotenv()
+TOKEN = os.getenv("TOKEN")
+GUILDS = os.getenv("GUILDS")
+GUILDS = json.loads(GUILDS)
+GUILDS = [discord.Object(id=guild) for guild in GUILDS]
+
+
+class Bot(commands.Bot):
+ def __init__(self):
+ super().__init__(command_prefix="!",
+ intents=discord.Intents.all())
+ self.GUILDS = GUILDS
+ self.TOKEN = TOKEN
+
+ async def setup_hook(self):
+ await self.load_extension(f"cogs.tictactoe")
+ for guild in self.GUILDS:
+ await self.tree.sync(guild=guild)
+
+ async def on_ready(self):
+ print("ready...")
+
+
+bot = Bot()
+bot.run(bot.TOKEN)
+
+if __name__ == '__main__':
+ pass
diff --git a/chart.py b/chart.py
new file mode 100644
index 0000000..fc20ad8
--- /dev/null
+++ b/chart.py
@@ -0,0 +1,23 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+# Fixing random state for reproducibility
+np.random.seed(19680801)
+
+
+plt.rcdefaults()
+fig, ax = plt.subplots()
+# Example data
+people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
+y_pos = np.arange(len(people))
+performance = 3 + 10 * np.random.rand(len(people))
+error = np.random.rand(len(people))
+
+ax.barh(y_pos, performance, xerr=error, align='center')
+ax.set_yticks(y_pos, labels=people)
+ax.invert_yaxis() # labels read top-to-bottom
+ax.set_xlabel('Performance')
+ax.set_title('How fast do you want to go today?')
+
+
+plt.savefig('chart.png')
\ No newline at end of file
diff --git a/cogs/meme.py b/cogs/meme.py
new file mode 100644
index 0000000..530fae1
--- /dev/null
+++ b/cogs/meme.py
@@ -0,0 +1,152 @@
+import json
+import os
+
+import requests
+from discord.ext import commands
+from dotenv import load_dotenv
+from pymongo import MongoClient
+from requests.exceptions import HTTPError
+from schema import Schema, SchemaError, And, Regex
+
+load_dotenv()
+
+TOKEN = os.getenv('TOKEN')
+COMMAND = os.getenv('DISCORD_COMMAND_ROOT')
+BOTNAME = os.getenv('BOT_NAME')
+API_ROOT = os.getenv('MEME_API_ROOT')
+MONGODB_URI = os.getenv('MONGODB_URI')
+DB = os.getenv('DB')
+COLLECTION = os.getenv('COLLECTION')
+
+collection = MongoClient(MONGODB_URI)[DB][COLLECTION]
+
+
+# all commands that this bot will respond to will begin with ";;bot_name"
+# bot = commands.Bot(command_prefix=COMMAND + BOTNAME, strip_after_prefix=True, intents=discord.Intents.all())
+
+def get_random_memes(count):
+ try:
+ response = requests.get(f"{API_ROOT}{count}")
+ except HTTPError as error:
+ raise error
+ json_data = json.loads(response.text)
+ return json_data
+
+
+def get_random_meme_from_subreddits(subReddit, count):
+ try:
+ response = requests.get(f"{API_ROOT}{subReddit}/{count}")
+ except HTTPError as error:
+ raise error
+ if (response.status_code == 404 or response.status_code == 400):
+ raise HTTPError(response.text)
+ json_data = json.loads(response.text)
+ return json_data
+
+
+def check_link_die(link):
+ try:
+ response = requests.get(link)
+ except:
+ return False
+ if (response.status_code == 404):
+ return False
+ return True
+
+
+meme_schema = Schema({
+ "name": str,
+ "url": And(str, Regex("^(https:\/\/i.redd.it\/)\w+\.\w{3}$"))})
+
+
+class Meme(commands.Cog):
+ def __init__(self, bot):
+ self.bot = bot
+
+ @commands.command()
+ async def meme(self, ctx, *args):
+ # ;;random
+ if (len(args) == 0):
+ random_meme = get_random_memes(1)['memes'][0]
+ while (check_link_die(random_meme['url']) == False):
+ random_meme = get_random_memes(1)['memes'][0]
+ await ctx.send(random_meme['url'])
+
+ # ;;random 5
+ if (len(args) > 0):
+ try:
+ random_memes = get_random_memes(int(args[0]))['memes']
+ except:
+ await ctx.send("Unrecognized command. Pretty sure that wasn't a number :)")
+ pass
+ random_memes_image_link = [e['url'] for e in random_memes]
+ for link in random_memes_image_link:
+ if (check_link_die(link) == False):
+ continue
+ else:
+ await ctx.send(link)
+ pass
+
+ @commands.command()
+ async def meme_subreddit(self, ctx, arg1):
+ subreddit = arg1
+ try:
+ random_meme = get_random_meme_from_subreddits(subreddit, 1)
+ random_meme_subreddit = random_meme['memes'][0]
+ while (check_link_die(random_meme_subreddit['url']) == False):
+ random_meme = get_random_meme_from_subreddits(subreddit, 1)
+ random_meme_subreddit = random_meme['memes'][0]
+ await ctx.send(random_meme_subreddit['url'])
+ except HTTPError as err:
+ await ctx.send('Subreddit not found')
+ pass
+ pass
+
+ @commands.command()
+ async def save_meme(self, ctx, arg1):
+ try:
+ response = await ctx.channel.fetch_message(ctx.message.reference.message_id)
+ meme_url = response.content
+ except print(0):
+ await ctx.send("You forgot to reply to a meme, or the meme you replied to wasn't saved in the right format")
+ pass
+ saved_name = arg1
+ saved_meme = {
+ "name": saved_name,
+ "url": meme_url,
+ }
+ try:
+ meme_schema.validate(saved_meme)
+ except SchemaError as err:
+ await ctx.send(err)
+ pass
+ collection.insert_one(saved_meme)
+ await ctx.send("Saved to database!")
+ pass
+
+ @commands.command()
+ async def load_meme(self, ctx, arg1):
+ lookup_name = arg1
+ try:
+ found_meme = collection.find_one({"name": lookup_name})
+ await ctx.send(found_meme["url"])
+ except TypeError:
+ await ctx.send("Meme not found in database!")
+ pass
+ pass
+
+ @commands.command()
+ async def delete_meme(self, ctx, arg1):
+ lookup_name = arg1
+ try:
+ found_meme = collection.find_one({"name": lookup_name})
+ except TypeError:
+ await ctx.send("Meme not found in database!")
+ pass
+ collection.delete_one(found_meme)
+ await ctx.send("Meme was deleted from database!")
+ pass
+
+
+async def setup(bot):
+ await bot.add_cog(Meme(bot))
diff --git a/cogs/musicbot.py b/cogs/musicbot.py
new file mode 100644
index 0000000..e7ba015
--- /dev/null
+++ b/cogs/musicbot.py
@@ -0,0 +1,129 @@
+# Inspired by https://github.com/afazio1/robotic-nation-proj/blob/main/projects/discord-bot/new-music-bot/music-yt.py
+
+import os
+import subprocess
+import time
+
+import discord
+import requests
+import wavelink
+from discord.ext import commands
+from dotenv import load_dotenv
+
+
+class MusicBot(commands.Cog):
+ voice_clients = {}
+
+ def __init__(self, client):
+ self.client = client
+
+ @commands.command()
+ async def join(self, ctx: commands.Context):
+ # Leave current channel
+ if ctx.voice_client is not None:
+ await ctx.voice_client.disconnect()
+ if not ctx.message.author.voice:
+ await ctx.send('You are not in a voice channel')
+ return
+ channel = ctx.message.author.voice.channel
+ self.voice_clients[ctx.guild.id] = await channel.connect(cls=wavelink.Player())
+ await ctx.send(f'Joined {channel}')
+
+ @commands.command()
+ async def leave(self, ctx):
+ if ctx.guild.id in self.voice_clients:
+ await self.voice_clients[ctx.guild.id].stop()
+ del self.voice_clients[ctx.guild.id]
+ await ctx.send('Left')
+ if ctx.voice_client is not None:
+ await ctx.voice_client.disconnect()
+
+ @commands.command()
+ async def play(self, ctx, *, search: wavelink.YouTubeTrack):
+ if ctx.guild.id not in self.voice_clients:
+ # Join the user's voice channel
+ await self.join(ctx)
+ if ctx.guild.id not in self.voice_clients:
+ return
+ voice = self.voice_clients[ctx.guild.id]
+ await voice.play(search)
+ embed = discord.Embed(
+ title=voice.source.title,
+ url=voice.source.uri,
+ description=f"Playing {voice.source.title} in {voice.channel}"
+ )
+ embed.set_image(url=voice.source.thumbnail)
+ if hasattr(ctx.author, 'avatar'):
+ embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar.url)
+ await ctx.send(embed=embed)
+
+ @commands.command()
+ async def stop(self, ctx):
+ if ctx.guild.id in self.voice_clients:
+ await self.voice_clients[ctx.guild.id].stop()
+ await ctx.send('Stopped')
+ else:
+ await ctx.send('Not in a voice channel')
+
+ @commands.command()
+ async def pause(self, ctx):
+ if ctx.guild.id in self.voice_clients:
+ await self.voice_clients[ctx.guild.id].pause()
+ await ctx.send('Paused')
+ else:
+ await ctx.send('Not in a voice channel')
+
+ @commands.command()
+ async def resume(self, ctx):
+ if ctx.guild.id in self.voice_clients:
+ await self.voice_clients[ctx.guild.id].resume()
+ await ctx.send('Resumed')
+ else:
+ await ctx.send('Not in a voice channel')
+
+ # Start the bot
+ @commands.Cog.listener()
+ async def on_ready(self):
+ print('Logged in as')
+ print(self.client.user.name)
+ print(self.client.user.id)
+ print('------')
+ # Try start Lavafront server
+ subprocess.Popen(["java", "-jar", "Lavalink.jar"])
+ # wait for port to open
+ while True:
+ try:
+ r = requests.get('http://localhost:2333')
+ break
+ except requests.exceptions.ConnectionError:
+ print("Waiting for lavalink to go live...")
+ time.sleep(1)
+ continue
+
+ async def connect_wavefront():
+ await self.client.wait_until_ready()
+ await wavelink.NodePool.create_node(
+ bot=self.client,
+ host='localhost',
+ port=2333,
+ password='youshallnotpass'
+ )
+
+ self.client.loop.create_task(connect_wavefront())
+
+ @commands.Cog.listener()
+ async def on_wavelink_node_ready(self, node: wavelink.Node):
+ print(f'Connected to wavefront! ID: {node.identifier}')
+
+
+async def setup(bot):
+ await bot.add_cog(MusicBot(bot))
+
+
+if __name__ == '__main__':
+ load_dotenv()
+ TOKEN = os.getenv('TOKEN')
+
+ client = commands.Bot(command_prefix='!')
+ client.add_cog(MusicBot(client))
+ client.run(TOKEN)
diff --git a/cogs/poll.py b/cogs/poll.py
new file mode 100644
index 0000000..95b7859
--- /dev/null
+++ b/cogs/poll.py
@@ -0,0 +1,67 @@
+
+from ast import alias
+import discord
+import asyncio
+import matplotlib.pyplot as plt
+import numpy as np
+from discord.ext import commands
+import emoji
+
+
+class Poll(commands.Cog):
+ def __init__(self, bot):
+ self.bot = bot
+
+ async def create_bar_chart(self,options, reactions, question):
+ totalVotes = 0
+ votes = []
+ #calculate the number of votes for each option
+ for reaction in reactions:
+ v=0
+ async for _ in reaction.users():
+ v+=1
+ totalVotes += v
+ votes.append(v)
+
+ percentages = [round((vote/totalVotes)* 100, 2) for vote in votes]
+ fig, ax = plt.subplots()
+ y_pos = np.arange(len(options))
+ error = np.random.rand(len(options))
+ ax.barh(y_pos, percentages, xerr=error, align='center')
+ ax.set_yticks(y_pos, labels=options)
+ ax.invert_yaxis() # labels read top-to-bottom
+ ax.set_xlabel('Votes (%)')
+ ax.set_title(question)
+ plt.savefig('chart.png')
+
+
+ @commands.command(aliases=["p"])
+ async def init_poll(self, ctx, question, time, *options):
+ if len(options) > 3:
+ await ctx.send("The number of options cannot exceed the allowed limit")
+ embed = discord.Embed(title = question,
+ description= f'Poll will end in {time} seconds :alarm_clock:. There are {len(options)} options:')
+ tmp = [':one:', ':two:', ':three:'][:len(options)]
+ emojis = [emoji.emojize(e, language='alias') for e in tmp]
+ for i in range(len(options)):
+ emo = emojis[i]
+ embed.add_field(name = emo, value = options[i], inline = True)
+ embed.add_field(name="Instructions", value="React to cast a vote", inline=False)
+ embed.set_footer(text = f'This poll is created by {ctx.author.name}')
+ poll = await ctx.send(embed = embed)
+ # add emojis as reactions to the poll
+ for emo in emojis:
+ await poll.add_reaction(emo)
+ await asyncio.sleep(int(time))
+ #calculate the result of the poll
+ message = await ctx.fetch_message(poll.id)
+ # create a bar chart as a result
+ await self.create_bar_chart(options, message.reactions, question)
+ result = discord.Embed(title='Result', description = "Here are the vote results")
+ file = discord.File("chart.png")
+ result.set_image(url="attachment://chart.png")
+ await ctx.send(file=file, embed = result)
+
+
+async def setup(bot):
+ await bot.add_cog(Poll(bot))
diff --git a/cogs/tictactoe.py b/cogs/tictactoe.py
new file mode 100644
index 0000000..5fee753
--- /dev/null
+++ b/cogs/tictactoe.py
@@ -0,0 +1,125 @@
+import discord
+import numpy as np
+from discord import app_commands
+from discord.ext import commands
+
+SIZE = 3
+
+
+class TicTacToeButton(discord.ui.Button):
+ def __init__(self, row, col):
+ super().__init__(style=discord.ButtonStyle.secondary, label=" ", row=row)
+ self.row = row
+ self.col = col
+
+ async def callback(self, interaction):
+ if self.view.current_player == self.view.player_1:
+ if interaction.user != self.view.player_1:
+ await interaction.response.send_message("Its not your Turn!", ephemeral=True)
+ return
+
+ self.style = discord.ButtonStyle.danger
+ self.label = 'X'
+ self.disabled = True
+ self.view.board[self.row][self.col] = 1
+ self.view.current_player = self.view.player_2
+ content = f":game_die: `{self.view.player_1.display_name}` **VS** `{self.view.player_2.display_name}`\n\n{self.view.player_2.mention}, select your move:"
+
+ else:
+ if interaction.user != self.view.player_2:
+ await interaction.response.send_message("Its not your Turn!", ephemeral=True)
+ return
+
+ self.style = discord.ButtonStyle.success
+ self.label = 'O'
+ self.disabled = True
+ self.view.board[self.row][self.col] = -1
+ self.view.current_player = self.view.player_1
+ content = f":game_die: `{self.view.player_1.display_name}` **VS** `{self.view.player_2.display_name}`\n\n{self.view.player_1.mention}, select your move:"
+
+ winner = self.view.check_winner()
+ if winner == self.view.player_1:
+ content = f":tada: {self.view.player_1.mention} has won the game!"
+ elif winner == self.view.player_2:
+ content = f":tada: {self.view.player_2.mention} has won the game!"
+ elif winner == "tie":
+ content = "No one won the game, it's a tie! Try again?"
+
+ # for child in self.view.children:
+ # child.disabled = True
+ # self.view.stop()
+
+ await interaction.response.edit_message(content=content, view=self.view)
+
+
+class TicTacToeView(discord.ui.View):
+ def __init__(self, player_1, player_2):
+ super().__init__()
+ self.player_1 = player_1
+ self.player_2 = player_2
+ self.current_player = self.player_1
+ self.board = np.zeros((SIZE, SIZE))
+
+ for row in range(SIZE):
+ for col in range(SIZE):
+ self.add_item(TicTacToeButton(row, col))
+
+ def check_winner(self):
+ col_sums = np.sum(self.board, axis=1)
+ for col_sum in col_sums:
+ if col_sum == SIZE:
+ return self.player_1
+ elif col_sum == -SIZE:
+ return self.player_2
+
+ row_sums = np.sum(self.board, axis=0)
+ for row_sum in row_sums:
+ if row_sum == SIZE:
+ return self.player_1
+ elif row_sum == -SIZE:
+ return self.player_2
+
+ diag_sum = self.board[0, 0] + self.board[1, 1] + self.board[2, 2]
+ if diag_sum == SIZE:
+ return self.player_1
+ elif diag_sum == -SIZE:
+ return self.player_2
+
+ diag_sum = self.board[0, 2] + self.board[1, 1] + self.board[2, 0]
+ if diag_sum == SIZE:
+ return self.player_1
+ elif diag_sum == -SIZE:
+ return self.player_2
+
+ if np.all(self.board):
+ return "tie"
+
+
+class TicTacToe(commands.Cog):
+ def __init__(self, bot):
+ self.bot = bot
+
+ @app_commands.command(
+ name="tictactoe",
+ description="Play a game of TicTacToe"
+ )
+ @app_commands.describe(
+ opponent="User you want to play with"
+ )
+ async def tictactoe(self, interaction, opponent: discord.Member):
+ player_1 = interaction.user
+ player_2 = opponent
+
+ if player_1 == player_2 or player_2.bot:
+ await interaction.response.send_message("you cannot challenge that user")
+ else:
+ await interaction.response.send_message(
+ f":game_die: `{player_1.display_name}` **VS** `{player_2.display_name}`\n\n{player_1.mention}, select your move:",
+ view=TicTacToeView(player_1, player_2))
+
+
+async def setup(bot):
+ await bot.add_cog(
+ TicTacToe(bot),
+ guilds=bot.GUILDS if hasattr(bot, 'GUILDS') else []
+ )
diff --git a/env-example b/env-example
new file mode 100644
index 0000000..d69d245
--- /dev/null
+++ b/env-example
@@ -0,0 +1,8 @@
+TOKEN =
+DISCORD_GUILD =
+DISCORD_COMMAND_ROOT = ;;
+BOT_NAME = random
+MEME_API_ROOT = https://meme-api.herokuapp.com/gimme/
+MONGODB_URI = mongodb+srv://:@cluster0..mongodb.net/test_discord_bot?retryWrites=true&w=majority
+DB = test_discord_bot
+COLLECTION = memes
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..b04efca
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,17 @@
+aiohttp==3.8.1
+aiosignal==1.2.0
+async-timeout==4.0.2
+attrs==21.4.0
+charset-normalizer==2.1.0
+discord.py @ git+https://github.com/Rapptz/discord.py@87bc79e6e311da60b024ae61a2a3757e55606aa6
+frozenlist==1.3.0
+idna==3.3
+multidict==6.0.2
+numpy==1.23.1
+python-dotenv==0.20.0
+yarl==1.7.2
+wavelink~=1.3.2
+matplotlib~=3.5.3
+emoji~=2.0.0
+pymongo~=4.2.0
+schema~=0.7.5
\ No newline at end of file
diff --git a/w1/python_cheatsheet.md b/w1/python_cheatsheet.md
index 0089d1f..af3f7ea 100644
--- a/w1/python_cheatsheet.md
+++ b/w1/python_cheatsheet.md
@@ -1,6 +1,7 @@
Xin Yu's COMP10001 Python Notes
-> You don't need to know everything in this document to create the Discord Bot - just skim through the document to remmind yourself of Python's Syntax
+> You don't need to know everything in this document to create the Discord Bot - just skim through the document to
+> remmind yourself of Python's Syntax
---
Table of Contents
@@ -12,69 +13,76 @@
- [Operators (Highest to Lowest precedence)](#operators-highest-to-lowest-precedence)
- [Conditionals](#conditionals)
- [While Loop](#while-loop)
- - [For loop](#for-loop)
+ - [For loop](#for-loop)
- [Functions](#functions)
- [Files](#files)
- [`csv` Library](#csv-library)
- [Iterators](#iterators)
- - [Iterators VS Sequences](#iterators-vs-sequences)
+ - [Iterators VS Sequences](#iterators-vs-sequences)
- [`itertools` Library](#itertools-library)
- [Unpacking](#unpacking)
- [Data Types](#data-types)
- - [Integer](#integer)
- - [String](#string)
- - [Float](#float)
- - [Boolean](#boolean)
- - [List](#list)
- - [Tuple](#tuple)
- - [Dictionary](#dictionary)
- - [Default Dictionaries](#default-dictionaries)
- - [Unpacking Dictionaries:](#unpacking-dictionaries)
- - [Set](#set)
+ - [Integer](#integer)
+ - [String](#string)
+ - [Float](#float)
+ - [Boolean](#boolean)
+ - [List](#list)
+ - [Tuple](#tuple)
+ - [Dictionary](#dictionary)
+ - [Default Dictionaries](#default-dictionaries)
+ - [Unpacking Dictionaries:](#unpacking-dictionaries)
+ - [Set](#set)
- [Errors](#errors)
- [Statements and Expressions](#statements-and-expressions)
- [Extra](#extra)
- - [`pillow` Library](#pillow-library)
- - [Data Science](#data-science)
- - [`matplotlib` LIbrary](#matplotlib-library)
+ - [`pillow` Library](#pillow-library)
+ - [Data Science](#data-science)
+ - [`matplotlib` LIbrary](#matplotlib-library)
---
## Printing & Inputting
+
- `print(object)` displays object to user
- `input(str)` displays str to user and asks user for input
+
## Commenting
+
- `# hello` single line comment
- `'''hello'''` or `"""hello"""` docstring/multi-line comment
---
+
## Operators (Highest to Lowest precedence)
+
- Arithmetic Operators
- - `()` brackets
- - `**` exponent
- - `%` modulus/remainder
- - `//` floor/integer division
- - `/` division
- - `*` multiplication
- - `-` subtraction (also a unary operator; make number negative)
- - `+` addition (also a unary operator; make number positive)
+ - `()` brackets
+ - `**` exponent
+ - `%` modulus/remainder
+ - `//` floor/integer division
+ - `/` division
+ - `*` multiplication
+ - `-` subtraction (also a unary operator; make number negative)
+ - `+` addition (also a unary operator; make number positive)
- Comparison Operators
- - `==` equal to
- - `!=` not equal to
- - `<` less than
- - `>` greater than
- - `<=` less than or equal to
- - `>=` more than or equal to
- - `in`
+ - `==` equal to
+ - `!=` not equal to
+ - `<` less than
+ - `>` greater than
+ - `<=` less than or equal to
+ - `>=` more than or equal to
+ - `in`
- Boolean Operators
- - `not`
- - `and`
- - `or`
+ - `not`
+ - `and`
+ - `or`
---
+
## Conditionals
+
```python
if :
@@ -86,15 +94,21 @@ elif :
else:
```
+
---
+
## While Loop
+
```python
while :
```
+
- `break` immediately exists while loop
- `continue` jumps back to start of loop
+
### For loop
+
```python
# with range()
for i in range():
@@ -104,19 +118,25 @@ for i in range():
for i in :
# i will become the char/ele
```
+
---
+
## Functions
+
```python
CONSTANT = 0 # declare constants before all functions
def ():
```
+
---
+
## Files
+
- `fp = open(filename, mode)` opens file
- - `r` read (default)
- - `w` write
- - `a` append
+ - `r` read (default)
+ - `w` write
+ - `a` append
- `fp = open(new-filename, 'w'/'a')` creates new file to write or append to
- `fp.read()` return entire contents of file as a single string
- `fp.readline()` returns a single line of text as a string
@@ -124,19 +144,23 @@ def ():
- `fp.write(str)` write str to fp, appending it to whatever is currently there
- `fp.writelines(str_list)` writes each string in str_list as separate lines to the file
- `fp.close()` closes file *important: must always be done once finished using file
-- `with open(filename) as name: ` opens file as name; file can only be accessed within the code block; file is closed automatically on exiting code block
+- `with open(filename) as name: ` opens file as name; file can only be accessed within the code block; file is closed
+ automatically on exiting code block
## `csv` Library
+
- CSV: Comma-Separated Values - represents tabular data
- `import csv` imports csv module
- `fp = open('filename.csv', mode)` opens csv file
- `reader = csv.reader(fp)` returns a csv reader object
- - can be iterated through
+ - can be iterated through
- `reader.next(fp)` return the next parsed line from fp as a list
- `csv.DictReader(open("filename.csv", mode))` returns list of dictionaries for each row
- `csv.writer()` returns a csv writer object
-- `.writerows(2d-data)` expects a two-dimensional data representation as input, and writes that data to the file in CSV format
+- `.writerows(2d-data)` expects a two-dimensional data representation as input, and writes that data to the file in CSV
+ format
- `writerow(list)` writes a single CSV row
+
```python
import csv
csvdata = csv.reader(open('filename.csv'))
@@ -159,29 +183,38 @@ for row in csvdata:
print(row)
#OrderedDict([('header', 'row'), ('value', ' value'), ('value', ' value')])
```
+
---
+
## Iterators
+
- `iterator = iter(str/list/tuple/set/dict)` initialize iterator
- `next(iterator)` returns next char/elem/key
- **there is no way to more backward**
+
### Iterators VS Sequences
+
||Iterators|Sequences|
|:--:|:--:|:--:|
-|Random Access|No random access|Has random acces
(you can access any element in the sequence, as many times as you like)|
+|Random Access|No random access|Has random acces
(you can access any element in the sequence, as many times as you
+like)|
|Position Tracking|Remembers where you are up to|No position tracking within the sequence|
|`len()`|cannot use|can use|
|Finity|Can be Infinite|Must be finite|
|Transverse|Can only transverse once, forwards|can transverse it many times|
## `itertools` Library
+
- `from itertools import permutations, combinations, product ...`
- `permutations(str/tuple/list/set/dict, r)` returns r-length tuples of permutations
- `combinations(str/tuple/list/set/dict, r)` returns r-length tuples of combinations
- `product(p, q, r, ...)` returns tuples of combination of p, q, r...; length of tuples = number of arguments
- `cycle(str/tuple/list/set/dict)` returns an infinite iterators that cycles through char/elem/keys
-- `groupby(str/tuple/list/set/dict, key(lambda criteria))` returns an iterator which generates 2-tuples: (group, elements-of-group)
+- `groupby(str/tuple/list/set/dict, key(lambda criteria))` returns an iterator which generates 2-tuples: (group,
+ elements-of-group)
## Unpacking
+
```python
# unpacking tuple
p0, p1 = (1, 2)
@@ -196,79 +229,102 @@ from itertools import permutations
for p0, p1 in permutations ("ABC", 2):
print(p0, p1)
```
+
---
+
## Data Types
+
### Integer
+
- `int(value)` convert value to **integer** (whole number)
+
### String
+
- `str(value)` convert value to **string** (chunk of text)
- - `str + str` string concatenation
- - `str * int` string replication
- - `str[index]` string indexing
- - `str[start-index:end-index:step]` string slicing (does not include end-index btw)
- - `str.upper()` make all uppercase
- - `str.lower()` make all lowercase
- - `str.strip(str)`
- - `str.split(str)` return a list of string-delimited substrings in string
- - f-strings
- - `f'string {variable/operation/etc.}'`
- - `f'{val : filling align window .precision format-code}'` format specifier
- - eg: `f'A float: {4.35:0<5.5f}'`
- -
- |Part|Explanation|
- |:--:|:--:|
- |`:`|Specifies value from format specifier|
- |`filling` (char)|fills extra space with filling (default=spaces)|
- |`<` left
`^` middle
`>` right
`,` thousand-separatign commas|aligns formatted text to the left/middle/right (default=right)|
- |`window` (int)|sets formatted windoe to be int characters long(default=length of fomatted value)|
- |`.precision` (int)|formats the number to int decimal places // indicates number of characters in string|
- |`f` float
`s` string
`g` 'optimal' float
`d` integer
`c` Unicode character|(default=auto)|
+ - `str + str` string concatenation
+ - `str * int` string replication
+ - `str[index]` string indexing
+ - `str[start-index:end-index:step]` string slicing (does not include end-index btw)
+ - `str.upper()` make all uppercase
+ - `str.lower()` make all lowercase
+ - `str.strip(str)`
+ - `str.split(str)` return a list of string-delimited substrings in string
+ - f-strings
+ - `f'string {variable/operation/etc.}'`
+ - `f'{val : filling align window .precision format-code}'` format specifier
+ - eg: `f'A float: {4.35:0<5.5f}'`
+ -
+ |Part|Explanation|
+ |:--:|:--:|
+ |`:`|Specifies value from format specifier|
+ |`filling` (char)|fills extra space with filling (default=spaces)|
+ |`<` left
`^` middle
`>` right
`,` thousand-separatign commas|aligns formatted text to the
+ left/middle/right (default=right)|
+ |`window` (int)|sets formatted windoe to be int characters long(default=length of fomatted value)|
+ |`.precision` (int)|formats the number to int decimal places // indicates number of characters in string|
+ |`f` float
`s` string
`g` 'optimal' float
`d` integer
`c` Unicode character|(default=auto)|
+
### Float
+
- `float(value)` convert value to **float** (real number)
+
### Boolean
+
- `bool(value)` convert value to **boolean** (truth value)
+
### List
+
- `list(value)` convert value to **list**
- - mutable
- - sequence of values
- - values can be different types
- - `list(range(start, end, step))` returns a list from start to end (end not included)
- - `list[index]` index can be positive (start from 0) or negative (start from -1)
- - `list[start-index: end-index: step-size]` returns elements from start-index to end-index (end-index not included)
- - `list.append(item)` append/add item to a list
- - `list.remove(item)` remove first instance of item from list
- - `list.pop(index)` remove item of index
- - `mylist.insert(index, item)` insert item in specific index in list - other items are moved backward
+ - mutable
+ - sequence of values
+ - values can be different types
+ - `list(range(start, end, step))` returns a list from start to end (end not included)
+ - `list[index]` index can be positive (start from 0) or negative (start from -1)
+ - `list[start-index: end-index: step-size]` returns elements from start-index to end-index (end-index not included)
+ - `list.append(item)` append/add item to a list
+ - `list.remove(item)` remove first instance of item from list
+ - `list.pop(index)` remove item of index
+ - `mylist.insert(index, item)` insert item in specific index in list - other items are moved backward
+
### Tuple
+
- `tuple(value)` convert *iterable* value (list, tuple, string) to **tuple**
- - immutable
- - sequence of values
- - values can be different types
+ - immutable
+ - sequence of values
+ - values can be different types
+
### Dictionary
+
- `d = {}` or `d = dict()` initilize dictionary
- `dict(value)` convert to **dictionary**
- collection of key and associated values
- keys must be unique
- values can be of different types
- Methods/Operations
- - `d[key]` access value associated to key - returns `KeyError` if key doesn't exist
- - `d.get(key)` returns value associated with key - returns `None` if key doesn't exist
- - `key in d` test for presence of key
- - `d.pop(key, no-key-value)` deletes the key-value pair and returns the value or returns no-key-value if key does not exist (default=None)
- - `del d[key]` deletes key-value pair
- - `d.copy()` makes a shallow copy of dictionary
- - `d.clear()` deletes all key-value pairs from the dictionary
+ - `d[key]` access value associated to key - returns `KeyError` if key doesn't exist
+ - `d.get(key)` returns value associated with key - returns `None` if key doesn't exist
+ - `key in d` test for presence of key
+ - `d.pop(key, no-key-value)` deletes the key-value pair and returns the value or returns no-key-value if key does
+ not exist (default=None)
+ - `del d[key]` deletes key-value pair
+ - `d.copy()` makes a shallow copy of dictionary
+ - `d.clear()` deletes all key-value pairs from the dictionary
- Iterables
- - `d.keys()` returns iterable collection of keys
- - `d.values()` returns iterable collection of values
- - `d.items()` returns iterable collection of 2-tuples (key, value)
- - `list(d)` same as `list(d.keys())`
+ - `d.keys()` returns iterable collection of keys
+ - `d.values()` returns iterable collection of values
+ - `d.items()` returns iterable collection of 2-tuples (key, value)
+ - `list(d)` same as `list(d.keys())`
+
#### Default Dictionaries
+
- `from collections import defaultdict (*as dd)` import default dictionary, *optional
- `d = defaultdictd(value-data-type)`
- Why better than dictionary?
- - Default dictionaries are useful because, when queried with a key which is not yet in the dictionary, rather than raising a `KeyError`, the default dictionary adds it as a new entry, with its value set to a default value.
+ - Default dictionaries are useful because, when queried with a key which is not yet in the dictionary, rather than
+ raising a `KeyError`, the default dictionary adds it as a new entry, with its value set to a default value.
+
#### Unpacking Dictionaries:
+
```python
my_dict = {'first': 1, 'second': 2, 'third': 3}
for key, value in my_dict.items():
@@ -277,7 +333,9 @@ for key, value in my_dict.items():
# second 2
# third 3
```
+
### Set
+
- stores unique data (removes duplicates)
- elements must be immutable type
- *unordered* collection
@@ -292,10 +350,11 @@ for key, value in my_dict.items():
- `set.issubset`
-- `len(str/list/tuple/set/dict)` used to find number of characters in a string or elements in a list/tuple/set or number of key-value pairs in a dict
+- `len(str/list/tuple/set/dict)` used to find number of characters in a string or elements in a list/tuple/set or number
+ of key-value pairs in a dict
- `sorted(str/list/tuple/set, reverse = True/False)` returns list of sorted characters/elements
- - `sorted(str/list/tuple/set, key=lambda x: x[])`
- - `sorted(str/list/tuple/set, key=lambda x: (x[], x[]))`
+ - `sorted(str/list/tuple/set, key=lambda x: x[])`
+ - `sorted(str/list/tuple/set, key=lambda x: (x[], x[]))`
- `list.sort(list)` sorts (mutates) the list
- `type(object)` find out type of object
- `chr(int)` convert integer to corresponding Unicode character
@@ -304,9 +363,11 @@ for key, value in my_dict.items():
- `id(object)` returns id of object
---
+
## Errors
-- `SyntaxError`
- - detected before you run
+
+- `SyntaxError`
+ - detected before you run
``` python
>>> a +
File "", line 1
@@ -338,8 +399,8 @@ for key, value in my_dict.items():
a = 1
IndentationError: unexpected indent
```
-- `Runtime Error`
- - detected while running
+- `Runtime Error`
+ - detected while running
```python
>>> a = b
Traceback (most recent call last):
@@ -367,15 +428,17 @@ for key, value in my_dict.items():
KeyError: 'a'
```
- `Logic Error` the intended output is not the same as actual output
- - detected after running
+ - detected after running
- `TypeError`
- `IndexError`
- `StopIteration` exception that is raised when the iterator runs out of objects to iterate over
---
+
## Statements and Expressions
+
- Expression: some code which generates an object
-- Simple Expression: contains 1 operator
+- Simple Expression: contains 1 operator
- Complex Expression: has more parts than a simple expression
- Logical Expression: evaluates to bool
- Statement: any valid line of Python code
@@ -390,16 +453,20 @@ for key, value in my_dict.items():
|`def my_func():`|||||β
|
---
+
## Extra
+
### `pillow` Library
+
- `import PIL.Image as pim` make pillowtools available in your program
- `image = pim.open(filename)` opens filename and returns an object of Image datatype
-- `image = pim.new(mode, size, bg_colour)` creates new blank image and returns an object of Image datatype
+- `image = pim.new(mode, size, bg_colour)` creates new blank image and returns an object of Image datatype
|field|details|
|:--:|:--:|
|mode|`"1"` black and white images
`"L"` greyscale images
`"RGB"` colour images|
|size|2-tuple`(width, height)`|
- |bg_colour|`0` black // `1` white
`int_between_0-255(inclusive)` 0 for black, 255 for white
`(R, G, B)` between 0-255(inclusive)|
+ |bg_colour|`0` black // `1` white
`int_between_0-255(inclusive)` 0 for black, 255 for white
`(R, G, B)` between
+ 0-255(inclusive)|
- `image.mode` returns the image's mode
- `image.size` returns image's size (2-tuple)
- `image.getpixel(address)` address is 2-tuple
@@ -408,21 +475,23 @@ for key, value in my_dict.items():
- Effects and transformations
### Data Science
+
- big data problems
- 4 broad steps
- 1. data wrangling
- 2. data visualisation
- 3. data analysis
- 4. empirical evaluation
+ 1. data wrangling
+ 2. data visualisation
+ 3. data analysis
+ 4. empirical evaluation
#### `matplotlib` LIbrary
+
- `import matplotlib.pyplot as plt`
- `ax = plt.axes()` generate aces to draw on
- `ax.bar(locations, heights, width)` draw bar chart within an axes` object
- - `locations` (list) the locations of the lower left corners of the bars
- - `heights` (list) their respective heights
- - `width` (list) width of the bars
+ - `locations` (list) the locations of the lower left corners of the bars
+ - `heights` (list) their respective heights
+ - `width` (list) width of the bars
- `ax.set_title(str)` set title
- `ax.set_ylabel(str)` set y label
- `ax.set_xlabel(str)` set x label (opt)
diff --git a/w1/python_setup.md b/w1/python_setup.md
index bba0c12..6756c90 100644
--- a/w1/python_setup.md
+++ b/w1/python_setup.md
@@ -1,12 +1,14 @@
Python Setup
* There are many ways to setup your Python Environment, we go through 2 ways in this document:
- |[Replit Setup](#replit-setup)|[VS Code Setup](#vs-code-setup)|
- |--|--|
- |platform for writing code online|code editor|
- |online - code is public
(when using a free account)|offline - code stored on your computer
(no one can access it unless you want them to)|
- |no downloads required; faster/easier to setup|requires a few downloads and more time to setup|
- |useful for small projects|more long-term solution for coding
(has lots of extensions that allow you to customise your experience)|
+ |[Replit Setup](#replit-setup)|[VS Code Setup](#vs-code-setup)|
+ |--|--|
+ |platform for writing code online|code editor|
+ |online - code is public
(when using a free account)|offline - code stored on your computer
(
+ no one can access it unless you want them to)|
+ |no downloads required; faster/easier to setup|requires a few downloads and more time to setup|
+ |useful for small projects|more long-term solution for coding
(has lots of extensions that allow you to
+ customise your experience)|
Table of Contents
@@ -18,27 +20,32 @@
-
## Replit Setup
+
> π Replit Documentation (Introduction to Replit): [https://docs.replit.com/](https://docs.replit.com/)
+
1. Create a Replit Account
- * Head over to [https://replit.com/](https://replit.com/) to create a replit Account
+ * Head over to [https://replit.com/](https://replit.com/) to create a replit Account
2. Create a Repl for this project
- * A Repl is like a folder to contain all of your code for a specific project
+ * A Repl is like a folder to contain all of your code for a specific project
3. That's it!
* you can now add files/folders into your Repl, write code, run code (press on the Green Triangle at the top), etc.
## VS Code Setup
-> π Python in Visual Studio Code Guide: [https://code.visualstudio.com/docs/languages/python](https://code.visualstudio.com/docs/languages/python)
+
+> π Python in Visual Studio Code
+> Guide: [https://code.visualstudio.com/docs/languages/python](https://code.visualstudio.com/docs/languages/python)
+
* Recommended VSCode Extensions:
- * [Python (extension)](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
- * [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance)
- * [Code Runner](https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner)
+ * [Python (extension)](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
+ * [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance)
+ * [Code Runner](https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner)
## Other Alternatives
+
* There are many other online/offline code editors that your can use such as:
- * [Atom](https://atom.io/): offline
- * [Codespaces](https://github.com/features/codespaces): online
- * [PyCharm](https://www.jetbrains.com/pycharm/): offline
- * [Sublime Text](http://www.sublimetext.com/): offline
- * many many more...
\ No newline at end of file
+ * [Atom](https://atom.io/): offline
+ * [Codespaces](https://github.com/features/codespaces): online
+ * [PyCharm](https://www.jetbrains.com/pycharm/): offline
+ * [Sublime Text](http://www.sublimetext.com/): offline
+ * many many more...
\ No newline at end of file
diff --git a/w2/README.md b/w2/README.md
new file mode 100644
index 0000000..f6b19f1
--- /dev/null
+++ b/w2/README.md
@@ -0,0 +1,516 @@
+[Participant's Workbook] Week 2: Memebot
+
+> Related Pages: [DecodED 3](./README.md)
+
+---
+
+Making a meme bot with MongoDB and an external API
+Table of Contents
+
+Table of Contents
+
+- [0. Host your bot on repl.it & Flask](#0-host-your-bot-on-replit-&-flask)
+ - [Repl.it](#replit)
+ - [Flask](#flask)
+- [1. Basic knowledge](#1-basic-knowledge)
+ - [API & HTTP requests](#api-&-http-requests)
+ - [Database](#database)
+- [2. Install necessary modules](#2-install-necessary-modules)
+ - [requests](#requests)
+ - [pymongo](#pymongo)
+ - [schema](#schema)
+- [3. Making HTTP requests to API](#3-making-http-requests-to-api)
+ - [Get](#get)
+- [4. Bind event listeners to command](#4-bind-event-listeners-to-command)
+ - [Command recognition](#command-recognition)
+- [5. Setup MongoDB account](#5-setup-mongodb-account)
+ - [MongoDB Atlas](#mongodb-atlas)
+ - [MongoDB URI](#mongodb-uri)
+- [6. Programmatically connect application to database](#6-programmatically-connect-application-to-database)
+ - [Insert](#insert)
+ - [Find](#find)
+ - [Delete](#delete)
+- [7. Defensive programming & error handling](#7-defensive-programming-&-error-handling)
+ - [404 Not Found](#404-not-found)
+ - [Validation schema](#validation-schema)
+ - [DB-lookup non-existent resource](#db-lookup-non-existent-resource)
+ - [Error-proof delete](#error-proof-delete)
+
+
+
+---
+
+## 0. Host your bot on repl.it & Flask
+
+### [Repl.it](https://replit.com/~)
+
+-[Host discord bot using repl.it](https://www.codementor.io/@garethdwyer/building-a-discord-bot-with-python-and-repl-it-miblcwejz)
+
+### [Flask](https://flask.palletsprojects.com/en/2.2.x/)
+
+Using the [Flask](https://www.fullstackpython.com/flask.html) framework.
+=> To create a server & keep the bot alive for a couple of hours (repl.it kills your hosted bot if not interacted with for a few hours)
+
+- Code for deployment => creating the server
+
+```python
+# ./keep_alive.py
+from flask import Flask
+from threading import Thread
+
+app = Flask('')
+
+@app.route('/')
+def home():
+ return "I'm alive"
+
+def run():
+ app.run(host='0.0.0.0',port=8080)
+
+def keep_alive():
+ t = Thread(target=run)
+ t.start()
+```
+
+Adding these lines in main.py (before setting up the `TOKEN`)
+
+```python
+# ./main.py
+from keep_alive import keep_alive
+keep_alive()
+TOKEN = os.environ.get("DISCORD_BOT_TOKEN")
+```
+
+## 1. Basic knowledge
+
+> π Before we dive in head-first into the world of wonderful tech, let's get to know the basics first!
+
+- This section assumes you haved followed along at least from step 0 to 6, and has a working basic Discord bot as well as some fundamental understanding of how Discord.py works
+
+### API & HTTP requests
+
+
+ β What are API & HTTP requests?
+
+
+
+- In programming, apps need a way to communicate with each other
+- The interface through which data is exchanged is called an API
+
+
+
+- Hyper-text Transfer Protocol - rules for transferring data over the internet
+- URL - unique resource locator β address of a resource stored somewhere remotely over the internet
+
+
+
+> Status code commonly encountered and will be part of today's program:
+
+- 200 - OK (everything went well!)
+- 400 - bad request (URL misspelled) β think address written in Martian
+- 404 - Error (specified address empty; not found) β think no oneβs home or address points to non-existent house
+
+
+
+### Database
+
+
+ β What is a database?
+
+- For database connection, weβll use [MongoDB](https://www.mongodb.com) - itβs a free database thatβs relatively simple to setup and use
+- Database is where we store data; under the hood everything is stored as binary - i.e. 1 & 0
+- Internally, each database has a different manner of storing data; some as entries in a table, some a bit differently
+- MongoDB stores data as documents (not the MS Word type though!) - data stored as fields in an object
+
+
+
+## 2. Install necessary modules
+
+### [`requests`](https://requests.readthedocs.io/en/latest)
+
+- Library to make HTTP requests to a specified API endpoint (URL), with some optional parameters (request headers)
+ ```
+ pip install -U requests
+ ```
+
+### [`pymongo`](https://github.com/mongodb/mongo-python-driver)
+
+- Driver library to maintain a connection to MongoDB, with query abilities
+ ```
+ pip install -U pymongo
+ ```
+
+### [`schema`](https://github.com/keleshev/schema)
+
+- Library to validate data structure via creating & reusing schema (blueprint objects)
+ ```
+ pip install -U schema
+ ```
+
+> π Let us know if you run into any errors during installation!
+
+## 3. Making HTTP requests to API
+
+### Get
+
+- Method used to fetch a resource at a specified URL
+
+ - Before we make this request, we need to import the installed library into our Python program
+
+ ```python
+ import requests
+ ```
+
+ - Let's make a function to make an API call
+
+ ```python
+ def get_random_memes(count):
+ response = requests.get(f"{API_ROOT}{count}") # String interpolation - this will become "https://meme-api.herokuapp.com/gimme/"
+ json_data = json.loads(response.text)
+ return json_data
+ ```
+
+ > π JSON, short for JavaScript Object Notation, is a file format frequently used for sending data over the internet. As what is actually transfered over is raw text written in JSON format, we have to explicitly parse that text into JSON before we can use it.
+
+ - To actually invoke our API call in our bot command, we simply call this function with the appropriate parameters
+ ```python
+ fetched_result = get_random_memes(1)
+ one_random_meme = fetched_result['memes'][0]
+ ```
+ > π Part of working with API is working with other people's code, AKA dangerous territory where we have no control over. We have no way of knowing for certain what the desired outcome will be, and the best we can do is to read through their documentation.
+ - The following API can be found [here](https://github.com/D3vd/Meme_Api) - have a read through, and see if you can explain why we extract a meme object via `one_random_meme = fetched_result['memes'][0]`
+
+ - Let's extend our API calls to also accept a subreddit name
+
+ ```python
+ def get_random_meme_from_subreddits(subReddit, count):
+ response = requests.get(f"")
+ json_data = json.loads(response.text)
+ return json_data
+ ```
+
+ - And same goes to invoking this function...
+ > π Let us know if you need a hint!
+
+## 4. Bind event listeners to command
+
+### Command recognition
+
+- Before we get started on any business logic, let's define a client event listener (i.e. user sends message in channel)
+
+```python
+@commands.command(name='new')
+async def _new(ctx, *args):
+
+ # Recognize first variation of command (i.e. ;;new)
+ # Respond accordingly
+
+ # Recognize second variation of command (i.e. ;;new 5)
+ # Respond accordingly
+
+ #...
+
+ pass
+```
+
+- Let's define our first event listener - when a user messages `;;new`
+
+ - `*args` takes in an arbitrary amount of parameters as a list
+ - By accessing how many parameters were extracted from a user's command invocation (i.e. `len(args)`), we can differentiate which variation of the command they were using
+ - We would like to respond with a single meme when the command is called without any additional parameter
+
+ ```python
+ if (len(arg) == 0):
+ random_meme = get_random_memes(1)['memes'][0]
+ await ctx.send(random_meme['url']) # Have bot send message to channel - i.e. "respond"
+ ```
+
+ > π Host your bot, message `;;new` into the chat, and see if it works!
+
+- A bit trickier, let's define our second event listener - when a user messages `;;new 3`
+
+ - We would like to extract the number of memes (in this case 3, but ideally any numeric value will do!)
+ - In this case, the number of passed parameters will be one (i.e. `len(args) == 1`)
+ - We extract this number via the first element of the `args` list (i.e. `args[0]`)
+ - And respond with the correct number of memes
+
+ ```python
+ if (len(args) > 0):
+ random_memes = get_random_memes(int(args[0]))['memes']
+ random_memes_image_link = [e['url'] for e in random_memes] # Destructure response to get all memes' URLs
+ for link in random_memes_image_link: # Iterate over all fetched URLs & respond
+ await ctx.send(link)
+ ```
+
+ > π `random_memes_image_link = [e['url'] for e in random_memes]` - this particular quirky syntax is Python's list comprehension - it's a really powerful tool to quickly reshape data structure to our use case, if leveraged properly. See if you can reason about this line!
+
+- Moving on to our third event listener - when a user messages `;;subreddit wholesomememes`
+
+ - First, we need to rig this event listener to respond to the right command
+
+ ```python
+ @commands.command(name='subreddit')
+ async def _subreddit(ctx, arg1):
+ subreddit = arg1
+ ```
+
+ > π Instead of passing in `*args`, we are now using `arg1`. See if you can reason about this particular change!
+ > Hint - notice the intented different behaviour between our two commands
+
+ - With the subreddit name extracted, we can now implement the functionality
+
+ ```python
+ random_meme = get_random_meme_from_subreddits(subreddit, 1)
+ await ctx.send(random_meme_subreddit['url'])
+ ```
+
+ #### **π‘ Challenge**
+
+ Right now, our bot assumes the user would always want to fetch a single meme whenever they specify a subreddit name. Can you refactor the code to provide some additional flexibility - i.e. allow the user to specify how many memes to fetch after the subreddit name?
+
+- We have a few event listeners left to define regarding database, but with the knowledge we have right now we can define the command recognition straight away
+
+ - An event listener to respond to `;;save best_meme_ever` -> extract the user-defined name `best_meme_ever` for now; we'll work more on it later
+ - `;;load best_meme_ever` -> extract the name `best_meme_ever`
+ - `;;delete best_meme_ever` -> `best_meme_ever`
+ > π Hint: the name is all the way at the end of the string, so it's actually pretty similar to how we just extracted the subreddit name from our previous event listener
+
+- Different from the rest, our `save` event listener is a bit more quirky
+
+ - We'd like it to save a meme we replied to when calling the command `;;save name_here`
+ - We need to rig this behaviour (and provide some fault tolerance while we're at it - i.e. user-friendly error message if broken)
+
+ ```python
+ @commands.command(name='save')
+ async def _subreddit(ctx, arg1):
+ try:
+ response = await ctx.channel.fetch_message(ctx.message.reference.message_id) # this reads the message that the command-caller was responding to
+ meme_url = response.content # this extracts the meme's URL from the original message
+ except print(0):
+ await ctx.send("You forgot to reply to a meme, or the meme you replied to wasn't saved in the right format")
+ pass
+ ```
+
+ - And from there on out, we can refer to the URL of the specified meme simply via the variable `meme_url`
+
+## 5. Setup MongoDB account
+
+### [MongoDB Atlas](https://www.mongodb.com/atlas/database)
+
+
+First, we need to create a MongoDB account & a test database
+
+- Click on βtry for freeβ
+- Click on βbuild my first clusterβ
+- Pick a cloud provider (let's go with AWS)
+- Pick the free-tier cluster that is the closest to us
+- Wait 7-10 minutes for memory allocation to happen on a physical machine
+- Create the database we will use today β click on βCluster0β >> "Collections" tab >> "Add my own data"
+- Give the database a name & the collection a name -> βtest_discord_botβ, βmemesβ β Click on "Create"
+
+
+
+### MongoDB URI
+
+
+Now we need to provide our application with access rights
+
+- Click on "Project 0" >> "Connect" >> "Go back"
+- Click on βWhitelist IP address" β set the IP address that is allowed to access the database (for this example, let's make it so that everyone can access the database - 0.0.0.0)
+- Create a MongoDB user with username & password to login
+- Choose a connection method >> "Connect your application"
+- Choose "Python" - this will generate the connection string appropriate to use for a Python application >> "version 3.6 or later" >> Copy βconnection string onlyβ
+- Paste this under MONGODB_URI in .env and import it in our application
+
+
+
+> π Let us know if you run into any errors during the process!
+
+## 6. Programmatically connect application to database
+
+- First we need to create the connection to the database
+ - We need to use MongoClient - it's an object that represents the client (us!)'s connection to the database
+ - Import it in our code
+ ```python
+ from pymongo import MongoClient
+ ```
+ - Create it with the `MONGODB_URI` we saved to our `.env` earlier
+ ```python
+ cluster = MongoClient(MONGODB_URI)
+ ```
+ - With it, access the specific `DB` & `COLLECTION` we created earlier for our meme bot
+ ```python
+ db = cluster[DB]
+ collection = db[COLLECTION]
+ ```
+
+### Insert
+
+- Before we can insert anything into the database, we need to create a document - basically a Python dictionary, with the keys holding the values we'd like to save
+
+```python
+meme1 = {
+ "_id": 1, # auto-generated if not provided
+ "name": "Leo Dicaprio drinking",
+ "url": "https://link_to_meme_img"
+}
+```
+
+- Now, let's insert our new meme into the database
+
+```python
+collection.insert_one(meme1)
+```
+
+#### **π‘ Challenge**
+
+With the knowledge you know have, can you fill in the `;;save best_meme_ever` event listener we defined earlier to provide the intended functionality?
+
+> π Hint: we would like it to save a meme with the specified name to the database - it should also be saved with the previously-extracted `meme_url`
+
+### Find
+
+- Let's make a find query
+
+```python
+results = collection.find_one({"name": "Leo Dicaprio drinking"})
+```
+
+- Print out the result and see if it works!
+
+#### **π‘ Challenge**
+
+With the knowledge you know have, can you fill in the `;;load best_meme_ever` event listener we defined earlier to provide the intended functionality?
+
+> π Hint: we would like it to load a meme with the specified name from the database
+
+### Delete
+
+- Finally, let's make a delete query
+
+```python
+collection.delete_one({"name": "Leo Dicaprio drinking"})
+```
+
+> π Delete operation doesn't give a meaningful return value, which means error-proofing this type of operation is a bit more tricky - more on this later
+
+#### **π‘ Challenge**
+
+With the knowledge you know have, can you fill in the `;;delete best_meme_ever` event listener we defined earlier to provide the intended functionality?
+
+> π Hint: we would like it to delete a meme with the specified name from the database
+
+## 7. Defensive programming & error handling
+
+- We donβt want anyone to see a nasty bug!
+- Bury them deep under our error handler & provide a user-friendly error message instead!
+
+### 404 Not Found
+
+- Our API calls can resolve to an error - in this case, they will throw a `HTTPError` exception
+- First, let's import this error definition into our code
+
+```python
+from requests.exceptions import HTTPError
+```
+
+- Anticipate & intercept this behaviour with a `try/catch` block
+
+```python
+try:
+ response = requests.get(f"{API_ROOT}{subReddit}/{count}")
+except HTTPError as error:
+ return error
+```
+
+- Sometimes, the error can occur from the server-side - i.e. our API call resolves successfully, but the response is actually an error message (typically denoted by status code `404 Not Found`)
+- This error oftentimes result from a malformatted URL, but it's possible that it is caused by a faulty resource (i.e. the URL to the image is still pointing to the right location, but the image is already deleted from the server)
+- Let's define our own error handling function
+
+```python
+def check_link_die(link):
+ try:
+ response = requests.get(link)
+ except:
+ return False
+ if(response.status_code == 404):
+ return False
+ return True
+```
+
+- And invoke it recursively whenever we fetch a meme until we get a verified working image
+
+```python
+random_meme = get_random_memes(1)['memes'][0]
+while(check_link_die(random_meme['url']) == False):
+ random_meme = get_random_memes(1)['memes'][0]
+```
+
+#### **π‘ Challenge**
+
+Locate all meme-fetching operations in our code and refactor them to guarantee we can always get a working meme
+
+> π Let us know if you need a hint!
+
+### Validation schema
+
+- We don't want to insert a faulty meme into our database (i.e. one with a malformatted URL)
+- To enforce correct data shape (i.e. what fields it contains, what value in each field is) we define a `schema`
+- First, import the necessary modules from the installed library into our program
+
+```python
+from schema import Schema, SchemaError, And, Regex
+```
+
+- Now let's define our `meme_schema`, AKA what we would like a meme object to look like
+
+```python
+meme_schema = Schema({
+ "name": str,
+ "url": And(str, Regex("^(https:\/\/i.redd.it\/)\w+\.\w{3}$"))}) # URL must be a string matching the provided RegEx pattern
+```
+
+> π Donβt worry about the RegEx - even we donβt fully grasp it! Basically itβs witchtalk in tech, but it is quite useful for whitelisting a certain string pattern. This RegEx pattern here matches `https://i.redd.it/abcdxyz.png` - where `abcxyz` is a variable-length alphanumeric string, and `png` is a 3-character alphanumeric string representing the file format (e.g. `png`, `jpg`)
+
+- Finally, we will validate a meme to our `meme_schema` before posting it to our collection. If it doesn't pass validation, a `SchemaError` exception will be thrown, which we can intercept using the now-familiar syntax `try/catch` block
+
+```python
+try:
+ meme_schema.validate(saved_meme)
+except SchemaError as err:
+ await ctx.send(err)
+ return
+collection.insert_one(saved_meme)
+```
+
+### DB-lookup non-existent resource
+
+- If we try to lookup a non-existent meme from the database, the response will be empty
+ - If we try to access a named property on an empty data structure, we will get a `TypeError` exception
+
+```python
+try:
+ found_meme = collection.find_one({"name": lookup_name})
+ await ctx.send(found_meme["url"])
+except TypeError:
+ await ctx.send("Meme not found in database!")
+```
+
+### Error-proof delete
+
+
+β Idempotence - what is it, and what has it got to do with the DELETE operation?
+
+- According to RESTful conventions & standard (rules to conform to when designing API), DELETE is IDEMPOTENT
+ - This means they have no side-effect when performed twice (i.e. they arenβt supposed to be aware of whether they achieved the desired effect β whether truly deleted an existing resource, or got brushed off from the database due to referencing a non-existent resource)
+ - Unfortunately, this means for work for us - we need to distinguish between a successful deletion operation & a βgood try, I attemptedβ one
+
+
+
+- Our solution is to perform a separate find query beforehand, to identify if the resource we are trying to delete even exists in the first place
+
+#### **π‘ Challenge**
+
+Implement the modified, safer version of our delete command - whether or not it succeeds in deleting a resource, the user is informed accordingly afterwards
+
+> π Let us know if you need a hint!
diff --git a/w2/mdcontent/images/API.png b/w2/mdcontent/images/API.png
new file mode 100644
index 0000000..b35c93a
Binary files /dev/null and b/w2/mdcontent/images/API.png differ
diff --git a/w2/mdcontent/images/HTTP methods.jpeg b/w2/mdcontent/images/HTTP methods.jpeg
new file mode 100644
index 0000000..1522be2
Binary files /dev/null and b/w2/mdcontent/images/HTTP methods.jpeg differ
diff --git a/w2/mdcontent/images/HTTP request.png b/w2/mdcontent/images/HTTP request.png
new file mode 100644
index 0000000..930c1ed
Binary files /dev/null and b/w2/mdcontent/images/HTTP request.png differ
diff --git a/w3/README.md b/w3/README.md
new file mode 100644
index 0000000..d7d295d
--- /dev/null
+++ b/w3/README.md
@@ -0,0 +1,107 @@
+[Participant's Workbook] Building a Music Bot
+
+> Related Pages: [DecodED 3](./README.md)
+
+---
+
+Table of Contents
+
+Table of Contents
+
+- [0. Downloading ffmpeg](#0-create-a-discord-account-and-discord-server)
+ - [For Windows](#create-a-discord-account)
+ - [For Mac](#create-a-discord-server)
+- [1. Installing Modules: `youtube_dl`](#1-create-an-application)
+- [2. Setting up the join and leave commands](#2-installing-modules-discordpy-and-python-dotenv)
+- [3. Streaming audio to discord](#3-creating-a-bot-and-adding-it-to-your-server)
+- [4. Pausing and playing audio](#4-make-the-bot-say-hello-world)
+- [5. Sending the Youtube Thumbnail](#5-adding-commands)
+
+
+
+---
+
+## 0. Downloading ffmpeg
+
+
+β What is Lavalink?
+Lavalink is a library that allows you to play music on discord using the [Lavalink]() library.
+
+
+
+
+* We need to download ffmpeg first so that we can stream music.
+
+### For Windows
+
+Simply click on this link and ffmpeg will start
+downloading (https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-2022-07-24-git-39a538f430-full_build.7z)
+
+### For Mac
+
+Simply click on this link and ffmpeg will start downloading (https://evermeet.cx/ffmpeg/ffmpeg-107536-g8daa0fea9f.zip)
+
+## 1. Un-ziping and copying the ffmpeg.exe path
+
+Un-zip the ffmpeg and than open the folder, open bin, from there copy the path of ffmpeg.exe which we will use while
+adding ffmpeg.
+
+## 1. Installing Modules: 'youtube_dl'
+
+* Before we begin creating the bot, we have to install a few modules
+* Install `youtube_dl`
+ * youtube_dl is an open source download manager for youtube video and it will help us play our youtube videos audio
+ in real time.
+ * to install it, type this into your terminal:
+ ```
+ pip install youtube_dl
+ ```
+ * now you need to add some youtube_dl option in your code
+ ```python
+ # youtube best audio options
+ yt_dl_opts = {'formats': 'beastaudio/best'}
+ ytdl = youtube_dl.YoutubeDL(yt_dl_opts)
+ ```
+
+## 2. Setting up the join and leave commands
+
+* We first need to set up a COG to put all of our code in:
+
+```python
+class MusicBot(commands.Cog):
+ def __init__(self, bot):
+ self.bot = bot
+
+```
+
+Our bot needs commands to join and leave the current audio server the user is on.
+To do this, you can use the on_event functions:
+
+* We now need to make sure that when ever we run the code, we can see that our bot has successfully logged-in. For that
+ we need to write a function on_ready.
+ ```python
+ # Start the bot
+ @client.event
+ async def on_ready():
+ print('Logged in as')
+ print(client.user.name)
+ print(client.user.id)
+ print('------')
+
+ ```
+
+## 4. Pausing and playing audio
+* Now we need our bot to pause and play the music. We can do that by the cogs me made and simply using this code.
+```python
+ # to pause the music
+ @commands.command()
+ async def pause(self, ctx):
+ await ctx.voice_client.pause()
+ await ctx.send("Paused")
+ # to resume the music
+ @commands.command()
+ async def resume(self, ctx):
+ await ctx.voice_client.resume()
+ await ctx.send("Resumed")
+```
+Now when ever you will add 'Paused' in discord it will pause the music and when you will add 'Resumed' it will resume the song again.
diff --git a/w4/Figure_1.png b/w4/Figure_1.png
new file mode 100644
index 0000000..bfab080
Binary files /dev/null and b/w4/Figure_1.png differ
diff --git a/w4/README.md b/w4/README.md
new file mode 100644
index 0000000..eb8987f
--- /dev/null
+++ b/w4/README.md
@@ -0,0 +1,179 @@
+# discord-bot-poll-system
+[Participant's Workbook] Create polling using Discord.py
+
+Table of Contents
+
+Table of Contents
+
+---
+- [0. Installing Modules: `discord.py` and `python-dotenv`, `asyncio`, `matplotlib`, `numpy`, `os`, `emoji`](#0-installing-modules)
+ - [β
Task: Install `discord.py`](#-task-install-discordpy)
+ - [β
Task: Install `python-dotenv`](#-task-install-python-dotenv)
+ - [β
Task: Install `asyncio`](#-task-install-asyncio)
+ - [β
Task: Install `matplotlib`](#-task-install-matplotlib)
+ - [β
Task: Install `numpy`](#-task-install-numpy)
+ - [β
Task: Install `os`](#-task-install-os)
+ - [β
Task: Install `emoji`](#-task-install-emoji)
+- [1. β
Task: Create a Cog for all your 'Poll' commands](#1-create-a-cog-for-all-your-poll-commands)
+ - [π Outcome: What are we building for this task?](#-outcome-what-are-we-building-for-this-task)
+ - [β
Task: Import essential modules](#-task-import-essential-modules)
+ - [β
Task: Create a constructor for the Poll Cog](#-task-create-a-constructor-for-the-Poll-Cog)
+ - [β
Task: Get inputs from users before sending back the poll](#-task-get-inputs-from-users-before-sending-back-the-poll)
+ - [β
Task: Send back an Embed as a poll](#-task-send-back-an-embed-as-a-poll)
+ - [β
Task: Use emoji module to convert strings to emojis and add fields and footer](#-task-use-emoji-module-to-convert-strings-to-emojis-and-add-fields-and-footer)
+ - [β
Task: Add emojis to complete the poll and set time for users to vote](#-task-add-emojis-to-complete-the-poll-and-set-time-for-users-to-vote)
+- [2. [π‘ Extension] implement web scrapping with Selenium to customise your poll](#2--extension-implement-web-scrapping-to-customise-your-poll)
+- [Related Links:](#related-links)
+
+
+
+---
+
+## 0. Installing Modules
+
+> π NOTE: You can skip this step if you're using **Replit** as it
+> can [automatically import](https://docs.replit.com/programming-ide/installing-packages#direct-imports) packages/modules
+> it for you
+
+* Now we're going to start writing the code for our bot!
+* Before we begin creating the bot, we have to install a few modules
+
+
+ π§© Hint: Accessing the Terminal in VS Code
+
+* There are 2 ways to access the terminal in VS Code:
+ 1. using the shortcut ```Ctrl + Shift + ` ```
+ 2. Terminal > New Terminal
+
+ * 
+
+
+
+### β
Task: Install `discord.py`
+
+* `discord.py` is basically a set of tools which will allow us to control our bot with simple function calls.
+* you can find the documentation for `discord.py` over [here](https://discordpy.readthedocs.io/en/stable/index.html#)
+* to install it, type this into your terminal:
+ ```
+ pip install -U discord.py
+ ```
+
+### β
Task: Install `python-dotenv`
+
+* `python-dotenv` is used to access our secret Discord token, which we will store in a `.env` file
+* you can find the documentation for it over [here](https://pypi.org/project/python-dotenv/)
+* to install it, type this into your terminal:
+ ```
+ pip install -U python-dotenv
+ ```
+
+### β
Task: Install `asyncio`
+
+* `asyncio` is used to write asynchronous code. Asynchronous code lets you execute a block of code without stopping (or blocking) the entire thread where the action is being executed.
+* you can find the documentation for it over [here](https://docs.python.org/3/library/asyncio.html)
+* to install it, type this into your terminal:
+ ```
+ pip install -U asyncio
+ ```
+
+### β
Task: Install `matplotlib`
+
+* `matplotlib ` is a Python library used to create chart visualization[here](https://matplotlib.org/)
+* to install it, type this into your terminal:
+ ```
+ pip install -U matplotlib
+ ```
+
+### β
Task: Install `os`
+
+* `os` is a module providing functions to interact with the operating system [here](https://docs.python.org/3/library/os.html)
+* to install it, type this into your terminal:
+ ```
+ pip install -U os
+ ```
+### β
Task: Install `emoji`
+* `emoji` is a library allowing us to convert string to emoji [here] https://pypi.org/project/emoji/
+* to install it, type this into your terminal:
+ ```
+ pip install -U emoji
+ ```
+> π **Ask for help**: Let us know if you run into any errors during installation and we'll try to help you out!
+
+---
+
+## 1. Create a cog for all your poll commands
+### π Outcome: What are we building for this task?
+
+
+
+### β
Task: Import essential modules
+ * `discord`, `asyncio`, `matplotlib`, `numpy` are modules we want to use to create a Poll Cog. So please import them:
+ ```
+ import discord
+ import asyncio
+ import matplotlib.pyplot as plt
+ import numpy as np
+ from discord.ext import commands
+
+ ```
+### β
Task: Create a constructor for the Poll Cog
+* The Poll class is a subclass of `commands.Cog` class.
+* We need a constructor to create the Cog class. This constructor can be done by using `init` method.
+* The `init` method should take the `bot` attribute to store information about our bot.
+ ```
+ def __init__(self, bot):
+ self.bot = bot
+ ```
+### β
Task: Get inputs from users before sending back the poll
+ * We need to write a function `init_poll` allowing us to create a poll command. A way for us to set the command is using decorator @commands.command(aliases=["p"]) above declaration of the `init_poll` function. This means when a user type `!p` , we will have a poll command.
+ * However, that's not enough as a user need to give a poll question, time for the poll and options for others to vote => give 3 more attributes for `init_poll` include question, time, *options.
+ ```
+ async def init_poll(self, ctx, question, time, *options):
+ ```
+ * You can see we also need to have `self` and `ctx`. We need `self` because it is a function declared inside the class. We need `ctx` because when user type their `!p` command, having the `ctx` attribute will allow us to have access to the `Context` object of the command.
+ *
+ * Next, we need apply a condition for our poll. You can customise how many options permitted for a poll. In this tutorial, there are maximum 3 options allowed.
+ ```
+ if len(options) > 3:
+ await ctx.send("The number of options cannot exceed the allowed limit")
+ ```
+ * you can notice that we are making use of ctx attribute to send back a message to the server. The data type of what is returned from ctx.send(...) is discord.Message. Here is more information about discord.Message: https://discordpy.readthedocs.io/en/stable/api.html#discord.Message
+### β
Task: Send back an Embed as a poll
+ * The discord bot has to send back the message to the server through `ctx.send(embed = Embed)`.
+ * A reason why we cannot send back a string like what we previous did is because our poll have a lot of information like time, question, given options. Hence, it is gonna messy if we put all of them into a string.
+ * Instead, make use of `embed attribute`. To do that, we first need to create an Embed object as following:
+ ```
+ embed = discord.Embed(title = question,
+ description= f'Poll will end in {time} seconds :alarm_clock:. There are {len(options)} options:')
+ ```
+### β
Task: Use emoji module to convert strings to emojis and add fields and footer
+ * To generate a list of 3 emojis: 1οΈβ£, 2οΈβ£, 3οΈβ£ , we need to generate a list of string and convert them to emojis
+ ```
+ tmp = [':one:', ':two:', ':three:']
+ emojis = [emoji.emojize(e, use_aliases=True) for e in tmp]
+ ```
+ * Because 1οΈβ£, 2οΈβ£, 3οΈβ£ are recent emojis added to the emoji package. The old ones are usually faces: π, βΊοΈ, etc . Hence DO NOT FORGET `use_aliases=True` to convert strings to the package.
+ * Next, we want to add option fields. Run a for loop for it:
+ ```
+ for i in range(len(options)):
+ emo = emojis[i]
+ embed.add_field(name = emo, value = options[i], inline = True)
+ ```
+ * Our poll looks much nicer if 3 choices are horizontally aligned. Hence DO NOT FORGET `inline = True`.
+ * Then, add another field for instruction:
+ ```
+ embed.add_field(name="Instructions", value="React to cast a vote", inline=False)
+ ```
+ * Finally, add footer by following instruction below:
+
+
+### β
Task: Add emojis to complete the poll and set time for users to vote
+ * Add emojis for users to vote
+ ```
+ for emo in emojis:
+ await poll.add_reaction(emo)
+ ```
+ * Finally we can set the the time limit for the vote
+ ```
+ await asyncio.sleep(int(time))
+ ```
diff --git a/w4/chart.png b/w4/chart.png
new file mode 100644
index 0000000..95eda7e
Binary files /dev/null and b/w4/chart.png differ
diff --git a/w4/oldReadme.md b/w4/oldReadme.md
new file mode 100644
index 0000000..91a33d3
--- /dev/null
+++ b/w4/oldReadme.md
@@ -0,0 +1,42 @@
+This is the discord bot built by using discord.py library and Selenium package to automate web browser interaction with Python. I also used Math plot library to visualize the voting results as a output.
+What's special about this application is that when a poll maker creates a question and gives suggestions for other members to vote on the discord channel: there will be web site running automatically to search for corresponding emojis from this API: https://emojipedia.org and provide them to the bot. Hence, members can vote by the emoji. After a specific limit of time, the discord bot will close the poll and return the result.
+# Example output: 
+CLICK TO THIS LINK TO SEE THE VIDEO: https://www.youtube.com/watch?v=mFfwhWzvYGI
+
+# Installation
+There are 3 important components needed to install: python3 + web automation + discord bot.
+1. Python3:
+- Install python3: https://www.python.org/downloads/
+3. Web Automation:
+- First install selenium by running this command in cmd: pip install selenium
+- Since the application uses Chrome Driver as the web driver version 104, so please make sure that you install the Driver version 104 and Google Chrome 104 to your desktop. Here are the links:
+- https://chromedriver.chromium.org/downloads
+- https://www.google.com/intl/en_au/chrome/beta/
+4. Discord Bot
+- Please install these follow packages: discord, asyncio, matplotlib, numpy, os, dot.env
+- For example: run "pip install discord"
+
+# Lessons
+# 1. Discord bot without dynamic emojis
+# 1. Create Main.py
+ STEP 1: import some already installed packages to your file (you will know why when we start using them).
+
+ STEP 2: create the discord bot. You can see we make use of commands module that we imported initially to create the discord bot here. You can put other string for the command prefix, for example, command_prefix = "." instead of "$".
+
+ STEP 3: create an asynchronous event listener that let us knows when our bot is ready. "The bot is ready" will print out when our bot starts running later on. You can see we are making use of
+
+ STEP 4: Finally, loading the token that generated in the "Set up discord bot" before.
+ to run our bot:
+
+
+# 2. Create Poll.py
+STEP 1: again, import some installed packages to the file.
+
+
+STEP 2: declare the subclass of commands.Cog.
+
+ - You can see __int__ method is the constructor of our class to create the Poll object later on in the Main.py file. We will need to have pass the already created bot object in the Main.py file later so that why we have to add it as an argument here.
+
+STEP 3: Now, it is time for us to declare a function and that function is decorated by discord.ext.commands.command to transform into to be discord.ext.commands.Command.
+
+ - Inside the decorator, you can see we passed "p" as an argument for "aliases". "aliases" is an array of string and it is an attribute of discord.ext.commands.Command class. It allows us to have mutilple names for our commands. You can add another string, for example, "poll" into that array.
diff --git a/w5/README.md b/w5/README.md
new file mode 100644
index 0000000..86fb335
--- /dev/null
+++ b/w5/README.md
@@ -0,0 +1,220 @@
+TicTacToe Bot
+
+---
+
+Table of Contents
+
+- [Object Oriented Concepts](#object-oriented-concepts)
+ - [Class and Objects](#class-and-objects)
+ - [Inheritance](#inheritance)
+- [1. Create Bot Object](#1-create-bot-object)
+- [2. Create TicTacToe Button](#2-create-tictactoe-button)
+- [3. Create TicTacToe Board](#3-create-tictactoe-board)
+- [4. TicTacToe Buttons handle click](#4-tictactoe-buttons-handle-click)
+- [5. Game Logic](#5-game-logic)
+- [6. Create Slash Commands](#6-create-slash-commands)
+
+## Object Oriented Concepts
+
+### Class and Objects
+
+
+
+
+A class is an abstract blueprint used to create more specific objects. On the other hand, an object is an instance
+derived from a class.
+
+### Inheritance
+
+
+
+
+Inheritance is one of the most important aspects of OOP. It allows classes to inherit features of other classes. Put
+another way, parent classes extend attributes and behaviors to child classes. Inheritance supports reusability.
+
+---
+
+## 1. Create Bot Object
+
+To create our guild attribute and bot token, we first need to create an environment file (link) and within it, we need
+to include .
+
+discord slash commands take 1-2 hours to sync globally (since they are doing it for every guild). Therefore, we can pass
+in specific guilds where the commands we create will sync instantly.
+
+First, we need to define a `Bot` class. The `Bot` class we define inherits methods and attributes defined by
+the `commands.Bot` class provided by the library. We then add our bot token and guilds to the class. We also need to
+define a `setup_hook()` method to intitialize the COG extension which contains the tictactoe game engine.
+
+```Python
+class Bot(commands.Bot):
+ def __init__(self):
+ super().__init__(command_prefix=" ",
+ intents=discord.Intents.all())
+ self.GUILDS = GUILDS
+ self.TOKEN = TOKEN
+
+ async def setup_hook(self):
+ await self.load_extension(f"tictactoe")
+ for guild in self.GUILDS:
+ await self.tree.sync(guild=guild)
+
+ async def on_ready(self):
+ print("ready...")
+```
+
+
+What is self?
+
+`self` is simply a keyword used to represent an instance (object) of the given class
+
+
+
+
+What is super()?
+
+`super()` is a reference to superclass (parent) objects
+
+
+
+
+## 2. Create TicTacToe Button
+
+We wil now create our `TicTacToeButton` class. The objects from this class will represent the clickable buttons of the
+TicTacToe board. The class will inherit the `discord.ui.Button` class from the discord.py library.
+
+We will first initialise the style and position of the button:
+
+```python
+def __init__(self, row, col):
+ super().__init__(style=discord.ButtonStyle.secondary, label=" ", row=row)
+ self.row = row
+ self.col = col
+```
+
+The different styles of a discord button can be
+found [here](https://user-images.githubusercontent.com/88476243/141746269-aaea9f9b-8f8e-4c59-9b67-75ec6e19d878.png)
+
+## 3. Create TicTacToe Board
+
+The `TicTacToe` class represents the TicTacToe board users play on. It inherits the `discord.ui.View` class from the
+discord.py library.
+
+The TicTacToe board will need to store state of the board:
+
+1. Player objects of current players
+2. Specify current player (by default player1 starts first)
+3. Create board
+ two-dimensional list initialized using numpy library
+4. Fill board with tictactoe buttons defined earlier
+
+Thus the initialisation for the Board:
+```python
+def __init__(self, player_1, player_2):
+ super().__init__()
+ self.player_1 = player_1
+ self.player_2 = player_2
+ self.current_player = self.player_1
+ self.board = np.zeros((SIZE, SIZE))
+
+ for row in range(SIZE):
+ for col in range(SIZE):
+ self.add_item(TicTacToeButton(row, col))
+```
+
+## 4. TicTacToe Buttons handle click
+
+The code for handling button clicks needs to be placed within this function (as per the docs) under
+the `TicTacToeButton` class:
+```Python
+async def callback(self, interaction):
+```
+
+This function is called whenever the button is clicked. Here are some of the variables you might need to make the
+required changes for a button click:
+```Python
+interaction.user # The user that's clicking the button
+self.style # The style of the current button
+self.label # The text on the current button
+self.disabled # Specifies whether the button is disabled: True / False
+self.view.board[self.row][self.col] # The current state of the game board (as defined in Step 3)
+self.view.current_player # The current player that's supposed to play
+```
+
+Here's what should be included within this function:
+- Update color and label of button to color of player that clicked
+- Disable the button to prevent further interaction
+- Update tictactoe board
+ - 1 if player1 clicked
+ - -1 if player2 clicked
+- Response to acknowledge click of button
+
+At the end of the function, you will need to update the board to reflect the latest changes. You can also include a
+message at the top the board by specifying a `content`. This is done like so:
+```Python
+await interaction.response.edit_message(content=content, view=self.view)
+```
+
+## 5. Game Logic
+
+Create the tictactoe logic for deciding a winner within a function called `check_winner(self)` that's placed inside
+the `TicTacToe` class.
+
+You can access the current state of the board by using `self.board`.
+
+
+Hint #1
+Take the sum for a given row/column if its equal to size of the board then we know there is a winner
+
+
+
+Hint #2
+We also have to check for diagonal winners using list indexing
+
+
+## 6. Create Slash Commands
+
+As a final step to bring our TicTacToe bot to life, we will use slash commands to interface with our bot. To create a
+slash command we will need to achive the following:
+
+- define the command name and description
+- define user input (discord member)
+- define the user input description
+- acknowlege command by sending tictactoe board view
+
+We now create a Commands class which inherits methods and attributes from the `commands.Cog` class provided by the
+library. This allows us to store the TicTacToe game engine as a COG in its own file. We also need to create a function
+to run when a slash commands event is raised.
+
+```python
+class Commands(commands.Cog):
+ def __init__(self, bot):
+ self.bot = bot
+
+ @app_commands.command(
+ name="tictactoe", # slash command name
+ description="Play a game of TicTacToe" # slash command description
+ )
+ @app_commands.describe(
+ opponent="User you want to play with" # user input description
+ )
+ async def tictactoe(self, interaction, opponent: discord.Member):
+ player_1 = interaction.user
+ player_2 = opponent
+
+ if player_1 == player_2 or player_2.bot:
+ await interaction.response.send_message("you cannot challenge that user")
+ else:
+ await interaction.response.send_message(
+ f":game_die: `{player_1.display_name}` **VS** `{player_2.display_name}`\n\n{player_1.mention}, select your move:",
+ view=TicTacToe(player_1, player_2))
+```
+
+sources
+
+- https://adrian-td96.medium.com/oop-for-dummies-3e6007c8e7f4#:~:text=Inheritance%20is%20one%20of%20the,Inheritance%20supports%20reusability
+ .
+- https://www.programiz.com/cpp-programming/inheritance
+- https://javatutorial.net/java-oop/
+
+[docs](https://gist.github.com/lykn/bac99b06d45ff8eed34c2220d86b6bf4)
\ No newline at end of file
diff --git a/w5/class-object-featured-image.png b/w5/class-object-featured-image.png
new file mode 100644
index 0000000..203618d
Binary files /dev/null and b/w5/class-object-featured-image.png differ
diff --git a/w5/images/class.png b/w5/images/class.png
new file mode 100644
index 0000000..203618d
Binary files /dev/null and b/w5/images/class.png differ
diff --git a/w5/images/inheritance.png b/w5/images/inheritance.png
new file mode 100644
index 0000000..d668279
Binary files /dev/null and b/w5/images/inheritance.png differ