diff --git a/README.md b/README.md
index c37f84f..d8399cc 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,9 @@ Cog Function
| forcemention | **Alpha** | Mentions unmentionable roles
Very simple cog, mention doesn't persist |
| hangman | **Beta** | Play a game of hangman
Some visual glitches and needs more customization |
| howdoi | **Incomplete** | Ask coding questions and get results from StackExchange
Not yet functional |
-| leaver | **Beta** | Send a message in a channel when a user leaves the server
Seems to be functional, please report any bugs or suggestions |
| infochannel | **Beta** | Create a channel to display server info
Just released, please report bugs |
+| launchlib | **Beta** | Access rocket launch data
Just released, please report bugs |
+| leaver | **Beta** | Send a message in a channel when a user leaves the server
Seems to be functional, please report any bugs or suggestions |
| lovecalculator | **Alpha** | Calculate the love between two users
[Snap-Ons] Just updated to V3 |
| lseen | **Alpha** | Track when a member was last online
Alpha release, please report bugs |
| nudity | **Alpha** | Checks for NSFW images posted in non-NSFW channels
Switched libraries, now functional |
diff --git a/chatter/chat.py b/chatter/chat.py
index 54f3b2d..a464e40 100644
--- a/chatter/chat.py
+++ b/chatter/chat.py
@@ -3,7 +3,6 @@ import logging
import os
import pathlib
from datetime import datetime, timedelta
-from typing import Literal
import discord
from chatterbot import ChatBot
@@ -14,9 +13,8 @@ from redbot.core import Config, commands
from redbot.core.commands import Cog
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.predicates import MessagePredicate
-from redbot.core.utils import AsyncIter
-log = logging.getLogger("red.fox_v3.chat")
+log = logging.getLogger("red.fox_v3.chatter")
class ENG_LG:
@@ -420,21 +418,18 @@ class Chatter(Cog):
for the message filtering
"""
###########
- is_private = isinstance(message.channel, discord.abc.PrivateChannel)
- # user_allowed check, will be replaced with self.bot.user_allowed or
- # something similar once it's added
- user_allowed = True
-
- if len(message.content) < 2 or is_private or not user_allowed or message.author.bot:
+ if len(message.content) < 2 or message.author.bot:
return
- if await self.bot.cog_disabled_in_guild(self, message.guild):
+ guild: discord.Guild = getattr(message, "guild", None)
+
+ if await self.bot.cog_disabled_in_guild(self, guild):
return
ctx: commands.Context = await self.bot.get_context(message)
- if ctx.prefix is not None:
+ if ctx.prefix is not None: # Probably unnecessary, we're in on_message_without_command
return
###########
@@ -454,23 +449,8 @@ class Chatter(Cog):
# print("not mentioned")
return
- author = message.author
- guild: discord.Guild = message.guild
-
channel: discord.TextChannel = message.channel
- # if author.id != self.bot.user.id:
- # if guild is None:
- # to_strip = "@" + channel.me.display_name + " "
- # else:
- # to_strip = "@" + guild.me.display_name + " "
- # text = message.clean_content
- # if not text.startswith(to_strip):
- # return
- # text = text.replace(to_strip, "", 1)
-
- # A bit more aggressive, could remove two mentions
- # Or might not work at all, since mentionables are pre-cleaned_content
message.content = message.content.replace(prefix, "", 1)
text = message.clean_content
diff --git a/exclusiverole/exclusiverole.py b/exclusiverole/exclusiverole.py
index ce94a7d..19635b2 100644
--- a/exclusiverole/exclusiverole.py
+++ b/exclusiverole/exclusiverole.py
@@ -89,7 +89,7 @@ class ExclusiveRole(Cog):
to_remove = (member_set - role_set) - {member.guild.default_role.id}
if to_remove and member_set & role_set:
- to_remove = [discord.utils.get(member.guild.roles, id=id) for id in to_remove]
+ to_remove = [discord.utils.get(member.guild.roles, id=r_id) for r_id in to_remove]
await member.remove_roles(*to_remove, reason="Exclusive roles")
@commands.Cog.listener()
diff --git a/launchlib/__init__.py b/launchlib/__init__.py
new file mode 100644
index 0000000..8e45f6e
--- /dev/null
+++ b/launchlib/__init__.py
@@ -0,0 +1,5 @@
+from .launchlib import LaunchLib
+
+
+def setup(bot):
+ bot.add_cog(LaunchLib(bot))
diff --git a/launchlib/countrymapper.py b/launchlib/countrymapper.py
new file mode 100644
index 0000000..694fda5
--- /dev/null
+++ b/launchlib/countrymapper.py
@@ -0,0 +1,254 @@
+countryISOMapping = {
+ "AFG": "AF",
+ "ALA": "AX",
+ "ALB": "AL",
+ "DZA": "DZ",
+ "ASM": "AS",
+ "AND": "AD",
+ "AGO": "AO",
+ "AIA": "AI",
+ "ATA": "AQ",
+ "ATG": "AG",
+ "ARG": "AR",
+ "ARM": "AM",
+ "ABW": "AW",
+ "AUS": "AU",
+ "AUT": "AT",
+ "AZE": "AZ",
+ "BHS": "BS",
+ "BHR": "BH",
+ "BGD": "BD",
+ "BRB": "BB",
+ "BLR": "BY",
+ "BEL": "BE",
+ "BLZ": "BZ",
+ "BEN": "BJ",
+ "BMU": "BM",
+ "BTN": "BT",
+ "BOL": "BO",
+ "BIH": "BA",
+ "BWA": "BW",
+ "BVT": "BV",
+ "BRA": "BR",
+ "VGB": "VG",
+ "IOT": "IO",
+ "BRN": "BN",
+ "BGR": "BG",
+ "BFA": "BF",
+ "BDI": "BI",
+ "KHM": "KH",
+ "CMR": "CM",
+ "CAN": "CA",
+ "CPV": "CV",
+ "CYM": "KY",
+ "CAF": "CF",
+ "TCD": "TD",
+ "CHL": "CL",
+ "CHN": "CN",
+ "HKG": "HK",
+ "MAC": "MO",
+ "CXR": "CX",
+ "CCK": "CC",
+ "COL": "CO",
+ "COM": "KM",
+ "COG": "CG",
+ "COD": "CD",
+ "COK": "CK",
+ "CRI": "CR",
+ "CIV": "CI",
+ "HRV": "HR",
+ "CUB": "CU",
+ "CYP": "CY",
+ "CZE": "CZ",
+ "DNK": "DK",
+ "DJI": "DJ",
+ "DMA": "DM",
+ "DOM": "DO",
+ "ECU": "EC",
+ "EGY": "EG",
+ "SLV": "SV",
+ "GNQ": "GQ",
+ "ERI": "ER",
+ "EST": "EE",
+ "ETH": "ET",
+ "FLK": "FK",
+ "FRO": "FO",
+ "FJI": "FJ",
+ "FIN": "FI",
+ "FRA": "FR",
+ "GUF": "GF",
+ "PYF": "PF",
+ "ATF": "TF",
+ "GAB": "GA",
+ "GMB": "GM",
+ "GEO": "GE",
+ "DEU": "DE",
+ "GHA": "GH",
+ "GIB": "GI",
+ "GRC": "GR",
+ "GRL": "GL",
+ "GRD": "GD",
+ "GLP": "GP",
+ "GUM": "GU",
+ "GTM": "GT",
+ "GGY": "GG",
+ "GIN": "GN",
+ "GNB": "GW",
+ "GUY": "GY",
+ "HTI": "HT",
+ "HMD": "HM",
+ "VAT": "VA",
+ "HND": "HN",
+ "HUN": "HU",
+ "ISL": "IS",
+ "IND": "IN",
+ "IDN": "ID",
+ "IRN": "IR",
+ "IRQ": "IQ",
+ "IRL": "IE",
+ "IMN": "IM",
+ "ISR": "IL",
+ "ITA": "IT",
+ "JAM": "JM",
+ "JPN": "JP",
+ "JEY": "JE",
+ "JOR": "JO",
+ "KAZ": "KZ",
+ "KEN": "KE",
+ "KIR": "KI",
+ "PRK": "KP",
+ "KOR": "KR",
+ "KWT": "KW",
+ "KGZ": "KG",
+ "LAO": "LA",
+ "LVA": "LV",
+ "LBN": "LB",
+ "LSO": "LS",
+ "LBR": "LR",
+ "LBY": "LY",
+ "LIE": "LI",
+ "LTU": "LT",
+ "LUX": "LU",
+ "MKD": "MK",
+ "MDG": "MG",
+ "MWI": "MW",
+ "MYS": "MY",
+ "MDV": "MV",
+ "MLI": "ML",
+ "MLT": "MT",
+ "MHL": "MH",
+ "MTQ": "MQ",
+ "MRT": "MR",
+ "MUS": "MU",
+ "MYT": "YT",
+ "MEX": "MX",
+ "FSM": "FM",
+ "MDA": "MD",
+ "MCO": "MC",
+ "MNG": "MN",
+ "MNE": "ME",
+ "MSR": "MS",
+ "MAR": "MA",
+ "MOZ": "MZ",
+ "MMR": "MM",
+ "NAM": "NA",
+ "NRU": "NR",
+ "NPL": "NP",
+ "NLD": "NL",
+ "ANT": "AN",
+ "NCL": "NC",
+ "NZL": "NZ",
+ "NIC": "NI",
+ "NER": "NE",
+ "NGA": "NG",
+ "NIU": "NU",
+ "NFK": "NF",
+ "MNP": "MP",
+ "NOR": "NO",
+ "OMN": "OM",
+ "PAK": "PK",
+ "PLW": "PW",
+ "PSE": "PS",
+ "PAN": "PA",
+ "PNG": "PG",
+ "PRY": "PY",
+ "PER": "PE",
+ "PHL": "PH",
+ "PCN": "PN",
+ "POL": "PL",
+ "PRT": "PT",
+ "PRI": "PR",
+ "QAT": "QA",
+ "REU": "RE",
+ "ROU": "RO",
+ "RUS": "RU",
+ "RWA": "RW",
+ "BLM": "BL",
+ "SHN": "SH",
+ "KNA": "KN",
+ "LCA": "LC",
+ "MAF": "MF",
+ "SPM": "PM",
+ "VCT": "VC",
+ "WSM": "WS",
+ "SMR": "SM",
+ "STP": "ST",
+ "SAU": "SA",
+ "SEN": "SN",
+ "SRB": "RS",
+ "SYC": "SC",
+ "SLE": "SL",
+ "SGP": "SG",
+ "SVK": "SK",
+ "SVN": "SI",
+ "SLB": "SB",
+ "SOM": "SO",
+ "ZAF": "ZA",
+ "SGS": "GS",
+ "SSD": "SS",
+ "ESP": "ES",
+ "LKA": "LK",
+ "SDN": "SD",
+ "SUR": "SR",
+ "SJM": "SJ",
+ "SWZ": "SZ",
+ "SWE": "SE",
+ "CHE": "CH",
+ "SYR": "SY",
+ "TWN": "TW",
+ "TJK": "TJ",
+ "TZA": "TZ",
+ "THA": "TH",
+ "TLS": "TL",
+ "TGO": "TG",
+ "TKL": "TK",
+ "TON": "TO",
+ "TTO": "TT",
+ "TUN": "TN",
+ "TUR": "TR",
+ "TKM": "TM",
+ "TCA": "TC",
+ "TUV": "TV",
+ "UGA": "UG",
+ "UKR": "UA",
+ "ARE": "AE",
+ "GBR": "GB",
+ "USA": "US",
+ "UMI": "UM",
+ "URY": "UY",
+ "UZB": "UZ",
+ "VUT": "VU",
+ "VEN": "VE",
+ "VNM": "VN",
+ "VIR": "VI",
+ "WLF": "WF",
+ "ESH": "EH",
+ "YEM": "YE",
+ "ZMB": "ZM",
+ "ZWE": "ZW",
+ "XKX": "XK",
+}
+
+
+def country_mapping(countrycode):
+ return countryISOMapping[countrycode]
diff --git a/launchlib/info.json b/launchlib/info.json
new file mode 100644
index 0000000..8df1059
--- /dev/null
+++ b/launchlib/info.json
@@ -0,0 +1,20 @@
+{
+ "author": [
+ "Bobloy"
+ ],
+ "min_bot_version": "3.4.0",
+ "description": "Pull information from the Launch Library API for space flights",
+ "hidden": false,
+ "install_msg": "Thank you for installing LaunchLib. Get started with `[p]load launchlib`, then `[p]help LaunchLib`",
+ "short": "Access launch data for space flights",
+ "end_user_data_statement": "This cog does not store any End User Data",
+ "requirements": ["python-launch-library"],
+ "tags": [
+ "bobloy",
+ "utils",
+ "launch",
+ "space",
+ "api",
+ "library"
+ ]
+}
\ No newline at end of file
diff --git a/launchlib/launchlib.py b/launchlib/launchlib.py
new file mode 100644
index 0000000..0c6eeef
--- /dev/null
+++ b/launchlib/launchlib.py
@@ -0,0 +1,130 @@
+import asyncio
+import functools
+import logging
+
+import discord
+import launchlibrary as ll
+from redbot.core import Config, commands
+from redbot.core.bot import Red
+
+from launchlib.countrymapper import country_mapping
+
+log = logging.getLogger("red.fox_v3.launchlib")
+
+
+class LaunchLib(commands.Cog):
+ """
+ Cog Description
+
+ Less important information about the cog
+ """
+
+ def __init__(self, bot: Red):
+ super().__init__()
+ self.bot = bot
+ self.config = Config.get_conf(self, identifier=0, force_registration=True)
+
+ default_guild = {}
+
+ self.config.register_guild(**default_guild)
+
+ self.api = ll.Api()
+
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
+ async def _embed_launch_data(self, launch: ll.Launch):
+ status: ll.LaunchStatus = launch.get_status()
+
+ rocket: ll.Rocket = launch.rocket
+
+ title = launch.name
+ description = status.description
+
+ urls = launch.vid_urls + launch.info_urls
+ if not urls and rocket:
+ urls = rocket.info_urls + [rocket.wiki_url]
+ if urls:
+ url = urls[0]
+ else:
+ url = None
+
+ color = discord.Color.green() if status.id in [1, 3] else discord.Color.red()
+
+ em = discord.Embed(title=title, description=description, url=url, color=color)
+
+ if rocket and rocket.image_url and rocket.image_url != "Array":
+ em.set_image(url=rocket.image_url)
+
+ agency = getattr(launch, "agency", None)
+ if agency is not None:
+ em.set_author(
+ name=agency.name,
+ url=agency.wiki_url,
+ icon_url=f"https://www.countryflags.io/{country_mapping(agency.country_code)}/flat/64.png",
+ )
+
+ field_options = [
+ ("failreason", "Fail Reason"),
+ ("holdreason", "Hold Reason"),
+ ("id", "ID"),
+ ("hashtag", "Hashtag"),
+ ]
+ for f in field_options:
+ data = getattr(launch, f[0], None)
+ if data is not None and data:
+ em.add_field(name=f[1], value=data)
+
+ if launch.missions:
+ field_options = [
+ ("description", "Mission Description"),
+ ("typeName", "Mission Type"),
+ ("name", "Mission Name"),
+ ]
+ for mission in launch.missions:
+ for f in field_options:
+ data = mission.get(f[0], None)
+ if data is not None and data:
+ em.add_field(name=f[1], value=data)
+
+ if rocket and rocket.family:
+ em.add_field(name="Rocket Family", value=rocket.family)
+
+ em.timestamp = launch.windowstart
+
+ em.set_footer()
+
+ return em
+
+ @commands.group()
+ async def launchlib(self, ctx: commands.Context):
+ if ctx.invoked_subcommand is None:
+ pass
+
+ @launchlib.command()
+ async def next(self, ctx: commands.Context, num_launches: int = 1):
+ # launches = await api.async_next_launches(num_launches)
+ loop = asyncio.get_running_loop()
+
+ launches = await loop.run_in_executor(
+ None, functools.partial(self.api.fetch_launch, num=num_launches)
+ )
+
+ # launches = self.api.fetch_launch(num=num_launches)
+
+ print(len(launches))
+
+ async with ctx.typing():
+ for x, launch in enumerate(launches):
+ if x >= num_launches:
+ return
+
+ em = await self._embed_launch_data(launch)
+ if em is not None:
+ try:
+ await ctx.send(embed=em)
+ except discord.HTTPException:
+ await ctx.send(str(launch))
+ log.exception("Failed to send embed")
+ await asyncio.sleep(2)
diff --git a/timerole/timerole.py b/timerole/timerole.py
index ff93e39..a103bb3 100644
--- a/timerole/timerole.py
+++ b/timerole/timerole.py
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
import discord
from redbot.core import Config, checks, commands
from redbot.core.bot import Red
-from redbot.core.commands import Cog
+from redbot.core.commands import Cog, parse_timedelta
from redbot.core.utils.chat_formatting import pagify
@@ -20,7 +20,7 @@ class Timerole(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
- self.updating = self.bot.loop.create_task(self.check_day())
+ self.updating = asyncio.create_task(self.check_day())
async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete"""
@@ -40,7 +40,7 @@ class Timerole(Cog):
"""
await self.timerole_update()
- await ctx.send("Success")
+ await ctx.tick()
@commands.group()
@checks.mod_or_permissions(administrator=True)
@@ -52,23 +52,34 @@ class Timerole(Cog):
@timerole.command()
async def addrole(
- self, ctx: commands.Context, role: discord.Role, days: int, *requiredroles: discord.Role
+ self, ctx: commands.Context, role: discord.Role, time: str, *requiredroles: discord.Role
):
"""Add a role to be added after specified time on server"""
guild = ctx.guild
- to_set = {"days": days, "remove": False}
+ try:
+ parsed_time = parse_timedelta(time, allowed_units=["weeks", "days", "hours"])
+ except commands.BadArgument:
+ await ctx.maybe_send_embed("Error: Invalid time string.")
+ return
+
+ days = parsed_time.days
+ hours = parsed_time.seconds // 60 // 60
+
+ to_set = {"days": days, "hours": hours, "remove": False}
if requiredroles:
to_set["required"] = [r.id for r in requiredroles]
await self.config.guild(guild).roles.set_raw(role.id, value=to_set)
await ctx.maybe_send_embed(
- "Time Role for {0} set to {1} days until added".format(role.name, days)
+ "Time Role for {0} set to {1} days and {2} hours until added".format(
+ role.name, days, hours
+ )
)
@timerole.command()
async def removerole(
- self, ctx: commands.Context, role: discord.Role, days: int, *requiredroles: discord.Role
+ self, ctx: commands.Context, role: discord.Role, time: str, *requiredroles: discord.Role
):
"""
Add a role to be removed after specified time on server
@@ -76,14 +87,24 @@ class Timerole(Cog):
Useful with an autorole cog
"""
guild = ctx.guild
+ try:
+ parsed_time = parse_timedelta(time, allowed_units=["weeks", "days", "hours"])
+ except commands.BadArgument:
+ await ctx.maybe_send_embed("Error: Invalid time string.")
+ return
- to_set = {"days": days, "remove": True}
+ days = parsed_time.days
+ hours = parsed_time.seconds // 60 // 60
+
+ to_set = {"days": days, "hours": hours, "remove": True}
if requiredroles:
to_set["required"] = [r.id for r in requiredroles]
await self.config.guild(guild).roles.set_raw(role.id, value=to_set)
await ctx.maybe_send_embed(
- "Time Role for {0} set to {1} days until removed".format(role.name, days)
+ "Time Role for {0} set to {1} days and {2} hours until removed".format(
+ role.name, days, hours
+ )
)
@timerole.command()
@@ -174,7 +195,7 @@ class Timerole(Cog):
await member.add_roles(role, reason="Timerole")
else:
await member.remove_roles(role, reason="Timerole")
- except discord.Forbidden:
+ except (discord.Forbidden, discord.NotFound) as e:
results += "{} : {} **(Failed)**\n".format(member.display_name, role.name)
else:
results += "{} : {}\n".format(member.display_name, role.name)
@@ -192,7 +213,11 @@ class Timerole(Cog):
continue
if (
- member.joined_at + timedelta(days=role_dict[str(role_id)]["days"])
+ member.joined_at
+ + timedelta(
+ days=role_dict[str(role_id)]["days"],
+ hours=role_dict[str(role_id)].get("hours", 0),
+ )
<= datetime.today()
):
# Qualifies
@@ -201,4 +226,4 @@ class Timerole(Cog):
async def check_day(self):
while self is self.bot.get_cog("Timerole"):
await self.timerole_update()
- await asyncio.sleep(86400)
+ await asyncio.sleep(3600)