79 Commits

Author SHA1 Message Date
aikaterna
5b3fe0218a [CardsAgainstHumanity] Fix for joincah + id match 2020-01-01 14:57:07 -08:00
aikaterna
17ac242cce [Tools] Add color in role list 2019-12-30 08:43:53 -08:00
jack1142
c78f7e8b21 [Wolfram] Improve reliability of requests for step-by-step solutions (#87)
* Improve reliability of requests for step-by-step solutions

* Fix NameError...

* And of course I forgot to put params kwarg, oof

* Pagify output since solutions can sometimes be long
2019-12-12 19:25:40 -08:00
Michael H
eadf478714 Avoid touching config as much. (#88)
Could cache some of this on load as well, but this is a really cheap way to reduce the times interacting with config for now.
2019-12-12 17:00:57 -08:00
aikaterna
df554b9c4a [Wolfram] Slight url encoding changes 2019-12-12 10:26:15 -08:00
aikaterna
872bb7c6bc [Dungeon] update the listener too 2019-12-11 17:37:09 -08:00
aikaterna
4e7a77f270 [Wolfram] no local files to save 2019-12-11 17:33:07 -08:00
aikaterna
86d7009288 [Wolfram] add pillow to info.json reqs 2019-12-11 17:32:14 -08:00
aikaterna
0de7ed51bb [Wolfram] Add step solve and image responses 2019-12-11 17:31:09 -08:00
aikaterna
e3fa64c85e [Dungeon] Remove unused import, backwards compat 2019-12-11 10:01:57 -08:00
PredaaA
329a88d12b Update otherbot.py (#86) 2019-12-11 09:51:30 -08:00
aikaterna
c5ada7df8a [NoFlippedTables] No tables in dms 2019-12-11 08:59:45 -08:00
aikaterna
fe3097a928 [Tools] no msg here 2019-12-06 20:46:31 -08:00
aikaterna
672c231b8a [Tools] Add listroles alias, add 0s for >100 roles 2019-12-06 20:42:26 -08:00
aikaterna
a06a80b268 [NoFlippedTables] Snackburr > snackbear 2019-12-06 20:20:57 -08:00
aikaterna
df45fd9548 [Tools] Add listroles & black formatting 2019-12-06 20:16:21 -08:00
zephyrkul
7039596bcf add beautifulsoup to cog requirements (#84) 2019-12-06 19:35:54 -08:00
aikaterna
676d9e27b3 [NoFlippedTables] Add toggle, off by default + black format 2019-12-06 14:53:12 -08:00
aikaterna
581824f5ff [Region] Give it an info.json already 2019-12-06 12:54:07 -08:00
aikaterna
898dae5e6f [NoFlippedTables] Initial commit 2019-12-06 12:52:25 -08:00
Flame442
11b7eb2e1f Multiple changes (#81)
Fixed an error when the press f message is deleted
Fixed a bug where two press fs could be started and the second would error
Fixed a bug where a message from any channel could be used as the thing to pay respects to
2019-11-19 15:03:50 -08:00
dudeamax99
807a4715ab pressf reaction now shows username (#80)
After reacting to a press f message, bot says
{server nickname}has paid their respects
now says 
{username} has paid their respects
2019-11-08 19:01:06 -08:00
dudeamax99
8f119435da Changes "has paid respects" to "has paid their respects". (#79)
* Changes "has paid respects" to "has paid their respects".

* Fixes dumb grammtical error
2019-11-08 18:30:34 -08:00
aikaterna
70f52f8832 Update readme for snacktime 2019-11-05 08:59:54 -08:00
aikaterna
65b5b1a064 [Snacktime] update info.json round 2 2019-11-01 14:20:08 -07:00
aikaterna
b90bb77be9 [Snacktime] update info.json 2019-11-01 14:15:05 -07:00
aikaterna
d19c0c2826 [Snacktime] Fix for greedy messages 2019-11-01 14:12:36 -07:00
aikaterna
12ca45123b [Snacktime] snacktime command is guild only 2019-11-01 12:20:22 -07:00
aikaterna
1e549fd96c [Snacktime] Fix some message formatting 2019-11-01 09:58:26 -07:00
aikaterna
c64252de6e [Snacktime] Initial commit
Thanks to irdumb for making such an excellent, fun cog. This was long overdue to get this bad boy on v3.
2019-11-01 09:49:29 -07:00
aikaterna
ccad084b2b [Dictionary] Rewrite + antonyms and synonyms 2019-10-26 11:46:37 -07:00
aikaterna
5076b4a1b7 [Tools] Let's use escape instead 2019-10-13 12:25:19 -07:00
aikaterna
869f4bb8da [Tools] Strip some formatting on guild names 2019-10-13 12:20:14 -07:00
aikaterna
7f7357796e [Tools] Menu-ify the server list 2019-10-13 12:16:24 -07:00
PredaaA
c3d8bb8d05 Update trickortreat.py (#75) 2019-10-10 18:45:37 -07:00
PredaaA
a1ca7dff60 [TrickOrTreat] Fix humanize_number for Red < 3.2 (#74)
* Update trickortreat.py

* Update trickortreat.py
2019-10-10 18:36:15 -07:00
Nodja
ef12d46745 Preserve order when fetching urls from page (#69) 2019-10-10 18:13:10 -07:00
PredaaA
60274065f2 [TrickOrTreat] Change cboard and cinventory style (#73)
* Update trickortreat.py

* Update trickortreat.py
2019-10-10 18:02:54 -07:00
aikaterna
a05bf4ede4 [PressF] Press f for mass mentions 2019-10-10 17:59:52 -07:00
Flame442
85dee0ad8b Fixed a few bugs (#72) 2019-10-09 18:32:39 -07:00
aikaterna
82c33359b1 who did this 2019-10-09 17:07:12 -07:00
aikaterna
a888776a6c [PressF] to pay respects 2019-10-09 16:47:48 -07:00
aikaterna
8fc1ff176f [DadJokes] Initial commit 2019-10-08 17:37:09 -07:00
PredaaA
632aceaf89 Update trickortreat.py (#71) 2019-10-07 18:49:02 -07:00
PredaaA
780858e6d8 [TrickOrTreat] Add eaten candies in eat command + changes in cboard (#70)
* Update trickortreat.py

* Oops

* Add required permissions for the bot in cboard
2019-10-07 18:21:00 -07:00
aikaterna
1b46f00812 [RndStatus] oops :awesome: 2019-10-04 19:52:26 -07:00
aikaterna
80e5095ebc [RndStatus] Fix for not updating status 2019-10-04 19:14:47 -07:00
aikaterna
decf472b6c [RndStatus] Fix for not rotating status 2019-10-04 19:14:30 -07:00
NIXC
50f0ad6099 fix uneating candy (#68) 2019-10-04 18:44:45 -07:00
aikaterna
e8db484870 [Trick Or Treat] Fix totcooldown pt 2 2019-10-03 18:05:28 -07:00
aikaterna
729dce1ae2 [Trick Or Treat] Fix totcooldown
Thanks Nin.
2019-10-03 17:53:18 -07:00
PredaaA
5d87c3dcdc Update trickortreat.py (#67) 2019-10-03 11:57:50 -07:00
aikaterna
6c6cdf1a20 [TrickOrTreat] cooldown -> totcooldown 2019-10-02 08:17:56 -07:00
aikaterna
d94fc1b596 Update trickortreat.py 2019-10-01 19:45:28 -07:00
aikaterna
e6181a704b [Trick Or Treat] Set time before waiting 2019-10-01 19:42:41 -07:00
Flame442
3d68a3af31 Fixes ranges that do not fully contain the stated values (#66) 2019-10-01 19:42:03 -07:00
PredaaA
ddff041974 Update region.py (#65) 2019-09-29 08:24:13 -07:00
Dezyox
b3012f117e Update the countdown for the 5th anninversary (#64)
Updated the anniversary, it’s now the 5th 🎉
2019-09-26 11:40:43 -07:00
aikaterna
46494784e4 [Retrosign] http -> https
Thanks @ComradeCactus for the tip. Resolves #57
2019-09-17 08:48:39 -07:00
aikaterna
9d803c4710 [Pupper] Fix for min/max setting
Thanks Flame.
2019-09-17 08:29:08 -07:00
aikaterna
a74c15e73a [Region] Initial commit 2019-09-16 12:39:41 -07:00
Draper
c020dc5e89 Fixes Console errors when bot is used in DM (#54)
* Removes 3.0 compatibility
Improves Disk IO by writting to config every 60 seconds
Also listen to `on_typing`, `on_message_edit`, `on_reaction_remove`, `on_reaction_add`

* Change from `on_message` to `on_message_without_command`

* Hotfix for seen in DM's
2019-08-21 06:09:07 -07:00
aikaterna
00eddecbd9 [Timezone] They -> the 2019-08-16 08:13:59 -07:00
Draper
fdecc629c5 [Seen] Improves DisK I/O for bots with larger server and member base (#53)
* Removes 3.0 compatibility
Improves Disk IO by writting to config every 60 seconds
Also listen to `on_typing`, `on_message_edit`, `on_reaction_remove`, `on_reaction_add`

* Change from `on_message` to `on_message_without_command`
2019-08-12 07:59:33 -07:00
aikaterna
3a7d27fbb3 Update pupper.py 2019-08-07 22:04:31 -07:00
aikaterna
2e9000160e [Pupper] Group cmd fix
Oh yeah, there's these things called group commands that need to have decos modified too. Imagine that
2019-08-07 09:38:07 -07:00
aikaterna
0fadb07e48 [Pupper] More info, cmd name change 2019-08-07 08:13:48 -07:00
aikaterna
2f52012b33 [Pupper] pet command -> pets command
Changed to not conflict with the adventure cog or other social cogs.
2019-08-07 08:12:11 -07:00
aikaterna
d4a70d5782 [Pupper] Initial commit 2019-08-06 18:58:40 -07:00
aikaterna
d57ba2d78a [Blurplefy] Fix constructor parameter must be str
Resolves #50
2019-07-25 08:33:56 -07:00
aikaterna
1d0b23c03f [YouTube] Initial commit 2019-07-24 14:37:01 -07:00
aikaterna
e8e58ce9e3 Add dictionary 2019-07-23 17:06:45 -07:00
aikaterna
a7fb88bad2 [CardsAgainstHumanity] No bundled_data in init 2019-06-25 14:20:24 -07:00
aikaterna
f296c65ef2 [V3] __unload -> cog_unload 2019-06-25 10:49:17 -07:00
Iangit1
ed1aa25490 Fix streaming status (#46)
* Streaming status fix

Added a settable streamer which is used to create a twitch url.
If rndstatus type is set to 1 (streaming) it uses the url so the bot actually shows as streaming (with purple dot and button to watch streamer) instead of saying live on twitch playing with a green dot.

* Changed delay back to 300
2019-06-24 17:19:26 -07:00
aikaterna
24903d720c [CardsAgainstHumanity] Add info.json 2019-06-14 09:42:03 -07:00
aikaterna
a79b59ee85 [V3 Tools] Fix for #45 2019-06-14 09:23:29 -07:00
aikaterna
17d6a597a7 3.1 changes 2019-05-28 22:32:25 -07:00
Ellie
831740f18c V3.0 + V3.1 compability (#44)
* Update away.py

* Update dungeon.py

* V3.0 + V3.1 compability

* V3.0 + V3.1 compability

* V3.0 + V3.1 compability

* V3.0 + V3.1 compability

* V3.0 + V3.1 compability

* V3.0 + V3.1 compability
2019-05-22 08:06:40 -07:00
46 changed files with 1901 additions and 185 deletions

View File

@@ -5,12 +5,18 @@ adventure - Originally by Locastan. My version is a collaboration between Trusty
antiphoneclapper - Detects and removes bad GIFs posted in chat that have malformed frames. Thanks to Sitryk for all of the code that actually mattered for detection in this cog. Config stuff tacked on by me. Later iterations will include emoji recognizing and possibly link detection for these bad images.
away - Originally by Paddo, written for v3 by Axas, final tests by aikaterna. Set and unset a user as being "away".
away - Originally by Paddo, written for v3 by Axas, final tests by aikaterna, and large improvements by TrustyJAID. Set and unset a user as being "away".
blurplefy - Make an avatar or an image upload blurple for Discord's 3rd anniversary.
cah - Cards Against Humanity, played in DM's. This can rate limit large bots via the sheer number of messages sent. Install and use with caution on larger bots.
chatchart - Generates a pie chart to display chat activity over the last 5000 messages. Requested by violetnyte.
dadjokes - Another UltimatePancake cog. Get some dad jokes on command.
dictonary - I stole this from UltimatePancake's v2 cogs, defines words via a dictonary. Only the "define" command for now until the dependency owner updates their files with a pending PR.
dungeon - New users with new accounts will be shuffled off to a locked channel on-join to help mitigate raiders. Please see the dungeon_readme.md file on this repo for more information.
imgwelcome - The repo can be found at: https://github.com/aikaterna/imgwelcome
@@ -19,6 +25,8 @@ inspirobot - Fetch "inspirational" messages from inspirobot.me with [p]inspireme
leveler - A v3 port of Stevy's v2 leveler, originally by Fixator and modified by me. Add the repo at: https://github.com/aikaterna/Fixator10-Cogs
noflippedtables - A v3 port of irdumb's v2 cog with a little extra surprise included. Unflip all the tables.
nolinks - A very blunt hammer to remove anything that looks like a link. Roles can be whitelisted and it can watch multiple channels.
otherbot - Alert a role when bot(s) go offline.
@@ -27,10 +35,18 @@ partycrash - A port of Will's partycrash command from the v2 Admin cog. This cog
pingtime - Show all shards' pingtimes.
pressf - A port/rewrite of NekoTony's v2 pressf cog. Pay your respects by pressing F.
pupper - A cog for Ryan5374. A pet that comes around on an on_message listener and waits for someone to pet it (react with a standard wave emoji), and rewards with credits. Many attributes are configurable.
region - A command to change the voice region of a server. Requires the guild admin or mod role or guild administrator.
retrosign - A v3 port of Anismash's retrosign cog: https://github.com/Anismash/Ani-Cogs/tree/master/retrosign
rndstatus - A v3 port of Twentysix's rndstatus cog with a couple extra settings.
snacktime - A v3 port of irdumb's snacktime cog. Now with friends!
timezone - A v3 port of Fishyfing's timezone cog with a few improvements.
trickortreat - A trick or treat-based competitive candy eating game with a leaderboard and other fun commands like stealing candy from guildmates.
@@ -39,4 +55,6 @@ tools - A collection of mod and admin tools, ported from my v2 version. Sitryk i
wolfram - A v3 port of Paddo's abandoned Wolfram Alpha cog.
youtube - A v3 port of Paddo's youtube search cog for v2.
Are you looking for the v3 lavalink music cog? It's been added to Red v3 as the audio module. Install from develop and ask in the Red support server for help if you need it, or join my server. https://discord.gg/th6eS3T

View File

@@ -7,6 +7,12 @@ from redbot.core import commands, checks, Config
BaseCog = getattr(commands, "Cog", object)
listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad
if listener is None:
def listener(name=None):
return lambda x: x
class AntiPhoneClapper(BaseCog):
"""This cog deletes bad GIFs that will crash phone clients."""
@@ -67,6 +73,7 @@ class AntiPhoneClapper(BaseCog):
tile_sizes.append(im.tile[0][1][2:])
return any([x[0] > limit[0] or x[1] > limit[1] for x in tile_sizes])
@listener()
async def on_message(self, m):
if not m.attachments:
return
@@ -106,5 +113,5 @@ class AntiPhoneClapper(BaseCog):
else:
return
def __unload(self):
def cog_unload(self):
self.bot.loop.create_task(self.session.close())

View File

@@ -8,6 +8,12 @@ IMAGE_LINKS = re.compile(r"(http[s]?:\/\/[^\"\']*\.(?:png|jpg|jpeg|gif|png))")
BaseCog = getattr(commands, "Cog", object)
listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad
if listener is None:
def listener(name=None):
return lambda x: x
class Away(BaseCog):
"""Le away cog"""
@@ -126,7 +132,7 @@ class Away(BaseCog):
for word in message.split():
match = re.search(r"<@!?([0-9]+)>", word)
if match:
user = await self.bot.get_user_info(int(match.group(1)))
user = await self.bot.fetch_user(int(match.group(1)))
message = re.sub(match.re, "@" + user.name, message)
return message
@@ -183,6 +189,7 @@ class Away(BaseCog):
return True
return False
@listener()
async def on_message(self, message):
tmp = {}
guild = message.guild

View File

@@ -20,9 +20,7 @@ darkblurple = (78, 93, 148)
white = (255, 255, 255)
BaseCog = getattr(commands, "Cog", object)
class Blurplefy(BaseCog):
class Blurplefy(commands.Cog):
def __init__(self, bot):
"""Blurplefy images and check content of images."""
self.bot = bot
@@ -93,7 +91,7 @@ class Blurplefy(BaseCog):
else:
picture = user.avatar_url
try:
async with self.session.request("GET", picture) as r:
async with self.session.request("GET", str(picture)) as r:
response = await r.read()
except ValueError:
await ctx.send("{}, please link a valid image URL.".format(ctx.author.display_name))
@@ -120,7 +118,7 @@ class Blurplefy(BaseCog):
role_check = False
try:
async with self.session.request("GET", picture) as r:
async with self.session.request("GET", str(picture)) as r:
response = await r.read()
except ValueError:
await ctx.send("{}, please link a valid image URL.".format(ctx.author.display_name))
@@ -226,7 +224,7 @@ class Blurplefy(BaseCog):
else:
picture = user.avatar_url
try:
async with self.session.request("GET", picture) as r:
async with self.session.request("GET", str(picture)) as r:
response = await r.read()
except ValueError:
await ctx.send("{}, please link a valid image URL.".format(ctx.author.display_name))
@@ -398,21 +396,21 @@ class Blurplefy(BaseCog):
@commands.command()
async def countdown(self, ctx):
"""Countdown to Discord's 4th Anniversary."""
"""Countdown to Discord's 5th Anniversary."""
embed = discord.Embed(name="", colour=0x7289da)
timeleft = (
datetime.datetime(2018, 5, 13)
datetime.datetime(2019, 5, 13)
+ datetime.timedelta(hours=7)
- datetime.datetime.utcnow()
)
embed.set_author(name="Time left until Discord's 4th Anniversary")
embed.set_author(name="Time left until Discord's 5th Anniversary")
if int(timeleft.total_seconds()) < 0:
timeleft = (
datetime.datetime(2019, 5, 13)
datetime.datetime(2020, 5, 13)
+ datetime.timedelta(hours=7)
- datetime.datetime.utcnow()
)
embed.set_author(name="Time left until Discord's 4th Anniversary")
embed.set_author(name="Time left until Discord's 5th Anniversary")
embed.add_field(
name="Countdown to midnight, May 13, California time (UTC-7):",
value=("{}".format(self._dynamic_time(int(timeleft.total_seconds())))),
@@ -437,5 +435,5 @@ class Blurplefy(BaseCog):
msg = ""
return msg.format(d, h, m, s)
def __unload(self):
self.session.close()
def cog_unload(self):
self.bot.loop.create_task(self.session.close())

View File

@@ -1,8 +1,5 @@
from redbot.core import commands, data_manager
from .cah import CardsAgainstHumanity
def setup(bot: commands.Bot):
n = CardsAgainstHumanity(bot)
data_manager.load_bundled_data(n, __file__)
bot.add_cog(n)
def setup(bot):
bot.add_cog(CardsAgainstHumanity(bot))

View File

@@ -71,11 +71,18 @@ class CardsAgainstHumanity(BaseCog):
return member
# No member yet - try ID
memID = "".join(list(filter(str.isdigit, name)))
newMem = memberForID(memID, server)
newMem = self.memberforid(memID, server)
if newMem:
return newMem
return None
@staticmethod
def memberforid(checkid, server):
for member in server.members:
if str(member.id) == str(checkid):
return member
return None
def getreadabletimebetween(self, first, last):
# A helper function to make a readable string between two times
timeBetween = int(last - first)

18
cah/info.json Normal file
View File

@@ -0,0 +1,18 @@
{
"author": [
"aikaterna"
],
"description": "Cards Against Humanity, played in DMs.",
"install_msg": "Warning: This cog can send a lot of DMs, and the bot can become rate limited. Be warned that installing this cog on a bot with a lot of users or servers might be detrimental to the bot's performance. Thanks for installing, have fun.",
"permissions" : [
"embed_links",
"manage_roles"
],
"short": "Cards Against Humanity.",
"tags": [
"CAH",
"cards",
"games"
],
"type": "COG"
}

5
dadjokes/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .dadjokes import DadJokes
def setup(bot):
bot.add_cog(DadJokes(bot))

16
dadjokes/dadjokes.py Normal file
View File

@@ -0,0 +1,16 @@
from redbot.core import commands
import aiohttp
class DadJokes(commands.Cog):
"""Random dad jokes from icanhazdadjoke.com"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def dadjoke(self, ctx):
"""Gets a random dad joke."""
api = 'https://icanhazdadjoke.com/'
async with aiohttp.request('GET', api, headers={'Accept': 'text/plain'}) as r:
result = await r.text()
await ctx.send(f"`{result}`")

10
dadjokes/info.json Normal file
View File

@@ -0,0 +1,10 @@
{
"author": [
"UltimatePancake"
],
"description": "Gets a random dad joke from icanhazdadjoke.com",
"install_msg": "Gets a random dad joke from icanhazdadjoke.com. Thanks for installing.",
"short": "Random dad jokes",
"tags": ["jokes", "dad", "dadjokes"],
"type": "COG"
}

5
dictionary/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .dictionary import Dictionary
def setup(bot):
bot.add_cog(Dictionary(bot))

117
dictionary/dictionary.py Normal file
View File

@@ -0,0 +1,117 @@
import aiohttp
from bs4 import BeautifulSoup
import re
from redbot.core import commands
class Dictionary(commands.Cog):
"""Word, yo
Parts of this cog are adapted from the PyDictionary library."""
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
def cog_unload(self):
self.bot.loop.create_task(self.session.close())
async def _get_soup_object(self, url):
try:
async with self.session.request("GET", url) as response:
return BeautifulSoup(await response.text(), "html.parser")
except Exception as e:
print(e)
return
@commands.command()
async def antonym(self, ctx, *, word: str):
"""Displays antonyms for a given word."""
search_msg = await ctx.send("Searching...")
search_term = word.split(" ", 1)[0]
result = await self._antonym(ctx, search_term)
if not result:
return await search_msg.edit(content="This word is not in the dictionary.")
result_text = "*, *".join(result)
await search_msg.edit(content=f"Antonyms for **{search_term}**: *{result_text}*")
async def _antonym(self, ctx, word):
data = await self._get_soup_object(f"http://www.thesaurus.com/browse/{word}")
section = data.find_all("ul", {"class": "css-1lc0dpe et6tpn80"})
try:
section[1]
except IndexError:
return
spans = section[1].findAll("li")
antonyms = [span.text for span in spans[:50]]
return antonyms
@commands.command()
async def define(self, ctx, *, word: str):
"""Displays definitions of a given word."""
search_msg = await ctx.send("Searching...")
search_term = word.split(" ", 1)[0]
result = await self._definition(ctx, search_term)
str_buffer = ""
if not result:
return await search_msg.edit(content="This word is not in the dictionary.")
for key in result:
str_buffer += f"\n**{key}**: \n"
counter = 1
j = False
for val in result[key]:
if val.startswith("("):
str_buffer += f"{str(counter)}. *{val})* "
counter += 1
j = True
else:
if j:
str_buffer += f"{val}\n"
j = False
else:
str_buffer += f"{str(counter)}. {val}\n"
counter += 1
await search_msg.edit(content=str_buffer)
async def _definition(self, ctx, word):
html = await self._get_soup_object(f"http://wordnetweb.princeton.edu/perl/webwn?s={word}")
types = html.findAll("h3")
length = len(types)
lists = html.findAll("ul")
out = {}
if not lists:
return
for a in types:
reg = str(lists[types.index(a)])
meanings = []
for x in re.findall(r">\s\((.*?)\)\s<", reg):
if "often followed by" in x:
pass
elif len(x) > 5 or " " in str(x):
meanings.append(x)
name = a.text
out[name] = meanings
return out
async def _synonym(self, ctx, word):
data = await self._get_soup_object(f"http://www.thesaurus.com/browse/{word}")
section = data.find_all("ul", {"class": "css-1lc0dpe et6tpn80"})
try:
section[1]
except IndexError:
return
spans = section[0].findAll("li")
synonyms = [span.text for span in spans[:50]]
return synonyms
@commands.command()
async def synonym(self, ctx, *, word: str):
"""Displays synonyms for a given word."""
search_msg = await ctx.send("Searching...")
search_term = word.split(" ", 1)[0]
result = await self._synonym(ctx, search_term)
if not result:
return await search_msg.edit(content="This word is not in the dictionary.")
result_text = "*, *".join(result)
await search_msg.edit(content=f"Synonyms for **{search_term}**: *{result_text}*")

15
dictionary/info.json Normal file
View File

@@ -0,0 +1,15 @@
{
"author": [
"UltimatePancake", "aikaterna"
],
"description": "Gets definitions, antonyms, or synonyms for given words",
"install_msg": "After loading the cog with `[p]load dictionary`, use [p]help Dictionary to view commands.",
"short": "Gets definitions, antonyms, or synonyms for given words",
"tags": [
"dictionary"
],
"requirements": [
"beautifulsoup4"
],
"type": "COG"
}

View File

@@ -2,12 +2,9 @@ import asyncio
import datetime
import discord
from redbot.core import Config, commands, checks, modlog
from redbot.core.data_manager import cog_data_path
BaseCog = getattr(commands, "Cog", object)
class Dungeon(BaseCog):
class Dungeon(commands.Cog):
"""Auto-quarantine suspicious users."""
def __init__(self, bot):
@@ -348,6 +345,7 @@ class Dungeon(BaseCog):
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg)
return await ctx.send(embed=embed)
@commands.Cog.listener()
async def on_member_join(self, member):
default_avatar = False
toggle = await self.config.guild(member.guild).toggle()

View File

@@ -3,9 +3,7 @@ import discord
from redbot.core import commands
BaseCog = getattr(commands, "Cog", object)
class Inspirobot(BaseCog):
class Inspirobot(commands.Cog):
"""Posts images generated by https://inspirobot.me"""
def __init__(self, bot):
self.bot = bot
@@ -25,5 +23,5 @@ class Inspirobot(BaseCog):
except Exception as e:
await ctx.send(f"Oops, there was a problem: {e}")
def __unload(self):
self.session.close()
def cog_unload(self):
self.bot.loop.create_task(self.session.close())

View File

@@ -0,0 +1,5 @@
from .noflippedtables import NoFlippedTables
def setup(bot):
bot.add_cog(NoFlippedTables(bot))

View File

@@ -0,0 +1,9 @@
{
"name" : "NoFlippedTables",
"author" : ["irdumb", "aikaterna"],
"short" : "Unflip some tables.",
"description" : "Unflip all the flipped tables.",
"install_msg" : "Usage: [p]help tableset",
"tags" : ["noflippedtables", "no flip", "tables"],
"disabled" : false
}

View File

@@ -0,0 +1,131 @@
import asyncio
from random import uniform as randfloat
import re
from redbot.core import commands, checks, Config
from redbot.core.utils.chat_formatting import box
class NoFlippedTables(commands.Cog):
"""For the table sympathizers"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2712290002, force_registration=True)
default_guild = {
"ALL_TABLES": True,
"BOT_EXEMPT": False,
"SNACKBEAR": False,
"TOGGLE": False,
}
self.config.register_guild(**default_guild)
self.flippedTables = {}
@checks.mod_or_permissions(manage_guild=True)
@commands.group()
async def tableset(self, ctx):
"""Got some nice settings for my UNflipped tables"""
if ctx.invoked_subcommand is None:
settings = await self.config.guild(ctx.guild).all()
msg = "[Current Settings]\n"
for k, v in settings.items():
msg += str(k) + ": " + str(v) + "\n"
await ctx.send(box(msg, lang="ini"))
@tableset.command()
async def flipall(self, ctx):
"""Enables/disables right all unflipped tables in a message"""
settings = await self.config.guild(ctx.guild).ALL_TABLES()
await self.config.guild(ctx.guild).ALL_TABLES.set(not settings)
if not settings:
await ctx.send("All tables will now be unflipped.")
else:
await ctx.send("Now only one table unflipped per message.")
@tableset.command()
async def flipbot(self, ctx):
"""Enables/disables allowing bot to flip tables"""
settings = await self.config.guild(ctx.guild).BOT_EXEMPT()
await self.config.guild(ctx.guild).BOT_EXEMPT.set(not settings)
if not settings:
await ctx.send("Bot is now allowed to leave its own tables flipped.")
else:
await ctx.send("Bot must now unflip tables that itself flips.")
@tableset.command()
async def snackbear(self, ctx):
"""Snackburr is unflipping tables!"""
settings = await self.config.guild(ctx.guild).SNACKBEAR()
await self.config.guild(ctx.guild).SNACKBEAR.set(not settings)
if not settings:
await ctx.send("Snackburr will now unflip tables.")
else:
await ctx.send("Snackburr is heading off for his errands!")
@tableset.command()
async def toggle(self, ctx):
"""Toggle the unflipping on or off."""
settings = await self.config.guild(ctx.guild).TOGGLE()
await self.config.guild(ctx.guild).TOGGLE.set(not settings)
if not settings:
await ctx.send("No table shall be left unflipped in this server.")
else:
await ctx.send("No more unflipping here.")
@commands.Cog.listener()
# so much fluff just for this OpieOP
async def on_message(self, message):
channel = message.channel
user = message.author
if not message.guild:
return
if hasattr(user, "bot") and user.bot is True:
return
toggle = await self.config.guild(message.guild).TOGGLE()
if not toggle:
return
if channel.id not in self.flippedTables:
self.flippedTables[channel.id] = {}
# ┬─┬ ┬┬ ┻┻ ┻━┻ ┬───┬ ┻━┻ will leave 3 tables left flipped
# count flipped tables
for m in re.finditer("┻━*┻|┬─*┬", message.content):
t = m.group()
bot_exempt = await self.config.guild(message.guild).BOT_EXEMPT()
if "" in t and not (message.author.id == self.bot.user.id and bot_exempt):
if t in self.flippedTables[channel.id]:
self.flippedTables[channel.id][t] += 1
else:
self.flippedTables[channel.id][t] = 1
all_tables = await self.config.guild(message.guild).ALL_TABLES()
if not all_tables:
break
else:
f = t.replace("", "").replace("", "")
if f in self.flippedTables[channel.id]:
if self.flippedTables[channel.id][f] <= 0:
del self.flippedTables[channel.id][f]
else:
self.flippedTables[channel.id][f] -= 1
# wait random time. some tables may be unflipped by now.
await asyncio.sleep(randfloat(0, 1.5))
tables = ""
deleteTables = []
# unflip tables in self.flippedTables[channel.id]
for t, n in self.flippedTables[channel.id].items():
snackburr = await self.config.guild(message.guild).SNACKBEAR()
if snackburr:
unflipped = t.replace("", "").replace("", "") + " ノʕ •ᴥ•ノʔ" + "\n"
else:
unflipped = t.replace("", "").replace("", "") + " ( ゜-゜ノ)" + "\n"
for i in range(0, n):
tables += unflipped
# in case being processed in parallel
self.flippedTables[channel.id][t] -= 1
deleteTables.append(t)
for t in deleteTables:
del self.flippedTables[channel.id][t]
if tables != "":
await channel.send(tables)

View File

@@ -5,6 +5,12 @@ from redbot.core import Config, commands, checks
BaseCog = getattr(commands, "Cog", object)
listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad
if listener is None:
def listener(name=None):
return lambda x: x
class NoLinks(BaseCog):
def __init__(self, bot):
self.bot = bot
@@ -104,6 +110,7 @@ class NoLinks(BaseCog):
await self.config.guild(ctx.guild).watching.set(channel_list)
await ctx.send(f"{self.bot.get_channel(channel.id).mention} will not have links removed.")
@listener()
async def on_message(self, message):
if isinstance(message.channel, discord.abc.PrivateChannel):
return

View File

@@ -4,6 +4,11 @@ from redbot.core import commands, checks, Config
BaseCog = getattr(commands, "Cog", object)
listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad
if listener is None:
def listener(name=None):
return lambda x: x
class Otherbot(BaseCog):
def __init__(self, bot):
@@ -23,7 +28,13 @@ class Otherbot(BaseCog):
@otherbot.command()
async def channel(self, ctx, channel: discord.TextChannel = None):
"""Sets the channel to report in."""
"""
Sets the channel to report in.
Default to the current one.
"""
if not channel:
channel = ctx.channel
await self.config.guild(ctx.guild).reporting.set(channel.id)
await ctx.send(f"Reporting channel set to: {channel.mention}.")
@@ -56,7 +67,7 @@ class Otherbot(BaseCog):
msg += "None."
if not bot_user:
for saved_bot_id in data["watching"]:
bot_user = await self.bot.get_user_info(saved_bot_id)
bot_user = await self.bot.fetch_user(saved_bot_id)
if len(bot_user.name) > 16:
bot_name = f"{bot_user.name:16}...#{bot_user.discriminator}"
else:
@@ -75,7 +86,10 @@ class Otherbot(BaseCog):
f"Reporting channel set to: {ctx.message.channel.mention}. Use `{ctx.prefix}otherbot channel` to change this."
)
@listener()
async def on_member_update(self, before, after):
if after.guild is None or not after.bot:
return
data = await self.config.guild(after.guild).all()
if after.status == discord.Status.offline and (after.id in data["watching"]):
channel_object = self.bot.get_channel(data["reporting"])

4
pressf/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from .pressf import PressF
def setup(bot):
bot.add_cog(PressF(bot))

13
pressf/info.json Normal file
View File

@@ -0,0 +1,13 @@
{
"author": [
"aikaterna"
],
"description": "Pay respects to a thing or user by pressing f.",
"install_msg": "Thanks for installing, have fun.",
"short": "Press f to pay respects.",
"tags": [
"pressf",
"respects"
],
"type": "COG"
}

66
pressf/pressf.py Normal file
View File

@@ -0,0 +1,66 @@
import asyncio
import discord
from redbot.core import commands
from redbot.core.utils.common_filters import filter_mass_mentions
class PressF(commands.Cog):
"""Pay some respects."""
def __init__(self, bot):
self.bot = bot
self.channels = {}
@commands.command()
@commands.bot_has_permissions(add_reactions=True)
async def pressf(self, ctx, *, user: discord.User = None):
"""Pay respects by pressing F"""
if str(ctx.channel.id) in self.channels:
return await ctx.send(
"Oops! I'm still paying respects in this channel, you'll have to wait until I'm done."
)
self.channels[str(ctx.channel.id)] = {}
if user:
answer = user.display_name
else:
await ctx.send("What do you want to pay respects to?")
def check(m):
return m.author == ctx.author and m.channel == ctx.channel
try:
pressf = await ctx.bot.wait_for("message", timeout=120.0, check=check)
except asyncio.TimeoutError:
del self.channels[str(ctx.channel.id)]
return await ctx.send("You took too long to reply.")
answer = pressf.content[:1900]
message = await ctx.send(
f"Everyone, let's pay respects to **{filter_mass_mentions(answer)}**! Press the f reaction on the this message to pay respects."
)
await message.add_reaction("\U0001f1eb")
self.channels[str(ctx.channel.id)] = {'msg_id': message.id, 'reacted': []}
await asyncio.sleep(120)
try:
await message.delete()
except (discord.errors.NotFound, discord.errors.Forbidden):
pass
amount = len(self.channels[str(ctx.channel.id)]['reacted'])
word = "person has" if amount == 1 else "people have"
await ctx.send(f"**{amount}** {word} paid respects to **{filter_mass_mentions(answer)}**.")
del self.channels[str(ctx.channel.id)]
@commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
if str(reaction.message.channel.id) not in self.channels:
return
if self.channels[str(reaction.message.channel.id)]['msg_id'] != reaction.message.id:
return
if user.id == self.bot.user.id:
return
if user.id not in self.channels[str(reaction.message.channel.id)]['reacted']:
if str(reaction.emoji) == "\U0001f1eb":
await reaction.message.channel.send(f"**{user.name}** has paid their respects.")
self.channels[str(reaction.message.channel.id)]['reacted'].append(user.id)

5
pupper/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .pupper import Pupper
async def setup(bot):
bot.add_cog(Pupper(bot))

12
pupper/info.json Normal file
View File

@@ -0,0 +1,12 @@
{
"author": [
"aikaterna"
],
"description": "Pupper is a configurable pet that can hand out credits.",
"install_msg": "This cog and its countdowns are triggered by messages. If your bot doesn't see any messages posted anywhere in any server, the pet will never come around. This pet helps to keep chat active but it needs to be slightly active in the first place. If you see a message asking for pats (or whatever other greeting message you set), the first person to react with a wave emoji will get a random amount of credits. This cog also deletes the messages involved in the game, which can lead to channels showing an unread message when there really isn't anything there any more. Use `[p]help pets` to get started. If this command doesn't work for you, the `pets` command is only usable by people with the bot's Admin or Mod role, or if the user has administrative perms on the Discord server.",
"short": "Pet the dog.",
"tags": [
"pets"
],
"type": "COG"
}

225
pupper/pupper.py Normal file
View File

@@ -0,0 +1,225 @@
import asyncio
import datetime
import discord
import random
from redbot.core import commands, checks, Config, bank
from redbot.core.utils.chat_formatting import box, humanize_list
class Pupper(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2767241393, force_registration=True)
self.pets = {}
default_guild = {
"borf_msg": "borf! (thank for pats h00man, have a doggocoin)",
"channel": [],
"cooldown": 3600,
"credits": [100, 500],
"hello_msg": "Hi! Can someone pet me?",
"last_pet": "2019-08-01 00:00:00.000001",
"toggle": False,
}
self.config.register_guild(**default_guild)
@commands.guild_only()
@checks.mod_or_permissions(administrator=True)
@commands.group()
async def pets(self, ctx):
"""Manage your pet."""
pass
@pets.command()
async def toggle(self, ctx):
"""Toggle pets on the server."""
toggle = await self.config.guild(ctx.guild).toggle()
msg = f"Pets active: {not toggle}.\n"
await self.config.guild(ctx.guild).toggle.set(not toggle)
await ctx.send(msg)
@pets.command()
async def cooldown(self, ctx, seconds: int = None):
"""Set the pet appearance cooldown in seconds.
300s/5 minute minimum. Default is 3600s/1 hour."""
if not seconds:
seconds = 3600
if seconds < 300:
seconds = 300
await self.config.guild(ctx.guild).cooldown.set(seconds)
await ctx.send(f"Pet appearance cooldown set to {seconds}.")
@pets.command()
async def credits(self, ctx, min_amt: int, max_amt: int):
"""Set the pet credits range on successful petting."""
if min_amt > max_amt:
return await ctx.send("Min must be less than max.")
if min_amt < 1 or max_amt < 1:
return await ctx.send("Min and max amounts must be greater than 1.")
await self.config.guild(ctx.guild).credits.set([min_amt, max_amt])
await ctx.send(f"Pet credit range set to {min_amt} - {max_amt}.")
@pets.command()
async def hello(self, ctx, *, message: str = None):
"""Set the pet greeting message."""
if not message:
hello = await self.config.guild(ctx.guild).hello_msg()
return await ctx.send(
f"Current greeting message: `{hello}`\nUse this command with the message you would like to set."
)
if len(message) > 1000:
return await ctx.send("That dog sure likes to talk a lot. Try a shorter message.")
await self.config.guild(ctx.guild).hello_msg.set(message)
await ctx.send(f"Pet hello message set to: `{message}`.")
@pets.command()
async def thanks(self, ctx, *, message: str = None):
"""Set the pet thanks message."""
if not message:
bye = await self.config.guild(ctx.guild).borf_msg()
return await ctx.send(
f"Current thanks message: `{bye}`\nUse this command with the message you would like to set."
)
if len(message) > 1000:
return await ctx.send("That dog sure likes to talk a lot. Try a shorter message.")
await self.config.guild(ctx.guild).borf_msg.set(message)
await ctx.send(f"Pet thanks message set to: `{message}`.")
@commands.guild_only()
@checks.mod_or_permissions(administrator=True)
@pets.group(invoke_without_command=True)
async def channel(self, ctx):
"""Channel management for pet appearance."""
await ctx.send_help()
channel_list = await self.config.guild(ctx.guild).channel()
channel_msg = "Petting Channels:\n"
if not channel_list:
channel_msg += "None."
for chan in channel_list:
channel_obj = self.bot.get_channel(chan)
channel_msg += f"{channel_obj.name}\n"
await ctx.send(box(channel_msg))
@channel.command()
async def add(self, ctx, channel: discord.TextChannel):
"""Add a text channel for pets."""
channel_list = await self.config.guild(ctx.guild).channel()
if channel.id not in channel_list:
channel_list.append(channel.id)
await self.config.guild(ctx.guild).channel.set(channel_list)
await ctx.send(f"{channel.mention} added to the valid petting channels.")
else:
await ctx.send(f"{channel.mention} is already in the list of petting channels.")
@channel.command()
async def addall(self, ctx):
"""Add all valid channels for the guild that the bot can speak in."""
bot_text_channels = [
c
for c in ctx.guild.text_channels
if c.permissions_for(ctx.guild.me).send_messages is True
]
channel_list = await self.config.guild(ctx.guild).channel()
channels_appended = []
channels_in_list = []
for text_channel in bot_text_channels:
if text_channel.id not in channel_list:
channel_list.append(text_channel.id)
channels_appended.append(text_channel.mention)
else:
channels_in_list.append(text_channel.mention)
pass
first_msg = ""
second_msg = ""
await self.config.guild(ctx.guild).channel.set(channel_list)
if len(channels_appended) > 0:
first_msg = (
f"{humanize_list(channels_appended)} added to the valid petting channels.\n"
)
if len(channels_in_list) > 0:
second_msg = (
f"{humanize_list(channels_in_list)}: already in the list of petting channels."
)
await ctx.send(f"{first_msg}{second_msg}")
@channel.command()
async def remove(self, ctx, channel: discord.TextChannel):
"""Remove a text channel from petting."""
channel_list = await self.config.guild(ctx.guild).channel()
if channel.id in channel_list:
channel_list.remove(channel.id)
else:
return await ctx.send(f"{channel.mention} not in the active channel list.")
await self.config.guild(ctx.guild).channel.set(channel_list)
await ctx.send(f"{channel.mention} removed from the list of petting channels.")
@channel.command()
async def removeall(self, ctx):
"""Remove all petting channels from the list."""
await self.config.guild(ctx.guild).channel.set([])
await ctx.send("All channels have been removed from the list of petting channels.")
def _pet_lock(self, guild_id, tf):
if tf:
self.pets[guild_id] = True
else:
self.pets[guild_id] = False
@commands.Cog.listener()
async def on_message(self, message):
if isinstance(message.channel, discord.abc.PrivateChannel):
return
if message.author.bot:
return
guild_data = await self.config.guild(message.guild).all()
if not guild_data["toggle"]:
return
if not guild_data["channel"]:
return
self.pets.setdefault(message.guild.id, False)
if self.pets[message.guild.id]:
return
last_time = datetime.datetime.strptime(str(guild_data["last_pet"]), "%Y-%m-%d %H:%M:%S.%f")
now = datetime.datetime.now(datetime.timezone.utc)
now = now.replace(tzinfo=None)
if (
int((now - last_time).total_seconds())
> await self.config.guild(message.guild).cooldown()
):
self._pet_lock(message.guild.id, True)
rando_channel = random.choice(guild_data["channel"])
await asyncio.sleep(random.randint(60, 480))
rando_channel_obj = self.bot.get_channel(rando_channel)
borf_msg = await rando_channel_obj.send(guild_data["hello_msg"])
pets = "👋"
pets_action = {"veryfastpats": "👋"}
def check(r, u):
return r.message.id == borf_msg.id and any(e in str(r.emoji) for e in pets)
try:
r, u = await self.bot.wait_for("reaction_add", check=check, timeout=300.0)
except asyncio.TimeoutError:
return await borf_msg.delete()
reacts = {v: k for k, v in pets_action.items()}
react = reacts[r.emoji]
if react == "veryfastpats":
await borf_msg.delete()
deposit = random.randint(guild_data["credits"][0], guild_data["credits"][1])
await bank.deposit_credits(u, deposit)
credits_name = await bank.get_currency_name(message.guild)
await rando_channel_obj.send(
content=f"{guild_data['borf_msg']} (`+{deposit}` {credits_name})",
delete_after=10,
)
else:
pass
self._pet_lock(message.guild.id, False)
await self.config.guild(message.guild).last_pet.set(str(now))

5
region/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .region import Region
def setup(bot):
bot.add_cog(Region())

9
region/info.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name" : "region",
"author" : ["aikaterna"],
"short" : "Change the Discord server's region.",
"description" : "Change the Discord server's region with a command.",
"install_msg" : "Thanks for installing.",
"tags" : ["voice region", "region"],
"disabled" : false
}

49
region/region.py Normal file
View File

@@ -0,0 +1,49 @@
import discord
from redbot.core import checks, commands
from redbot.core.utils.chat_formatting import humanize_list
class Region(commands.Cog):
"""Change the guild voice region."""
@checks.mod_or_permissions(administrator=True)
@commands.cooldown(1, 60, discord.ext.commands.BucketType.guild)
@commands.command()
async def region(self, ctx, *, region: str):
"""Set the current guild's voice region."""
regions = [
"japan",
"singapore",
"eu-central",
"europe",
"india",
"us-central",
"london",
"eu-west",
"amsterdam",
"brazil",
"dubai",
"us-west",
"hongkong",
"us-south",
"southafrica",
"us-east",
"sydney",
"frankfurt",
"russia",
]
region = region.replace(" ", "-")
if region not in regions:
ctx.command.reset_cooldown(ctx)
return await ctx.send(
f"`{region}` was not found in the list of Discord voice regions. Valid regions are: {humanize_list(regions)}."
)
try:
await ctx.guild.edit(region=region)
except discord.errors.Forbidden:
return await ctx.send("I don't have permissions to edit this guild's settings.")
except discord.errors.HTTPException:
return await ctx.send(f"Error: An invalid server region was passed: `{region}`")
await ctx.send(
f"The voice server region for `{ctx.guild.name}` has been changed to `{region}`."
)

View File

@@ -12,9 +12,7 @@ import re
import unicodedata
BaseCog = getattr(commands, "Cog", object)
class Retrosign(BaseCog):
class Retrosign(commands.Cog):
"""Make an 80s retro sign. Originally by Anismash"""
def __init__(self, bot):
self.bot = bot
@@ -67,7 +65,7 @@ class Retrosign(BaseCog):
async with ctx.channel.typing():
async with self.session.post(
"http://photofunia.com/effects/retro-wave", data=data
"https://photofunia.com/effects/retro-wave", data=data
) as response:
if response.status == 200:
soup = bs(await response.text(), "html.parser")
@@ -79,5 +77,5 @@ class Retrosign(BaseCog):
image = discord.File(fp=temp_image, filename="image.png")
await ctx.channel.send(file=image)
def __unload(self):
self.session.detach()
def cog_unload(self):
self.bot.loop.create_task(self.session.close())

View File

@@ -2,6 +2,4 @@ from .rndstatus import RndStatus
def setup(bot):
n = RndStatus(bot)
bot.add_listener(n.switch_status, "on_message")
bot.add_cog(n)
bot.add_cog(RndStatus(bot))

View File

@@ -4,9 +4,7 @@ from random import choice as rndchoice
import time
BaseCog = getattr(commands, "Cog", object)
class RndStatus(BaseCog):
class RndStatus(commands.Cog):
"""Cycles random statuses or displays bot stats.
If a custom status is already set, it won't change it until
@@ -28,9 +26,9 @@ class RndStatus(BaseCog):
"Python",
"with your heart.",
],
"type": "1",
"streamer": "rndstatusstreamer",
"type": 1,
}
self.config.register_global(**default_global)
@commands.group(autohelp=True)
@@ -57,6 +55,19 @@ class RndStatus(BaseCog):
"Done. Redo this command with no parameters to see the current list of statuses."
)
@rndstatus.command(name="streamer")
async def _streamer(self, ctx: commands.Context, *, streamer=None):
"""Set the streamer needed for streaming statuses.
"""
saved_streamer = await self.config.streamer()
if streamer == None:
return await ctx.send("Current Streamer: " + saved_streamer)
await self.config.streamer.set(streamer)
await ctx.send(
"Done. Redo this command with no parameters to see the current streamer."
)
@rndstatus.command()
async def botstats(self, ctx, *statuses: str):
"""Toggle for a bot stats status instead of random messages."""
@@ -91,7 +102,8 @@ class RndStatus(BaseCog):
else:
await ctx.send("Type must be between 0 and 3.")
async def switch_status(self, message):
@commands.Cog.listener()
async def on_message(self, message):
try:
current_game = str(message.guild.me.activity.name)
except AttributeError:
@@ -99,6 +111,8 @@ class RndStatus(BaseCog):
statuses = await self.config.statuses()
botstats = await self.config.botstats()
prefix = await self.bot.db.prefix()
streamer = await self.config.streamer()
url = "https://www.twitch.tv/" + streamer
if botstats:
total_users = sum(len(s.members) for s in self.bot.guilds)
@@ -106,30 +120,44 @@ class RndStatus(BaseCog):
botstatus = f"{prefix[0]}help | {total_users} users | {servers} servers"
if self.last_change == None:
type = await self.config.type()
await self.bot.change_presence(
activity=discord.Activity(name=botstatus, type=type)
)
if type == 1:
await self.bot.change_presence(
activity=discord.Streaming(name=botstatus, url=url)
)
else:
await self.bot.change_presence(
activity=discord.Activity(name=botstatus, type=type)
)
self.last_change = int(time.perf_counter())
if message.author.id == self.bot.user.id:
return
delay = await self.config.delay()
if abs(self.last_change - int(time.perf_counter())) < int(delay):
return
self.last_change = int(time.perf_counter())
if (current_game != str(botstatus)) or current_game == None:
type = await self.config.type()
return await self.bot.change_presence(
activity=discord.Activity(name=botstatus, type=type)
)
if type == 1:
return await self.bot.change_presence(
activity=discord.Streaming(name=botstatus, url=url)
)
else:
return await self.bot.change_presence(
activity=discord.Activity(name=botstatus, type=type)
)
if self.last_change == None:
self.last_change = int(time.perf_counter())
if len(statuses) > 0 and (current_game in statuses or current_game == None):
new_status = self.random_status(message, statuses)
self.last_change = int(time.perf_counter())
type = await self.config.type()
await self.bot.change_presence(
activity=discord.Activity(name=new_status, type=type)
)
if type == 1:
await self.bot.change_presence(
activity=discord.Streaming(name=new_status, url=url)
)
else:
await self.bot.change_presence(
activity=discord.Activity(name=new_status, type=type)
)
if message.author.id != self.bot.user.id:
delay = await self.config.delay()
@@ -139,9 +167,14 @@ class RndStatus(BaseCog):
if current_game != new_status:
if current_game in statuses or current_game == None:
type = await self.config.type()
await self.bot.change_presence(
activity=discord.Activity(name=new_status, type=type)
)
if type == 1:
await self.bot.change_presence(
activity=discord.Streaming(name=new_status, url=url)
)
else:
await self.bot.change_presence(
activity=discord.Activity(name=new_status, type=type)
)
def random_status(self, msg, statuses):
try:

View File

@@ -1,5 +1,7 @@
from .seen import Seen
def setup(bot):
bot.add_cog(Seen(bot))
async def setup(bot):
cog = Seen(bot)
await cog.initialize()
bot.add_cog(cog)

View File

@@ -1,36 +1,100 @@
import asyncio
import contextlib
import datetime
from typing import Union
import discord
import time
from datetime import datetime
from redbot.core import Config, commands
_SCHEMA_VERSION = 2
BaseCog = getattr(commands, "Cog", object)
class Seen(BaseCog):
"""Shows last time a user was seen in chat"""
class Seen(commands.Cog):
"""Shows last time a user was seen in chat."""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2784481001, force_registration=True)
default_member = {"member_seen": []}
default_global = dict(schema_version=1)
default_member = dict(seen=None)
self.config.register_global(**default_global)
self.config.register_member(**default_member)
self._cache = {}
self._task = self.bot.loop.create_task(self._save_to_config())
async def initialize(self):
asyncio.ensure_future(
self._migrate_config(
from_version=await self.config.schema_version(), to_version=_SCHEMA_VERSION
)
)
async def _migrate_config(self, from_version: int, to_version: int):
if from_version == to_version:
return
elif from_version < to_version:
all_guild_data = await self.config.all_members()
users_data = {}
for guild_id, guild_data in all_guild_data.items():
for user_id, user_data in guild_data.items():
for _, v in user_data.items():
if not v:
v = None
if user_id not in users_data:
users_data[guild_id][user_id] = {"seen": v}
else:
if (v and not users_data[guild_id][user_id]["seen"]) or (
v
and users_data[guild_id][user_id]["seen"]
and v > users_data[guild_id][user_id]["seen"]
):
users_data[guild_id][user_id] = {"seen": v}
group = self.config._get_base_group(self.config.MEMBER) # Bulk update to new scope
async with group.all() as new_data:
for guild_id, member_data in users_data.items():
new_data[guild_id] = member_data
# new schema is now in place
await self.config.schema_version.set(_SCHEMA_VERSION)
# migration done, now let's delete all the old stuff
await self.config.clear_all_members()
@commands.guild_only()
@commands.command(name="seen")
@commands.bot_has_permissions(embed_links=True)
async def _seen(self, ctx, author: discord.Member):
"""Shows last time a user was seen in chat"""
member_seen = await self.config.member(author).member_seen()
now = int(time.time())
try:
time_elapsed = int(now - member_seen)
except TypeError:
"""Shows last time a user was seen in chat."""
member_seen_config = await self.config.member(author).seen()
member_seen_cache = self._cache.get(author.guild.id, {}).get(author.id, None)
if not member_seen_cache and not member_seen_config:
embed = discord.Embed(
colour=discord.Color.red(), title="I haven't seen that user yet."
)
return await ctx.send(embed=embed)
if not member_seen_cache:
member_seen = member_seen_config
elif not member_seen_config:
member_seen = member_seen_cache
elif member_seen_cache > member_seen_config:
member_seen = member_seen_cache
elif member_seen_config > member_seen_cache:
member_seen = member_seen_config
else:
member_seen = member_seen_cache or member_seen_config
now = int(time.time())
time_elapsed = int(now - member_seen)
output = self._dynamic_time(time_elapsed)
if output[2] < 1:
ts = "just now"
else:
@@ -48,26 +112,88 @@ class Seen(BaseCog):
elif output[2] > 1:
ts += "{} minutes ago".format(output[2])
em = discord.Embed(colour=discord.Color.green())
avatar = author.avatar_url if author.avatar else author.default_avatar_url
avatar = author.avatar_url or author.default_avatar_url
em.set_author(name="{} was seen {}".format(author.display_name, ts), icon_url=avatar)
await ctx.send(embed=em)
def _dynamic_time(self, time_elapsed):
@staticmethod
def _dynamic_time(time_elapsed):
m, s = divmod(time_elapsed, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
return (d, h, m)
return d, h, m
@commands.Cog.listener()
async def on_message(self, message):
if (
not isinstance(message.channel, discord.abc.PrivateChannel)
and self.bot.user.id != message.author.id
):
prefixes = await self.bot.get_prefix(message)
if not any(message.content.startswith(n) for n in prefixes):
author = message.author
ts = int(time.time())
try:
await self.config.member(author).member_seen.set(ts)
except AttributeError:
pass
if getattr(message, "guild", None):
if message.guild.id not in self._cache:
self._cache[message.guild.id] = {}
self._cache[message.guild.id][message.author.id] = int(time.time())
@commands.Cog.listener()
async def on_typing(
self,
channel: discord.abc.Messageable,
user: Union[discord.User, discord.Member],
when: datetime.datetime,
):
if getattr(user, "guild", None):
if user.guild.id not in self._cache:
self._cache[user.guild.id] = {}
self._cache[user.guild.id][user.id] = int(time.time())
@commands.Cog.listener()
async def on_message_edit(self, before: discord.Message, after: discord.Message):
if getattr(after, "guild", None):
if after.guild.id not in self._cache:
self._cache[after.guild.id] = {}
self._cache[after.guild.id][after.author.id] = int(time.time())
@commands.Cog.listener()
async def on_reaction_remove(
self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]
):
if getattr(user, "guild", None):
if user.guild.id not in self._cache:
self._cache[user.guild.id] = {}
self._cache[user.guild.id][user.id] = int(time.time())
@commands.Cog.listener()
async def on_reaction_add(
self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]
):
if getattr(user, "guild", None):
if user.guild.id not in self._cache:
self._cache[user.guild.id] = {}
self._cache[user.guild.id][user.id] = int(time.time())
def cog_unload(self):
self.bot.loop.create_task(self._clean_up())
async def _clean_up(self):
if self._task:
self._task.cancel()
if self._cache:
group = self.config._get_base_group(self.config.MEMBER) # Bulk update to config
async with group.all() as new_data:
for guild_id, member_data in self._cache.items():
if str(guild_id) not in new_data:
new_data[str(guild_id)] = {}
for member_id, seen in member_data.items():
new_data[str(guild_id)][str(member_id)] = {"seen": seen}
async def _save_to_config(self):
await self.bot.wait_until_ready()
with contextlib.suppress(asyncio.CancelledError):
while True:
users_data = self._cache.copy()
self._cache = {}
group = self.config._get_base_group(self.config.MEMBER) # Bulk update to config
async with group.all() as new_data:
for guild_id, member_data in users_data.items():
if str(guild_id) not in new_data:
new_data[str(guild_id)] = {}
for member_id, seen in member_data.items():
new_data[str(guild_id)][str(member_id)] = {"seen": seen}
await asyncio.sleep(60)

5
snacktime/__init__.py Executable file
View File

@@ -0,0 +1,5 @@
from .snacktime import Snacktime
async def setup(bot):
bot.add_cog(Snacktime(bot))

9
snacktime/info.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name" : "Snacktime",
"author" : ["irdumb", "aikaterna"],
"short" : "ʕ •ᴥ•ʔ < It's snacktime, who wants snacks?",
"description" : "snackburr will come around every-so-often if you've asked him to.\nI hear snackburr likes to come around more often when people are partyin.",
"install_msg" : "A snack delivery bear has arrived ʕ•ᴥ• ʔ",
"tags" : ["snack", "snacktime", "snackburr", "party", "party time"],
"disabled" : false
}

73
snacktime/phrases.py Normal file
View File

@@ -0,0 +1,73 @@
FRIENDS = {
"Snackburr": "ʕ •ᴥ•ʔ <",
"Pancakes": "₍⸍⸌̣ʷ̣̫⸍̣⸌₎ <",
"Mr Pickles": "(=`ェ´=) <",
"Satin": "▼・ᴥ・▼ <",
"Thunky": "ᘛ⁐̤ᕐᐷ <",
"Jingle": "꒰∗´꒳`꒱ <",
"FluffButt": "/ᐠ。ꞈ。ᐟ\ <",
"Staplefoot": "( ̄(エ) ̄) <",
}
SNACKBURR_PHRASES = {
"SNACKTIME": [
"It's snack time!",
"I'm back with s'more snacks! Who wants!?",
"I'm back errbody! Who wants some snacks!?",
"Woo man those errands are crazy! Anyways, anybody want some snacks?",
"I got snacks! If nobody wants em, I'm gonna eat em all!!",
"Hey, I'm back! Anybody in the mood for some snacks?!",
"Heyyaaayayyyaya! I say Hey, I got snacks!",
"Heyyaaayayyyaya! I say Hey, What's goin on?... I uh.. I got snacks.",
"If anybody has reason why these snacks and my belly should not be wed, speak now or forever hold your peace!",
"Got another snack delivery guys!",
"Did somebody say snacks?!?! o/",
"Choo Choo! it's the pb train! Come on over guys!",
"Snacks are here! Dig in! Who wants a plate?",
"Pstt.. I got the snacks you were lookin for. <.<",
"I hope you guys are hungry! Cause i'm loaded to the brim with snacks!!!",
"I was hungry on the way over so I kinda started without you guys :3 Who wants snacks!?!",
"Beep beep! I got a snack delivery comin in! Who wants snacks!",
"Guess what time it is?! It's snacktime!! Who wants?!",
"Hey check out this sweet stach o' snacks I found! Who wants a cut?",
"Who's ready to gobble down some snacks!?",
"So who's gonna help me eat all these snacks? :3",
],
"OUT": [
"I'm out of snacks! I'll be back with more soon.",
"I'm out of snacks :( I'll be back soon with more!",
"Aight, I gotta head out! I'll be back with more, don worry :3",
"Alright, I gotta get back to my errands. I'll see you guys soon!",
],
"LONELY": ["I guess you guys don't like snacktimes.. I'll stop comin around."],
"NO_TAKERS": [
"I guess nobody wants snacks... more for me!",
"Guess nobody's here.. I'll just head out then",
"I don't see anybody.. <.< ... >.> ... All the snacks for me!!",
"I guess nobody wants snacks huh.. Well, I'll come back later",
"I guess i'll just come back later..",
],
"GIVE": [
"Here ya go, {0}, here's {1} pb!",
"Alright here ya go, {0}, {1} pb for you!",
"Yeah! Here you go, {0}! {1} pb!",
"Of course {0}! Here's {1} pb!",
"Ok {0}, here's {1} pb for you. Anyone else want some?",
"Alllright, {1} pb for {0}!",
"Hold your horses {0}! Alright, {1} pb for you :)",
],
"LAST_SECOND": [
"Fine fine, {0}, I'll give you {1} of my on-the-road pb.. Cya!",
"Oh! {0}, you caught me right before I left! Alright, i'll give you {1} of my own pb",
],
"GREEDY": [
"Don't be greedy now! you already got some pb {0}!",
"You already got your snacks {0}!",
"Come on {0}, you already got your snacks! We gotta make sure there's some for errbody!",
],
"ENABLE": [
"Oh you guys want snacks?! Aight, I'll come around every so often to hand some out!"
],
"DISABLE": ["You guys don't want snacks anymore? Alright, I'll stop comin around."],
}

466
snacktime/snacktime.py Normal file
View File

@@ -0,0 +1,466 @@
import asyncio
import discord
import logging
from random import randint
from random import choice as randchoice
from redbot.core import bank, checks, commands, Config
from redbot.core.utils.chat_formatting import box, humanize_list, pagify
from .phrases import FRIENDS, SNACKBURR_PHRASES
log = logging.getLogger("red.aikaterna.snacktime")
class Snacktime(commands.Cog):
"""Snackburr's passing out pb jars!"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2712291001, force_registration=True)
self.snackSchedule = {}
self.snacktimePrediction = {}
self.previousSpeaker = {}
self.snackInProgress = {}
self.acceptInput = {}
self.alreadySnacked = {}
self.msgsPassed = {}
self.startLock = {}
self.snacktimeCheckLock = {}
self.lockRequests = {}
self.channel_persona = {}
default_guild = {
"DELIVER_CHANNELS": [],
"FRIENDS": False,
"EVENT_START_DELAY": 1800,
"EVENT_START_DELAY_VARIANCE": 900,
"SNACK_DURATION": 240,
"SNACK_DURATION_VARIANCE": 120,
"MSGS_BEFORE_EVENT": 8,
"SNACK_AMOUNT": 200,
}
default_channel = {"repeatMissedSnacktimes": 0}
self.config.register_guild(**default_guild)
self.config.register_channel(**default_channel)
async def persona_choice(self, msg):
invite_friends = await self.config.guild(msg.guild).FRIENDS()
personas = FRIENDS
if not invite_friends:
return "Snackburr"
elif invite_friends is True:
del personas["Snackburr"]
return randchoice(list(personas.keys()))
async def get_response(self, msg, phrase_type):
scid = f"{msg.guild.id}-{msg.channel.id}"
persona = self.channel_persona[scid]
persona_phrase = FRIENDS.get(persona)
phrase = randchoice(SNACKBURR_PHRASES[phrase_type])
return f"`{persona_phrase} {phrase}`"
@commands.guild_only()
@commands.group()
@checks.mod_or_permissions(manage_guild=True)
async def snackset(self, ctx):
"""snack stuff"""
if ctx.invoked_subcommand is None:
guild_data = await self.config.guild(ctx.guild).all()
if not guild_data["DELIVER_CHANNELS"]:
channel_names = ["No channels set."]
else:
channel_names = []
for channel_id in guild_data["DELIVER_CHANNELS"]:
channel_obj = self.bot.get_channel(channel_id)
channel_names.append(channel_obj.name)
if guild_data["FRIENDS"] is True:
invite_friends = "Friends only"
elif guild_data["FRIENDS"] is False:
invite_friends = "Snackburr only"
else:
invite_friends = "Everyone's invited!"
msg = f"[Delivering in]: {humanize_list(channel_names)}\n"
msg += f"[Event start delay]: {guild_data['EVENT_START_DELAY']} seconds\n"
msg += (
f"[Event start variance]: {guild_data['EVENT_START_DELAY_VARIANCE']} seconds\n"
)
msg += f"[Friends status]: {invite_friends}\n"
msg += f"[Messages before event]: {guild_data['MSGS_BEFORE_EVENT']}\n"
msg += f"[Snack amount limit]: {guild_data['SNACK_AMOUNT']} pb\n"
msg += f"[Snack duration]: {guild_data['SNACK_DURATION']} seconds\n"
msg += f"[Snack duration variance]: {guild_data['SNACK_DURATION_VARIANCE']} seconds\n"
for page in pagify(msg, delims=["\n"]):
await ctx.send(box(page, lang="ini"))
@snackset.command()
async def errandtime(self, ctx, seconds: int):
"""How long snackburr needs to be out doin errands.. more or less."""
event_start_delay_variance = await self.config.guild(
ctx.guild
).EVENT_START_DELAY_VARIANCE()
if seconds <= event_start_delay_variance:
await ctx.send("errandtime must be greater than errandvariance!")
elif seconds <= 0:
await ctx.send("errandtime must be greater than 0")
else:
await self.config.guild(ctx.guild).EVENT_START_DELAY.set(seconds)
await ctx.send(
f"snackburr's errands will now take around {round(seconds/60, 2)} minutes!"
)
@snackset.command()
async def errandvariance(self, ctx, seconds: int):
"""How early or late snackburr might be to snacktime"""
event_start_delay = await self.config.guild(ctx.guild).EVENT_START_DELAY()
if seconds >= event_start_delay:
await ctx.send("errandvariance must be less than errandtime!")
elif seconds < 0:
await ctx.send("errandvariance must be 0 or greater!")
else:
await self.config.guild(ctx.guild).EVENT_START_DELAY_VARIANCE.set(seconds)
await ctx.send(
f"snackburr now might be {round(seconds/60, 2)} minutes early or late to snacktime"
)
@snackset.command(name="snacktime")
async def snacktimetime(self, ctx, seconds: int):
"""How long snackburr will hang out giving out snacks!.. more or less."""
snack_duration_variance = await self.config.guild(ctx.guild).SNACK_DURATION_VARIANCE()
if seconds <= snack_duration_variance:
await ctx.send("snacktime must be greater than snackvariance!")
elif seconds <= 0:
await ctx.send("snacktime must be greater than 0")
else:
await self.config.guild(ctx.guild).SNACK_DURATION.set(seconds)
await ctx.send(f"snacktimes will now last around {round(seconds/60, 2)} minutes!")
@snackset.command(name="snackvariance")
async def snacktimevariance(self, ctx, seconds: int):
"""How early or late snackburr might have to leave for errands"""
snack_duration = await self.config.guild(ctx.guild).SNACK_DURATION()
if seconds >= snack_duration:
await ctx.send("snackvariance must be less than snacktime!")
elif seconds < 0:
await ctx.send("snackvariance must be 0 or greater!")
else:
await self.config.guild(ctx.guild).SNACK_DURATION_VARIANCE.set(seconds)
await ctx.send(
f"snackburr now may have to leave snacktime {round(seconds/60, 2)} minutes early or late"
)
@snackset.command()
async def msgsneeded(self, ctx, amt: int):
"""How many messages must pass in a conversation before a snacktime can start"""
if amt <= 0:
await ctx.send("msgsneeded must be greater than 0")
else:
await self.config.guild(ctx.guild).MSGS_BEFORE_EVENT.set(amt)
await ctx.send(
f"snackburr will now wait until {amt} messages pass until he comes with snacks"
)
@snackset.command()
async def amount(self, ctx, amt: int):
"""How much pb max snackburr should give out to each person per snacktime"""
if amt <= 0:
await ctx.send("amount must be greater than 0")
else:
await self.config.guild(ctx.guild).SNACK_AMOUNT.set(amt)
await ctx.send(f"snackburr will now give out {amt} pb max per person per snacktime.")
@snackset.command(name="friends")
async def snackset_friends(self, ctx, choice: int):
"""snackburr's friends wanna know what all the hub-bub's about!
Do you want to
1: invite them to the party,
2: only allow snackburr to chillax with you guys, or
3: kick snackburr out on the curb in favor of his obviously cooler friends?
"""
if choice not in (1, 2, 3):
return await ctx.send_help()
choices = {
1: ("both", "Everybody's invited!"),
2: (False, "You chose to not invite snackburr's friends."),
3: (True, "You kick snackburr out in favor of his friends! Ouch. Harsh..."),
}
choice = choices[choice]
await self.config.guild(ctx.guild).FRIENDS.set(choice[0])
await ctx.send(choice[1])
@snackset.command()
async def deliver(self, ctx):
"""Asks snackburr to start delivering to this channel"""
deliver_channels = await self.config.guild(ctx.guild).DELIVER_CHANNELS()
if not deliver_channels:
deliver_channels = []
if ctx.channel.id not in deliver_channels:
deliver_channels.append(ctx.channel.id)
await self.config.guild(ctx.guild).DELIVER_CHANNELS.set(deliver_channels)
await ctx.send("snackburr will start delivering here!")
else:
deliver_channels.remove(ctx.channel.id)
await self.config.guild(ctx.guild).DELIVER_CHANNELS.set(deliver_channels)
await ctx.send("snackburr will stop delivering here!")
@commands.guild_only()
@commands.command()
async def snacktime(self, ctx):
"""Man i'm hungry! When's snackburr gonna get back with more snacks?"""
scid = f"{ctx.message.guild.id}-{ctx.message.channel.id}"
if self.snacktimePrediction.get(scid, None) == None:
if self.acceptInput.get(scid, False):
return
else:
phrases = [
"Don't look at me. I donno where snackburr's at ¯\_(ツ)_/¯",
"I hear snackburr likes parties. *wink wink",
"I hear snackburr is attracted to channels with active conversations",
"If you party, snackburr will come! 〈( ^o^)",
]
await ctx.send(randchoice(phrases))
return
seconds = self.snacktimePrediction[scid] - self.bot.loop.time()
if self.snacktimeCheckLock.get(scid, False):
if randint(1, 4) == 4:
await ctx.send("Hey, snackburr's on errands. I ain't his keeper Kappa")
return
self.snacktimeCheckLock[scid] = True
if seconds < 0:
await ctx.send(
f"I'm not sure where snackburr is.. He's already {round(abs(seconds/60), 2)} minutes late!"
)
else:
await ctx.send(
f"snackburr's out on errands! I think he'll be back in {round(seconds/60, 2)} minutes"
)
await asyncio.sleep(40)
self.snacktimeCheckLock[scid] = False
async def startSnack(self, message):
scid = f"{message.guild.id}-{message.channel.id}"
if self.acceptInput.get(scid, False):
return
self.channel_persona[scid] = await self.persona_choice(message)
await message.channel.send(await self.get_response(message, "SNACKTIME"))
self.acceptInput[scid] = True
self.alreadySnacked[scid] = []
guild_data = await self.config.guild(message.guild).all()
duration = guild_data["SNACK_DURATION"] + randint(
-guild_data["SNACK_DURATION_VARIANCE"], guild_data["SNACK_DURATION_VARIANCE"]
)
await asyncio.sleep(duration)
# sometimes fails sending messages and stops all future snacktimes. Hopefully this fixes it.
try:
# list isn't empty
if self.alreadySnacked.get(scid, False):
await message.channel.send(await self.get_response(message, "OUT"))
await self.config.channel(message.channel).repeatMissedSnacktimes.set(0)
else:
await message.channel.send(await self.get_response(message, "NO_TAKERS"))
repeat_missed_snacktimes = await self.config.channel(
message.channel
).repeatMissedSnacktimes()
await self.config.channel(message.channel).repeatMissedSnacktimes.set(
repeat_missed_snacktimes + 1
)
await asyncio.sleep(2)
if (repeat_missed_snacktimes + 1) > 9: # move to a setting
await message.channel.send(await self.get_response(message, "LONELY"))
deliver_channels = await self.config.guild(message.guild).DELIVER_CHANNELS()
new_deliver_channels = deliver_channels.remove(message.channel.id)
await self.config.guild(message.guild).DELIVER_CHANNELS.set(
new_deliver_channels
)
await self.config.channel(message.channel).repeatMissedSnacktimes.set(0)
except:
log.error("Snacktime: Failed to send message in startSnack")
self.acceptInput[scid] = False
self.snackInProgress[scid] = False
@commands.Cog.listener()
async def on_message(self, message):
if not message.guild:
return
if message.author.bot:
return
if not message.channel.permissions_for(message.guild.me).send_messages:
return
deliver_channels = await self.config.guild(message.guild).DELIVER_CHANNELS()
if not deliver_channels:
return
if message.channel.id not in deliver_channels:
return
scid = f"{message.guild.id}-{message.channel.id}"
if message.author.id != self.bot.user.id:
# if nobody has said anything since start
if self.previousSpeaker.get(scid, None) == None:
self.previousSpeaker[scid] = message.author.id
# if new speaker
elif self.previousSpeaker[scid] != message.author.id:
self.previousSpeaker[scid] = message.author.id
msgTime = self.bot.loop.time()
# if there's a scheduled snack
if self.snackSchedule.get(scid, None) != None:
# if it's time for a snack
if msgTime > self.snackSchedule[scid]:
# 1 schedule at a time, so remove schedule
self.snackSchedule[scid] = None
self.snackInProgress[scid] = True
# wait to make it more natural
naturalWait = randint(30, 240)
log.debug(f"Snacktime: snack trigger msg: {message.content}")
log.debug(f"Snacktime: Waiting {str(naturalWait)} seconds")
await asyncio.sleep(naturalWait)
# start snacktime
await self.startSnack(message)
# if no snack coming, schedule one
elif self.snackInProgress.get(scid, False) == False and not self.startLock.get(
scid, False
):
self.msgsPassed[scid] = self.msgsPassed.get(scid, 0) + 1
# check for collisions
msgs_before_event = await self.config.guild(message.guild).MSGS_BEFORE_EVENT()
if self.msgsPassed[scid] > msgs_before_event:
self.startLock[scid] = True
if self.lockRequests.get(scid, None) == None:
self.lockRequests[scid] = []
self.lockRequests[scid].append(message)
await asyncio.sleep(1)
log.debug(
f"Snacktime: :-+-|||||-+-: Lock request: {str(self.lockRequests[scid][0] == message)}"
)
if self.lockRequests[scid][0] == message:
await asyncio.sleep(5)
log.debug(f"Snacktime: {message.author.name} - I got the Lock")
self.lockRequests[scid] = []
# someone got through already
if self.msgsPassed[
scid
] < msgs_before_event or self.snackInProgress.get(scid, False):
log.debug("Snacktime: Lock: someone got through already.")
return
else:
log.debug(
"Snacktime: Lock: looks like i'm in the clear. lifting lock. If someone comes now, they should get the lock"
)
self.msgsPassed[scid] = msgs_before_event
self.startLock[scid] = False
else:
log.debug(f"Snacktime: {message.author.name} Failed lock")
return
if self.msgsPassed[scid] == msgs_before_event:
# schedule a snack
log.debug(f"Snacktime: activity: {message.content}")
guild_data = await self.config.guild(message.guild).all()
timeTillSnack = guild_data["EVENT_START_DELAY"] + randint(
-guild_data["EVENT_START_DELAY_VARIANCE"],
guild_data["EVENT_START_DELAY_VARIANCE"],
)
log.debug(f"Snacktime: {str(timeTillSnack)} seconds till snacktime")
self.snacktimePrediction[scid] = msgTime + guild_data["EVENT_START_DELAY"]
self.snackSchedule[scid] = msgTime + timeTillSnack
self.msgsPassed[scid] = 0
# it's snacktime! who want's snacks?
if self.acceptInput.get(scid, False):
if message.author.id not in self.alreadySnacked.get(scid, []):
agree_phrases = [
"holds out hand",
"im ready",
"i'm ready",
"hit me up",
"hand over",
"hand me",
"kindly",
"i want",
"i'll have",
"ill have",
"yes",
"pls",
"plz",
"please",
"por favor",
"can i",
"i'd like",
"i would",
"may i",
"in my mouth",
"in my belly",
"snack me",
"gimme",
"give me",
"i'll take",
"ill take",
"i am",
"about me",
"me too",
"of course",
]
userWants = False
for agreePhrase in agree_phrases:
# no one word answers
if (
agreePhrase in message.content.lower()
and len(message.content.split()) > 1
):
userWants = True
break
if userWants:
if self.alreadySnacked.get(scid, None) == None:
self.alreadySnacked[scid] = []
self.alreadySnacked[scid].append(message.author.id)
await asyncio.sleep(randint(1, 6))
snack_amount = await self.config.guild(message.guild).SNACK_AMOUNT()
snackAmt = randint(1, snack_amount)
try:
if self.acceptInput.get(scid, False):
resp = await self.get_response(message, "GIVE")
resp = resp.format(message.author.name, snackAmt)
await message.channel.send(resp)
else:
resp = await self.get_response(message, "LAST_SECOND")
resp = resp.format(message.author.name, snackAmt)
await message.channel.send(resp)
await bank.deposit_credits(message.author, snackAmt)
except:
log.info(
f"Snacktime: Failed to send pb message. {message.author.name} didn't get pb"
)
else:
more_phrases = [
"more pl",
"i have some more",
"i want more",
"i have another",
"i have more",
"more snack",
]
userWants = False
for morePhrase in more_phrases:
if morePhrase in message.content.lower():
userWants = True
break
if userWants:
await asyncio.sleep(randint(1, 6))
if self.acceptInput.get(scid, False):
resp = await self.get_response(message, "GREEDY")
await message.channel.send(resp.format(message.author.name))

View File

@@ -137,7 +137,7 @@ class Timezone(commands.Cog):
time = time.strftime(fmt)
await ctx.send(
f"{user.name}'s current timezone is: **{usertime}**\n"
f"They current time is: {str(time)}"
f"The current time is: {str(time)}"
)
else:
await ctx.send("That user hasn't set their timezone.")

View File

@@ -11,10 +11,8 @@ from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
from tabulate import tabulate
from contextlib import suppress as sps
BaseCog = getattr(commands, "Cog", object)
class Tools(BaseCog):
class Tools(commands.Cog):
"""Mod and Admin tools."""
def __init__(self, bot):
@@ -181,7 +179,7 @@ class Tools(BaseCog):
bancount = len(banlist)
ban_list = []
if bancount == 0:
banlist = "No users are banned from this server."
msg = "No users are banned from this server."
else:
msg = ""
for user_obj in banlist:
@@ -359,7 +357,7 @@ class Tools(BaseCog):
)
await awaiter.edit(embed=embed)
@commands.command(name='listguilds', aliases=['listservers', 'guildlist', 'serverlist'])
@commands.command(name="listguilds", aliases=["listservers", "guildlist", "serverlist"])
@checks.mod_or_permissions()
async def listguilds(self, ctx):
"""
@@ -367,22 +365,32 @@ class Tools(BaseCog):
"""
asciidoc = lambda m: "```asciidoc\n{}\n```".format(m)
guilds = sorted(self.bot.guilds, key=lambda g: -g.member_count)
header = ("```\n"
"The bot is in the following {} server{}\n"
"```").format(len(guilds), 's' if len(guilds) > 1 else '')
header = ("```\n" "The bot is in the following {} server{}:\n" "```").format(
len(guilds), "s" if len(guilds) > 1 else ""
)
max_zpadding = max([len(str(g.member_count)) for g in guilds])
form = "{gid} :: {mems:0{zpadding}} :: {name}"
all_forms = [form.format(gid=g.id, mems=g.member_count, name=g.name, zpadding=max_zpadding) for g in guilds]
final = '\n'.join(all_forms)
all_forms = [
form.format(
gid=g.id, mems=g.member_count, name=cf.escape(g.name), zpadding=max_zpadding
)
for g in guilds
]
final = "\n".join(all_forms)
await ctx.send(header)
for page in cf.pagify(final, delims=['\n'], shorten_by=16):
await ctx.send(asciidoc(page))
page_list = []
for page in cf.pagify(final, delims=["\n"], page_length=1000):
page_list.append(asciidoc(page))
if len(page_list) == 1:
return await ctx.send(asciidoc(page))
await menu(ctx, page_list, DEFAULT_CONTROLS)
@commands.guild_only()
@checks.mod_or_permissions(manage_channels=True)
@commands.command(name='listchannel', aliases=['channellist'])
@commands.command(name="listchannel", aliases=["channellist"])
async def listchannel(self, ctx):
"""
List the channels of the current server
@@ -391,15 +399,17 @@ class Tools(BaseCog):
channels = ctx.guild.channels
top_channels, category_channels = self.sort_channels(ctx.guild.channels)
topChannels_formed = '\n'.join(self.channels_format(top_channels))
categories_formed = '\n\n'.join([self.category_format(tup) for tup in category_channels])
topChannels_formed = "\n".join(self.channels_format(top_channels))
categories_formed = "\n\n".join([self.category_format(tup) for tup in category_channels])
await ctx.send(f"{ctx.guild.name} has {len(channels)} channel{'s' if len(channels) > 1 else ''}.")
await ctx.send(
f"{ctx.guild.name} has {len(channels)} channel{'s' if len(channels) > 1 else ''}."
)
for page in cf.pagify(topChannels_formed, delims=['\n'], shorten_by=16):
for page in cf.pagify(topChannels_formed, delims=["\n"], shorten_by=16):
await ctx.send(asciidoc(page))
for page in cf.pagify(categories_formed, delims=['\n\n'], shorten_by=16):
for page in cf.pagify(categories_formed, delims=["\n\n"], shorten_by=16):
await ctx.send(asciidoc(page))
@commands.guild_only()
@@ -569,6 +579,29 @@ class Tools(BaseCog):
)
)
@commands.guild_only()
@commands.command(aliases=["listroles"])
@checks.mod_or_permissions(manage_guild=True)
async def rolelist(self, ctx):
"""Displays the server's roles."""
form = "`{rpos:0{zpadding}}` - `{rid}` - `{rcolor}` - {rment} "
max_zpadding = max([len(str(r.position)) for r in ctx.guild.roles])
rolelist = [
form.format(rpos=r.position, zpadding=max_zpadding, rid=r.id, rment=r.mention, rcolor=r.color)
for r in ctx.guild.roles
]
rolelist = sorted(rolelist, reverse=True)
rolelist = "\n".join(rolelist)
embed_list = []
for page in cf.pagify(rolelist, shorten_by=1400):
embed = discord.Embed(
description=f"**Total roles:** {len(ctx.guild.roles)}\n\n{page}",
colour=await ctx.embed_colour(),
)
embed_list.append(embed)
await menu(ctx, embed_list, DEFAULT_CONTROLS)
@commands.command(hidden=True)
async def sharedservers(self, ctx, user: discord.Member = None):
"""Shows shared server info. Defaults to author."""
@@ -809,7 +842,10 @@ class Tools(BaseCog):
channels.pop(channels.index(c))
temp[c.category].append(c)
category_channels = sorted([(cat, sorted(chans, key=lambda c: c.position)) for cat, chans in temp.items()], key=lambda t: t[0].position)
category_channels = sorted(
[(cat, sorted(chans, key=lambda c: c.position)) for cat, chans in temp.items()],
key=lambda t: t[0].position,
)
return channels, category_channels
def channels_format(self, channels: list):
@@ -824,8 +860,14 @@ class Tools(BaseCog):
name_justify = max([len(c.name[:24]) for c in channels])
type_justify = max([len(type_name(c)) for c in channels])
return [channel_form.format(name=c.name[:24].ljust(name_justify), ctype=type_name(c).ljust(type_justify), cid=c.id) for c in channels]
return [
channel_form.format(
name=c.name[:24].ljust(name_justify),
ctype=type_name(c).ljust(type_justify),
cid=c.id,
)
for c in channels
]
def category_format(self, cat_chan_tuple: tuple):
@@ -834,7 +876,7 @@ class Tools(BaseCog):
chfs = self.channels_format(chs)
if chfs != []:
ch_forms = ['\t' + f for f in chfs]
return '\n'.join([f'{cat.name} :: {cat.id}'] + ch_forms)
ch_forms = ["\t" + f for f in chfs]
return "\n".join([f"{cat.name} :: {cat.id}"] + ch_forms)
else:
return '\n'.join([f'{cat.name} :: {cat.id}'] + ['\tNo Channels'])
return "\n".join([f"{cat.name} :: {cat.id}"] + ["\tNo Channels"])

View File

@@ -2,15 +2,20 @@ import asyncio
import datetime
import discord
import random
import math
from typing import Union # <- Remove this at 3.2
from babel.numbers import format_decimal # <- Remove this at 3.2
from redbot.core import commands, checks, Config, bank
from redbot.core.utils.chat_formatting import box, pagify
from redbot.core.utils.chat_formatting import box, pagify #, humanize_number <- Will be for 3.2
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
__version__ = "0.0.5"
BaseCog = getattr(commands, "Cog", object)
__version__ = "0.0.6a"
def humanize_number(val: Union[int, float]): # <- Remove this at 3.2
return format_decimal(val, locale="en_US")
class TrickOrTreat(BaseCog):
class TrickOrTreat(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2710311393, force_registration=True)
@@ -39,8 +44,10 @@ class TrickOrTreat(BaseCog):
pick = await self.config.guild(ctx.guild).pick()
if not candy_type:
candy_type = "candies"
if number <= 0:
number == 1
if number < 0:
return await ctx.send("That doesn't sound fun.")
if number == 0:
return await ctx.send("You pretend to eat a candy.")
if candy_type in ["candies", "candy"]:
candy_type = "candies"
if candy_type in ["lollipops", "lollipop"]:
@@ -127,7 +134,9 @@ class TrickOrTreat(BaseCog):
)
pluralcandy = "candy" if number == 1 else "candies"
await ctx.send(f"{random.choice(eat_phrase)} {number} {pluralcandy}.")
await ctx.send(
f"{random.choice(eat_phrase)} {number} {pluralcandy}. (Total eaten: `{humanize_number(await self.config.user(ctx.author).eaten() + number)}` \N{CANDY})"
)
await self.config.user(ctx.author).sickness.set(userdata["sickness"] + (number * 2))
await self.config.user(ctx.author).candies.set(userdata["candies"] - number)
await self.config.user(ctx.author).eaten.set(userdata["eaten"] + number)
@@ -154,7 +163,7 @@ class TrickOrTreat(BaseCog):
@commands.guild_only()
@checks.mod_or_permissions(administrator=True)
@commands.command()
async def balance(self, ctx):
async def totbalance(self, ctx):
"""Check how many candies are 'on the ground' in the guild."""
pick = await self.config.guild(ctx.guild).pick()
await ctx.send(f"The guild is currently holding: {pick} \N{CANDY}")
@@ -179,68 +188,111 @@ class TrickOrTreat(BaseCog):
@commands.guild_only()
@commands.command()
@commands.bot_has_permissions(embed_links=True, add_reactions=True)
async def cboard(self, ctx):
"""Show the candy eating leaderboard."""
userinfo = await self.config._all_from_scope(scope="USER")
if not userinfo:
return await ctx.send("No one has any candy.")
message = await ctx.send("Populating leaderboard...")
sorted_acc = sorted(userinfo.items(), key=lambda x: x[1]["eaten"], reverse=True)
msg = "{name:33}{score:19}\n".format(name="Name", score="Candies Eaten")
for i, account in enumerate(sorted_acc):
async with ctx.typing():
sorted_acc = sorted(userinfo.items(), key=lambda x: x[1]["eaten"], reverse=True)
# Leaderboard logic from https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/cogs/economy/economy.py#L445
pound_len = len(str(len(sorted_acc)))
score_len = 10
header = "{pound:{pound_len}}{score:{score_len}}{name:2}\n".format(
pound="#",
pound_len=pound_len + 3,
score="Candies Eaten",
score_len=score_len + 6,
name="\N{THIN SPACE}" + "Name"
if not str(ctx.author.mobile_status) in ["online", "idle", "dnd"]
else "Name",
)
temp_msg = header
for pos, account in enumerate(sorted_acc):
if account[1]["eaten"] == 0:
continue
user_idx = i + 1
user_obj = await self.bot.get_user_info(account[0])
try:
if account[0] in [member.id for member in ctx.guild.members]:
user_obj = ctx.guild.get_member(account[0])
else:
user_obj = await self.bot.fetch_user(account[0])
except AttributeError:
user_obj = await self.bot.fetch_user(account[0])
user_name = f"{user_obj.name}#{user_obj.discriminator}"
if len(user_name) > 28:
user_name = f"{user_obj.name[:19]}...#{user_obj.discriminator}"
user_idx = pos + 1
if user_obj == ctx.author:
name = f"{user_idx}. <<{user_name}>>"
temp_msg += (
f"{f'{user_idx}.': <{pound_len + 2}} "
f"{humanize_number(account[1]['eaten']) + ' 🍬': <{score_len + 4}} <<{user_name}>>\n"
)
else:
name = f"{user_idx}. {user_name}"
msg += f"{name:33}{account[1]['eaten']}\N{CANDY}\n"
temp_msg += (
f"{f'{user_idx}.': <{pound_len + 2}} "
f"{humanize_number(account[1]['eaten']) + ' 🍬': <{score_len + 4}} {user_name}\n"
)
page_list = []
for page in pagify(msg, delims=["\n"], page_length=1000):
pages = 1
for page in pagify(temp_msg, delims=["\n"], page_length=1000):
embed = discord.Embed(
colour=await ctx.embed_colour(), description=(box(page, lang="md"))
colour=0xF4731C,
description=box(f"\N{CANDY} Global leaderboard \N{CANDY}", lang="prolog")
+ (box(page, lang="md")),
)
embed.set_footer(
text=f"Page {humanize_number(pages)}/{humanize_number(math.ceil(len(temp_msg) / 1500))}"
)
pages += 1
page_list.append(embed)
await message.edit(content=box(f"\N{CANDY} Global Leaderboard \N{CANDY}", lang="prolog"))
await menu(ctx, page_list, DEFAULT_CONTROLS)
return await menu(ctx, page_list, DEFAULT_CONTROLS)
@commands.guild_only()
@commands.command()
@commands.bot_has_permissions(embed_links=True)
async def cinventory(self, ctx):
"""Check your inventory."""
userdata = await self.config.user(ctx.author).all()
msg = f"{ctx.author.mention}'s Candy Bag:\n{userdata['candies']} \N{CANDY}"
sickness = userdata["sickness"]
msg = f"{ctx.author.mention}'s Candy Bag:"
em = discord.Embed(color=await ctx.embed_color())
em.description = f"{userdata['candies']} \N{CANDY}"
if userdata["lollipops"]:
msg += f"\n{userdata['lollipops']} \N{LOLLIPOP}"
em.description += f"\n{userdata['lollipops']} \N{LOLLIPOP}"
if userdata["stars"]:
msg += f"\n{userdata['stars']} \N{WHITE MEDIUM STAR}"
if userdata["sickness"] in range(40, 54):
msg += "\n\n**Sickness is over 40/100**\n*You don't feel so good...*"
if userdata["sickness"] in range(55, 70):
msg += "\n\n**Sickness is over 55/100**\n*You don't feel so good...*"
if userdata["sickness"] in range(71, 84):
msg += "\n\n**Sickness is over 70/100**\n*You really don't feel so good...*"
if userdata["sickness"] in range(85, 100):
msg += "\n\n**Sickness is over 85/100**\n*The thought of more sugar makes you feel awful...*"
if userdata["sickness"] > 100:
msg += "\n\n**Sickness is over 100/100**\n*Better wait a while for more candy...*"
await ctx.send(msg)
em.description += f"\n{userdata['stars']} \N{WHITE MEDIUM STAR}"
if sickness in range(41, 56):
em.description += f"\n\n**Sickness is over 40/100**\n*You don't feel so good...*"
elif sickness in range(56, 71):
em.description += f"\n\n**Sickness is over 55/100**\n*You don't feel so good...*"
elif sickness in range(71, 86):
em.description += (
f"\n\n**Sickness is over 70/100**\n*You really don't feel so good...*"
)
elif sickness in range(86, 101):
em.description += f"\n\n**Sickness is over 85/100**\n*The thought of more sugar makes you feel awful...*"
elif sickness > 100:
em.description += (
f"\n\n**Sickness is over 100/100**\n*Better wait a while for more candy...*"
)
await ctx.send(msg, embed=em)
@commands.guild_only()
@checks.mod_or_permissions(administrator=True)
@commands.command()
async def cooldown(self, ctx, cooldown_time: int = 0):
async def totcooldown(self, ctx, cooldown_time: int = 0):
"""Set the cooldown time for trick or treating on the server."""
cooldown = await self.config.guild(ctx.guild).cooldown()
if not cooldown_time:
if cooldown_time < 0:
return await ctx.send("Nice try.")
if cooldown_time == 0:
await self.config.guild(ctx.guild).cooldown.set(300)
await ctx.send("Trick or treating cooldown time reset to 5m.")
return await ctx.send("Trick or treating cooldown time reset to 5m.")
elif 1 <= cooldown_time <= 30:
await self.config.guild(ctx.guild).cooldown.set(30)
return await ctx.send("Trick or treating cooldown time set to the minimum of 30s.")
else:
await self.config.guild(ctx.guild).cooldown.set(cooldown_time)
await ctx.send(f"Trick or treating cooldown time set to {cooldown_time}s.")
@@ -420,9 +472,10 @@ class TrickOrTreat(BaseCog):
async def totversion(self, ctx):
"""Trick or Treat version."""
await ctx.send(
f"Trick or Treat, version {__version__}\n\n*0.0.5 updates:*\n**Save values before waiting on messages:\nQuick commands will not overwrite other values**\n\n*0.0.4 updates:*\n**+2% star chance on trick or treat (6% total)\n+5% lollipop chance on trick or treat (25% total)\nMore RP messages\nFix for steal mechanic freezing\n**"
f"Trick or Treat, version {__version__}\n\n*0.0.6 updates:*\n**cooldown -> totcooldown\nGeneral cleanup for 2019**\n\n*0.0.5 updates:*\n**Save values before waiting on messages:\nQuick commands will not overwrite other values**\n\n*0.0.4 updates:*\n**+2% star chance on trick or treat (6% total)\n+5% lollipop chance on trick or treat (25% total)\nMore RP messages\nFix for steal mechanic freezing\n**"
)
@commands.Cog.listener()
async def on_message(self, message):
if isinstance(message.channel, discord.abc.PrivateChannel):
return
@@ -440,7 +493,7 @@ class TrickOrTreat(BaseCog):
new_sickness = 0
await self.config.user(message.author).sickness.set(new_sickness)
if "trick or treat" not in content:
if not content.startswith("trick or treat"):
return
toggle = await self.config.guild(message.guild).toggle()
if not toggle:
@@ -468,7 +521,7 @@ class TrickOrTreat(BaseCog):
"The wind starts to pick up as you look for the next house...",
]
return await message.channel.send(random.choice(messages))
await self.config.user(message.author).last_tot.set(str(now))
candy = random.randint(1, 25)
lollipop = random.randint(0, 100)
star = random.randint(0, 100)
@@ -526,4 +579,3 @@ class TrickOrTreat(BaseCog):
win_message += "\n**BONUS**: 1 \N{WHITE MEDIUM STAR}"
await message.channel.send(win_message)
await self.config.user(message.author).last_tot.set(str(now))

View File

@@ -8,5 +8,8 @@
"tags": [
"wolfram"
],
"requirements": [
"pillow"
],
"type": "COG"
}
}

View File

@@ -1,14 +1,16 @@
import aiohttp
import os
from redbot.core import Config, commands, checks
from redbot.core.utils.chat_formatting import box
import asyncio
import discord
from io import BytesIO
from PIL import Image
import xml.etree.ElementTree as ET
import urllib.parse
from redbot.core import Config, commands, checks
from redbot.core.utils.chat_formatting import box, pagify
BaseCog = getattr(commands, "Cog", object)
class Wolfram(BaseCog):
class Wolfram(commands.Cog):
"""Ask Wolfram Alpha any question."""
def __init__(self, bot):
@@ -23,29 +25,102 @@ class Wolfram(BaseCog):
@commands.command(name="wolfram", aliases=["ask"])
async def _wolfram(self, ctx, *question: str):
"""Ask Wolfram Alpha any question."""
api_key = await self.config.WOLFRAM_API_KEY()
if not api_key:
return await ctx.send(
"No API key set for Wolfram Alpha. Get one at http://products.wolframalpha.com/api/"
)
if api_key:
url = "http://api.wolframalpha.com/v2/query?"
query = " ".join(question)
payload = {"input": query, "appid": api_key}
headers = {"user-agent": "Red-cog/2.0.0"}
async with self.session.get(url, params=payload, headers=headers) as r:
result = await r.text()
root = ET.fromstring(result)
a = []
for pt in root.findall(".//plaintext"):
if pt.text:
a.append(pt.text.capitalize())
if len(a) < 1:
message = "There is as yet insufficient data for a meaningful answer."
else:
message = "\n".join(a[0:3])
url = "http://api.wolframalpha.com/v2/query?"
query = " ".join(question)
payload = {"input": query, "appid": api_key}
headers = {"user-agent": "Red-cog/2.0.0"}
async with self.session.get(url, params=payload, headers=headers) as r:
result = await r.text()
root = ET.fromstring(result)
a = []
for pt in root.findall(".//plaintext"):
if pt.text:
a.append(pt.text.capitalize())
if len(a) < 1:
message = "There is as yet insufficient data for a meaningful answer."
else:
message = "No API key set for Wolfram Alpha. Get one at http://products.wolframalpha.com/api/"
message = "\n".join(a[0:3])
await ctx.send(box(message))
@commands.command(name="wolframimage")
async def _image(self, ctx, *arguments: str):
"""Ask Wolfram Alpha any question. Returns an image."""
if not arguments:
return await ctx.send_help()
api_key = await self.config.WOLFRAM_API_KEY()
if not api_key:
return await ctx.send(
"No API key set for Wolfram Alpha. Get one at http://products.wolframalpha.com/api/"
)
width = 800
font_size = 30
layout = "labelbar"
background = "193555"
foreground = "white"
units = "metric"
query = " ".join(arguments)
query = urllib.parse.quote(query)
url = f"http://api.wolframalpha.com/v1/simple?appid={api_key}&i={query}%3F&width={width}&fontsize={font_size}&layout={layout}&background={background}&foreground={foreground}&units={units}"
async with ctx.channel.typing():
async with self.session.request("GET", url) as r:
img = await r.content.read()
if len(img) == 43:
# img = b'Wolfram|Alpha did not understand your input'
return await ctx.send(
"There is as yet insufficient data for a meaningful answer."
)
wolfram_img = BytesIO(img)
try:
await ctx.channel.send(
file=discord.File(wolfram_img, f"wolfram{ctx.author.id}.png")
)
except Exception as e:
await ctx.send(f"Oops, there was a problem: {e}")
@commands.command(name="wolframsolve")
async def _solve(self, ctx, *, query: str):
"""Ask Wolfram Alpha any math question. Returns step by step answers."""
api_key = await self.config.WOLFRAM_API_KEY()
if not api_key:
return await ctx.send(
"No API key set for Wolfram Alpha. Get one at http://products.wolframalpha.com/api/"
)
url = f"http://api.wolframalpha.com/v2/query"
params = {
"appid": api_key,
"input": query,
"podstate": "Step-by-step solution",
"format": "plaintext",
}
msg = ""
async with ctx.channel.typing():
async with self.session.request("GET", url, params=params) as r:
text = await r.content.read()
root = ET.fromstring(text)
for pod in root.findall(".//pod"):
if pod.attrib["title"] == "Number line":
continue
msg += f"{pod.attrib['title']}\n"
for pt in pod.findall(".//plaintext"):
if pt.text:
strip = pt.text.replace(" | ", " ").replace("| ", " ")
msg += f"- {strip}\n\n"
if len(msg) < 1:
msg = "There is as yet insufficient data for a meaningful answer."
for text in pagify(msg):
await ctx.send(box(text))
@checks.is_owner()
@commands.command(name="setwolframapi", aliases=["setwolfram"])
async def _setwolframapi(self, ctx, key: str):
@@ -55,5 +130,5 @@ class Wolfram(BaseCog):
await self.config.WOLFRAM_API_KEY.set(key)
await ctx.send("Key set.")
def __unload(self):
def cog_unload(self):
self.bot.loop.create_task(self.session.close())

5
youtube/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .youtube import YouTube
def setup(bot):
bot.add_cog(YouTube(bot))

12
youtube/info.json Normal file
View File

@@ -0,0 +1,12 @@
{
"author": [
"aikaterna"
],
"description": "Search youtube for videos, originally by Paddo. This version also includes a ytsearch command to look through multiple results.",
"install_msg": "Thanks for installing, have fun.",
"short": "Search youtube for videos.",
"tags": [
"youtube"
],
"type": "COG"
}

47
youtube/youtube.py Normal file
View File

@@ -0,0 +1,47 @@
import aiohttp
import re
from redbot.core import commands
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
class YouTube(commands.Cog):
"""Search YouTube for videos."""
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
async def _youtube_results(self, query: str):
try:
search_url = "https://www.youtube.com/results?"
payload = {"search_query": "".join(query)}
headers = {"user-agent": "Red-cog/3.0"}
async with self.session.get(search_url, params=payload, headers=headers) as r:
result = await r.text()
yt_find = re.findall(r"href=\"\/watch\?v=(.{11})", result)
url_list = []
for track in yt_find:
url = f"https://www.youtube.com/watch?v={track}"
if url not in url_list:
url_list.append(url)
except Exception as e:
url_list = [f"Something went terribly wrong! [{e}]"]
return url_list
@commands.command()
async def youtube(self, ctx, *, query: str):
"""Search on Youtube."""
result = await self._youtube_results(query)
await ctx.send(result[0])
@commands.command()
async def ytsearch(self, ctx, *, query: str):
"""Search on Youtube, multiple results."""
result = await self._youtube_results(query)
await menu(ctx, result, DEFAULT_CONTROLS)
def cog_unload(self):
self.bot.loop.create_task(self.session.close())