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)