Repository: Cog-Creators/Red-DiscordBot

Issue:
[V3 Streams] Youtube alerts added by ID have NoneType name
# Command bugs

Red v3.0.0.b18

#### Command name

[p]streamalert youtube UC79_r-MK8-bmJCYmyzAkvPw

#### What cog is this command from?

Streams 

#### What were you expecting to happen?

Add non-partner youtube channel ("Cade") to streamalert settings.
Add <id: channel-name>"Cade"</channel-name> name to streams settings in lieu of a Partner-Name.

#### What actually happened?
**Streams cog is completely disabled**

If the Youtube Channel ID is used <UC....xzy> instead of a Partner channel name AND if the youtube channel is *not* a Youtube Partner, then Streams sets the channel name as 'null'.

```
Exception during loading of cog
Traceback (most recent call last):
  File "C:\Users\jason\AppData\Local\Programs\Python\Python36\lib\site-packages\redbot\core\core_commands.py", line 87, in _load
    await bot.load_extension(spec)
  File "C:\Users\jason\AppData\Local\Programs\Python\Python36\lib\site-packages\redbot\core\bot.py", line 222, in load_extension
    await lib.setup(self)
  File "C:\Users\jason\AppData\Local\Programs\Python\Python36\lib\site-packages\redbot\cogs\streams\__init__.py", line 6, in setup
    await cog.initialize()
  File "C:\Users\jason\AppData\Local\Programs\Python\Python36\lib\site-packages\redbot\cogs\streams\streams.py", line 71, in initialize
    self.streams = await self.load_streams()
  File "C:\Users\jason\AppData\Local\Programs\Python\Python36\lib\site-packages\redbot\cogs\streams\streams.py", line 618, in load_streams
    return [x for x in streams if not (x.name.lower() in seen or seen_add(x.name.lower()))]
  File "C:\Users\jason\AppData\Local\Programs\Python\Python36\lib\site-packages\redbot\cogs\streams\streams.py", line 618, in <listcomp>
    return [x for x in streams if not (x.name.lower() in seen or seen_add(x.name.lower()))]
AttributeError: 'NoneType' object has no attribute 'lower'
```

#### How can we reproduce this issue?

[p]streamalert youtube UC79_r-MK8-bmJCYmyzAkvPw
[p]reload streams
[error]


Discussion:
Can repro on both b18 and V3/dev This makes the cog completely inoperable.

This should be a reasonably easy fix on preventing the issue on load by making this NoneType safe.

More work will need to be done to figure out how/why we are storing a NoneType here to begin with though.
Surely the traceback is different on V3/develop though? Since #1994 has been merged
If this is an easy fix we'll try to fit it in beta 19, I'm willing to accept a PR for it after our cut-off at 23:59 EST Friday
@Tobotimus Not related to the token. This has to do with a failure to get stream info in another case. The "easy" fix is a NoneType filter, but that doesn't fix the root problem, and will cause stream alerts for those channels to fail silently. This is still better than breaking all stream alerts to accomplish this.
This'll no longer be fatal to the Streams cog in beta 19 after the changes in #1994. However, the name for a youtube stream added by ID is still being set to `None`. This issue will remain open until that is fixed.

Resolving patch:
diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py
--- a/redbot/cogs/streams/streams.py
+++ b/redbot/cogs/streams/streams.py
@@ -472,7 +472,7 @@ def get_stream(self, _class, name):
                     return stream
                 elif not self.check_name_or_id(name) and stream.id == name:
                     return stream
-            if stream.type == _class.__name__ and stream.name.lower() == name.lower():
+            elif stream.type == _class.__name__ and stream.name.lower() == name.lower():
                 return stream
 
     def get_community(self, _class, name):
diff --git a/redbot/cogs/streams/streamtypes.py b/redbot/cogs/streams/streamtypes.py
--- a/redbot/cogs/streams/streamtypes.py
+++ b/redbot/cogs/streams/streamtypes.py
@@ -152,8 +152,14 @@ def __init__(self, **kwargs):
         super().__init__(**kwargs)
 
     async def is_online(self):
+        if not self._token:
+            raise InvalidYoutubeCredentials("YouTube API key is not set.")
+
         if not self.id:
             self.id = await self.fetch_id()
+        elif not self.name:
+            self.name = await self.fetch_name()
+
         url = YOUTUBE_SEARCH_ENDPOINT
         params = {
             "key": self._token,
@@ -188,7 +194,20 @@ def make_embed(self, data):
         return embed
 
     async def fetch_id(self):
-        params = {"key": self._token, "forUsername": self.name, "part": "id"}
+        return await self._fetch_channel_resource("id")
+
+    async def fetch_name(self):
+        snippet = await self._fetch_channel_resource("snippet")
+        return snippet["title"]
+
+    async def _fetch_channel_resource(self, resource: str):
+
+        params = {"key": self._token, "part": resource}
+        if resource == "id":
+            params["forUsername"] = self.name
+        else:
+            params["id"] = self.id
+
         async with aiohttp.ClientSession() as session:
             async with session.get(YOUTUBE_CHANNELS_ENDPOINT, params=params) as r:
                 data = await r.json()
@@ -202,7 +221,7 @@ async def fetch_id(self):
         elif "items" in data and len(data["items"]) == 0:
             raise StreamNotFound()
         elif "items" in data:
-            return data["items"][0]["id"]
+            return data["items"][0][resource]
         raise APIError()
 
     def __repr__(self):


Test patch: