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 rolesVery simple cog, mention doesn't persist
| | hangman | **Beta** |
Play a game of hangmanSome visual glitches and needs more customization
| | howdoi | **Incomplete** |
Ask coding questions and get results from StackExchangeNot yet functional
| -| leaver | **Beta** |
Send a message in a channel when a user leaves the serverSeems to be functional, please report any bugs or suggestions
| | infochannel | **Beta** |
Create a channel to display server infoJust released, please report bugs
| +| launchlib | **Beta** |
Access rocket launch dataJust released, please report bugs
| +| leaver | **Beta** |
Send a message in a channel when a user leaves the serverSeems 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 onlineAlpha release, please report bugs
| | nudity | **Alpha** |
Checks for NSFW images posted in non-NSFW channelsSwitched 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)