Skip to content

Commit 3e92b3d

Browse files
authored
🏷️ added /echo, /ping, and /youtube (#47)
* finished misc cog * added /echo, /ping, and /youtube * fixed bugs
2 parents 5b0fbcc + c4c33e2 commit 3e92b3d

File tree

6 files changed

+299
-12
lines changed

6 files changed

+299
-12
lines changed

ext/commands/info.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def joined(self, interaction: discord.Interaction, *, member: Optional[dis
4141
.add_field(name="📅 Date", value=f"{date}.") \
4242
.set_author(icon_url=member.avatar.url or None, name=member.name)
4343

44-
await interaction.response.send_message(embed=joined_embed, ephemeral=True)
44+
await interaction.response.send_message(embed=joined_embed)
4545

4646
@app_commands.command()
4747
@app_commands.describe(member="❓ The member to view the top role of.")
@@ -61,7 +61,7 @@ async def toprole(self, interaction: discord.Interaction, *, member: Optional[di
6161
.add_field(name="🏷️ Role", value=f"*@{member.top_role.name}*") \
6262
.set_author(icon_url=member.avatar.url or None, name=member.name)
6363

64-
await interaction.response.send_message(embed=top_role_embed, ephemeral=True)
64+
await interaction.response.send_message(embed=top_role_embed)
6565

6666
@app_commands.command()
6767
@app_commands.describe(member="❓ The member to view the permissions of.")
@@ -84,7 +84,7 @@ async def perms(self, interaction: discord.Interaction, *, member: Optional[disc
8484
value="\u200b".join(f"`{perm}` " for perm, value in member.guild_permissions if value) or "..."
8585
)
8686

87-
await interaction.response.send_message(embed=perms_embed, ephemeral=True)
87+
await interaction.response.send_message(embed=perms_embed)
8888

8989
@app_commands.command()
9090
@app_commands.describe(member="❓ The member to view the avatar of.")
@@ -103,7 +103,7 @@ async def avatar(self, interaction: discord.Interaction, *, member: Optional[dis
103103
) \
104104
.set_image(url=member.avatar.url or None)
105105

106-
await interaction.response.send_message(embed=avatar_embed, ephemeral=True)
106+
await interaction.response.send_message(embed=avatar_embed)
107107

108108
@app_commands.command()
109109
async def servericon(self, interaction: discord.Interaction) -> None:
@@ -118,7 +118,7 @@ async def servericon(self, interaction: discord.Interaction) -> None:
118118
) \
119119
.set_image(url=interaction.guild.icon.url)
120120

121-
await interaction.response.send_message(embed=server_icon_embed, ephemeral=True)
121+
await interaction.response.send_message(embed=server_icon_embed)
122122

123123

124124
async def setup(client: core.DiscordClient) -> None:

ext/commands/misc.py

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
information commands for BB.Bot.
44
"""
55
import datetime
6+
import random
67
import discord
78
import humanize
89
import twitchio
@@ -12,6 +13,8 @@
1213
from discord import app_commands
1314
from discord.ext import commands
1415

16+
from ext.views.youtube_view import YoutubeView
17+
1518

1619
class Misc(commands.Cog, name="Miscellaneous"):
1720
"""
@@ -31,7 +34,7 @@ async def twitch(self, interaction: discord.Interaction, *, broadcaster: str) ->
3134

3235
if not streams:
3336
error_embed = utils.create_error_embed(f"The streamer **`{broadcaster}`** is not currently live.")
34-
return await interaction.response.send_message(embed=error_embed)
37+
return await interaction.response.send_message(embed=error_embed, ephemeral=True)
3538

3639
stream: twitchio.Stream = streams[0]
3740
current_time = datetime.datetime.utcnow()
@@ -55,6 +58,30 @@ async def twitch(self, interaction: discord.Interaction, *, broadcaster: str) ->
5558
.add_field(name="❓ Category", value=stream.game_name, inline=False)
5659

5760
await interaction.response.send_message(embed=stream_embed)
61+
62+
@app_commands.command()
63+
@app_commands.describe(choices="❓ The choices to choose from, separated by commas.")
64+
async def choose(self, interaction: discord.Interaction, *, choices: str):
65+
"""
66+
🎲 Chooses a random option from a list of choices.
67+
"""
68+
choices = [x.strip() for x in choices.split(",")]
69+
70+
if len(choices) <= 1:
71+
error_embed = utils.create_error_embed("You need to give me at least 2 choices.")
72+
return await interaction.response.send_message(embed=error_embed, ephemeral=True)
73+
74+
numbered_choices = [f"**`{i + 1}`** — {x}" for i, x in enumerate(choices)]
75+
76+
choice_embed = discord.Embed(
77+
title="🎲 My Choice",
78+
description=f"**`{random.choice(choices)}`**",
79+
timestamp=datetime.datetime.utcnow(),
80+
color=self.client.theme,
81+
) \
82+
.add_field(name="❗ Options Given", value="\n".join(numbered_choices))
83+
84+
await interaction.response.send_message(embed=choice_embed)
5885

5986
@app_commands.command()
6087
async def meme(self, interaction: discord.Interaction):
@@ -63,14 +90,15 @@ async def meme(self, interaction: discord.Interaction):
6390
"""
6491
response = await self.client.session.get("https://meme-api.herokuapp.com/gimme")
6592
data = await response.json()
93+
url = data['url']
6694

6795
meme_embed = discord.Embed(
6896
title="🎲 Found a Meme",
69-
description=f"**`{data['title']}`**",
97+
description=f"**[{data['title']}]({url})**",
7098
timestamp=datetime.datetime.utcnow(),
71-
color=self.client.theme,
99+
color=self.client.theme,
72100
) \
73-
.set_image(url=f"{data['url']}") \
101+
.set_image(url=f"{url}") \
74102
.set_footer(text="❓ Try again? Use /meme.")
75103

76104
await interaction.response.send_message(embed=meme_embed)
@@ -81,19 +109,93 @@ async def poll(self, interaction: discord.Interaction, *, question: str):
81109
"""
82110
🎲 Creates a simple yes or no poll for users to vote on.
83111
"""
112+
if message is None:
113+
error_embed = utils.create_error_embed("You need to ask a question.")
114+
return await interaction.response.send_message(embed=error_embed)
115+
84116
poll_embed = discord.Embed(
85117
title="🎲 Poll",
86118
description=f"**`{question}`**",
87119
timestamp=datetime.datetime.utcnow(),
88120
color=self.client.theme,
89121
) \
90-
.set_author(name=interaction.user.name, url=interaction.user.avatar.url) \
122+
.set_author(name=interaction.user.name, icon_url=interaction.user.avatar.url) \
91123
.set_footer(text="Vote ✔️ Yes or ❌ No.")
92124

93125
message = await interaction.channel.send(embed=poll_embed)
94126
await message.add_reaction("✔️")
95127
await message.add_reaction("❌")
128+
129+
@app_commands.command()
130+
@app_commands.describe(message="❓ The phrase you want the bot to repeat.")
131+
async def echo(self, interaction: discord.Interaction, *, message: str):
132+
"""
133+
🎲 Repeats what you say.
134+
"""
135+
if message is None:
136+
error_embed = utils.create_error_embed("You need to tell me what to say.")
137+
return await interaction.response.send_message(embed=error_embed)
138+
139+
echo_embed = discord.Embed(
140+
title=f"🎲 Message",
141+
description=f"**`{message}`**",
142+
timestamp=datetime.datetime.utcnow(),
143+
color=self.client.theme,
144+
) \
145+
.set_author(name=interaction.user.name, icon_url=interaction.user.avatar.url)
146+
147+
await interaction.response.send_message(embed=echo_embed)
148+
149+
@app_commands.command()
150+
async def ping(self, interaction: discord.Interaction):
151+
"""
152+
🎲 Shows the bot's current websocket latency.
153+
"""
154+
await interaction.response.defer()
155+
156+
embed = discord.Embed(
157+
title="🏓 Pong!",
158+
description=f"⌛ Your ping is **{round(self.client.latency * 1000)}**ms.",
159+
timestamp=datetime.datetime.utcnow(),
160+
color=self.client.theme,
161+
)
162+
163+
await interaction.followup.send(embed=embed)
164+
165+
@app_commands.command()
166+
@app_commands.describe(search="❓ The YouTube video to search for.")
167+
async def youtube(self, interaction: discord.Interaction, *, search: str):
168+
"""
169+
🎲 Searches for a video on youtube and sends the link.
170+
"""
171+
if search is None:
172+
error_embed = utils.create_error_embed("You need to specify a search term.")
173+
return await interaction.response.send_message(embed=error_embed)
174+
175+
url = await utils.youtube_search_to_url(search)
176+
title = await utils.youtube_url_to_title(url)
177+
thumbnail = await utils.youtube_url_to_thumbnail(url)
178+
179+
embed = discord.Embed(
180+
title="🎲 Found a Video",
181+
description=f"🔗 [{title}]({url})",
182+
timestamp=datetime.datetime.utcnow(),
183+
color=self.client.theme,
184+
) \
185+
.set_thumbnail(url=thumbnail) \
186+
.set_footer(text=f"❓ Follow the link above to view the video.")
187+
188+
await interaction.response.send_message(embed=embed)
189+
190+
embed = discord.Embed(
191+
title="🎲 View in Discord?",
192+
description="❓ Click View In Discord to view the video in this text channel.",
193+
timestamp=datetime.datetime.utcnow(),
194+
color=self.client.theme,
195+
)
96196

197+
view = YoutubeView(url, interaction)
198+
view.message = await interaction.followup.send(embed=embed, view=view)
97199

98200

99201
async def setup(client: core.DiscordClient) -> None:

ext/utils/functions.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@
22
Module `functions` contains standalone utility functions to be
33
used in BB.Bot extensions.
44
"""
5+
import functools
6+
from typing import Any, Callable, List
57
import discord
68
import datetime
9+
import requests
10+
import re
11+
12+
from bs4 import BeautifulSoup
13+
from urllib import (
14+
parse,
15+
request
16+
)
717

818

919
def create_error_embed(message: str) -> discord.Embed:
@@ -21,5 +31,54 @@ def create_error_embed(message: str) -> discord.Embed:
2131
return discord.Embed(
2232
title=f"❌ An Error Occurred",
2333
description=f"🏷️ {message}",
24-
timestamp=datetime.datetime.utcnow()
25-
)
34+
timestamp=datetime.datetime.utcnow(),
35+
color=discord.Color.red(),
36+
)
37+
38+
39+
async def youtube_search_to_url(search_query: str) -> str:
40+
"""
41+
Returns the first result in a Youtube search for a given
42+
query.
43+
44+
Params:
45+
- search_query (str): The search terms to use.
46+
47+
Returns:
48+
- The URL of the first result found.
49+
"""
50+
query = parse.urlencode({"search_query": search_query})
51+
content = request.urlopen("http://www.youtube.com/results?" + query)
52+
results = re.findall(r"watch\?v=(\S{11})", content.read().decode())
53+
return "https://www.youtube.com/watch?v=" + results[0]
54+
55+
56+
async def youtube_url_to_title(url: str) -> str:
57+
"""
58+
Returns the title of a Youtube video, given a video's URL.
59+
60+
Params:
61+
- url (str): The URL of the Youtube video.
62+
63+
Returns:
64+
- The title of the Youtube video.
65+
"""
66+
reqs = requests.get(url)
67+
soup = BeautifulSoup(reqs.text, "html.parser")
68+
69+
return "".join([t for t in soup.find("title")])
70+
71+
72+
async def youtube_url_to_thumbnail(url: str) -> str:
73+
"""
74+
Returns the thumbnail of a Youtube video, given a video's URL.
75+
76+
Params:
77+
- url (str): The URL of the Youtube video.
78+
79+
Returns:
80+
- A URL for the thumbnail of the Youtube video.
81+
"""
82+
exp = r"^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*"
83+
s = re.findall(exp, url)[0][-1]
84+
return f"https://i.ytimg.com/vi/{s}/maxresdefault.jpg"

ext/views/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
Module `views` contains user interface components used
3+
in BB.Bot's extensions (commands or otherwise).
4+
"""

ext/views/base_view.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Module `base_view` contains the `BaseView` class, which
3+
provides common functionality.
4+
"""
5+
import discord
6+
7+
from ext import utils
8+
from typing import Any
9+
10+
from discord import (
11+
ui,
12+
app_commands
13+
)
14+
15+
16+
class BaseView(ui.View):
17+
"""
18+
Class `BaseView` is a subclass of `discord.ui.View` that
19+
implements `interaction_check` and `disable_all_buttons`.
20+
"""
21+
def __init__(self, interaction: discord.Interaction, **kwargs: Any) -> None:
22+
"""
23+
Creates an instance of `discord.ui.View` that implements
24+
an `interaction_check` and a `disable_all_buttons` method.
25+
26+
Params:
27+
- interaction (discord.Interaction): The interaction the command was invoked with.
28+
- message (discord.Message): Pass the message the view is sent in.
29+
- **kwargs (Any): Any keyword arguments that `discord.ui.View` accepts.
30+
"""
31+
super().__init__()
32+
self.command_author_id = interaction.user.id
33+
self.message: discord.Message = None
34+
35+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
36+
"""
37+
Prevents users who weren't the command sender from interacting
38+
with the view component.
39+
40+
Params:
41+
- interaction (discord.Interaction): The interaction used with the view.
42+
43+
Returns:
44+
- A `bool`. If false, the view will not execute any callbacks.
45+
"""
46+
if interaction.user.id == self.command_author_id:
47+
return True
48+
49+
error_embed = utils.create_error_embed("This isn't your interaction.")
50+
await interaction.response.send_message(embed=error_embed, ephemeral=True)
51+
return False
52+
53+
async def disable_all_buttons(self) -> None:
54+
"""
55+
Disables all buttons that are attached to the view component. This
56+
is usually done when a view should be closed, to prevent errors.
57+
58+
Params:
59+
- interaction (discord.Interaction): The interaction used with the view.
60+
"""
61+
for child in self.children:
62+
child.disabled = True
63+
64+
await self.message.edit(view=self)
65+
self.stop()

0 commit comments

Comments
 (0)