KiTTY, un bot Discord qui est un petit chat :) Il est basé sur une ancienne version du bot Red, sous Python 3.6 et qui a des fonctionnalités bien sympatiques !
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

693 lines
25 KiB

  1. from discord.ext import commands
  2. from .utils.dataIO import dataIO
  3. from .utils.chat_formatting import escape_mass_mentions
  4. from .utils import checks
  5. from collections import defaultdict
  6. from string import ascii_letters
  7. from random import choice
  8. import discord
  9. import os
  10. import re
  11. import aiohttp
  12. import asyncio
  13. import logging
  14. import json
  15. class StreamsError(Exception):
  16. pass
  17. class StreamNotFound(StreamsError):
  18. pass
  19. class APIError(StreamsError):
  20. pass
  21. class InvalidCredentials(StreamsError):
  22. pass
  23. class OfflineStream(StreamsError):
  24. pass
  25. class Streams:
  26. """Streams
  27. Alerts for a variety of streaming services"""
  28. def __init__(self, bot):
  29. self.bot = bot
  30. self.twitch_streams = dataIO.load_json("data/streams/twitch.json")
  31. self.hitbox_streams = dataIO.load_json("data/streams/hitbox.json")
  32. self.mixer_streams = dataIO.load_json("data/streams/beam.json")
  33. self.picarto_streams = dataIO.load_json("data/streams/picarto.json")
  34. settings = dataIO.load_json("data/streams/settings.json")
  35. self.settings = defaultdict(dict, settings)
  36. self.messages_cache = defaultdict(list)
  37. @commands.command()
  38. async def hitbox(self, stream: str):
  39. """Checks if hitbox stream is online"""
  40. stream = escape_mass_mentions(stream)
  41. regex = r'^(https?\:\/\/)?(www\.)?(hitbox\.tv\/)'
  42. stream = re.sub(regex, '', stream)
  43. try:
  44. embed = await self.hitbox_online(stream)
  45. except OfflineStream:
  46. await self.bot.say(stream + " is offline.")
  47. except StreamNotFound:
  48. await self.bot.say("That stream doesn't exist.")
  49. except APIError:
  50. await self.bot.say("Error contacting the API.")
  51. else:
  52. await self.bot.say(embed=embed)
  53. @commands.command(pass_context=True)
  54. async def twitch(self, ctx, stream: str):
  55. """Checks if twitch stream is online"""
  56. stream = escape_mass_mentions(stream)
  57. regex = r'^(https?\:\/\/)?(www\.)?(twitch\.tv\/)'
  58. stream = re.sub(regex, '', stream)
  59. try:
  60. data = await self.fetch_twitch_ids(stream, raise_if_none=True)
  61. embed = await self.twitch_online(data[0]["_id"])
  62. except OfflineStream:
  63. await self.bot.say(stream + " is offline.")
  64. except StreamNotFound:
  65. await self.bot.say("That stream doesn't exist.")
  66. except APIError:
  67. await self.bot.say("Error contacting the API.")
  68. except InvalidCredentials:
  69. await self.bot.say("Owner: Client-ID is invalid or not set. "
  70. "See `{}streamset twitchtoken`"
  71. "".format(ctx.prefix))
  72. else:
  73. await self.bot.say(embed=embed)
  74. @commands.command()
  75. async def mixer(self, stream: str):
  76. """Checks if mixer stream is online"""
  77. stream = escape_mass_mentions(stream)
  78. regex = r'^(https?\:\/\/)?(www\.)?(mixer\.com\/)'
  79. stream = re.sub(regex, '', stream)
  80. try:
  81. embed = await self.mixer_online(stream)
  82. except OfflineStream:
  83. await self.bot.say(stream + " is offline.")
  84. except StreamNotFound:
  85. await self.bot.say("That stream doesn't exist.")
  86. except APIError:
  87. await self.bot.say("Error contacting the API.")
  88. else:
  89. await self.bot.say(embed=embed)
  90. @commands.command()
  91. async def picarto(self, stream: str):
  92. """Checks if picarto stream is online"""
  93. stream = escape_mass_mentions(stream)
  94. regex = r'^(https?\:\/\/)?(www\.)?(picarto\.tv\/)'
  95. stream = re.sub(regex, '', stream)
  96. try:
  97. embed = await self.picarto_online(stream)
  98. except OfflineStream:
  99. await self.bot.say(stream + " is offline.")
  100. except StreamNotFound:
  101. await self.bot.say("That stream doesn't exist.")
  102. except APIError:
  103. await self.bot.say("Error contacting the API.")
  104. else:
  105. await self.bot.say(embed=embed)
  106. @commands.group(pass_context=True, no_pm=True)
  107. @checks.mod_or_permissions(manage_server=True)
  108. async def streamalert(self, ctx):
  109. """Adds/removes stream alerts from the current channel"""
  110. if ctx.invoked_subcommand is None:
  111. await self.bot.send_cmd_help(ctx)
  112. @streamalert.command(name="twitch", pass_context=True)
  113. async def twitch_alert(self, ctx, stream: str):
  114. """Adds/removes twitch alerts from the current channel"""
  115. stream = escape_mass_mentions(stream)
  116. regex = r'^(https?\:\/\/)?(www\.)?(twitch\.tv\/)'
  117. stream = re.sub(regex, '', stream)
  118. channel = ctx.message.channel
  119. try:
  120. data = await self.fetch_twitch_ids(stream, raise_if_none=True)
  121. except StreamNotFound:
  122. await self.bot.say("That stream doesn't exist.")
  123. return
  124. except APIError:
  125. await self.bot.say("Error contacting the API.")
  126. return
  127. except InvalidCredentials:
  128. await self.bot.say("Owner: Client-ID is invalid or not set. "
  129. "See `{}streamset twitchtoken`"
  130. "".format(ctx.prefix))
  131. return
  132. enabled = self.enable_or_disable_if_active(self.twitch_streams,
  133. stream,
  134. channel,
  135. _id=data[0]["_id"])
  136. if enabled:
  137. await self.bot.say("Alert activated. I will notify this channel "
  138. "when {} is live.".format(stream))
  139. else:
  140. await self.bot.say("Alert has been removed from this channel.")
  141. dataIO.save_json("data/streams/twitch.json", self.twitch_streams)
  142. @streamalert.command(name="hitbox", pass_context=True)
  143. async def hitbox_alert(self, ctx, stream: str):
  144. """Adds/removes hitbox alerts from the current channel"""
  145. stream = escape_mass_mentions(stream)
  146. regex = r'^(https?\:\/\/)?(www\.)?(hitbox\.tv\/)'
  147. stream = re.sub(regex, '', stream)
  148. channel = ctx.message.channel
  149. try:
  150. await self.hitbox_online(stream)
  151. except StreamNotFound:
  152. await self.bot.say("That stream doesn't exist.")
  153. return
  154. except APIError:
  155. await self.bot.say("Error contacting the API.")
  156. return
  157. except OfflineStream:
  158. pass
  159. enabled = self.enable_or_disable_if_active(self.hitbox_streams,
  160. stream,
  161. channel)
  162. if enabled:
  163. await self.bot.say("Alert activated. I will notify this channel "
  164. "when {} is live.".format(stream))
  165. else:
  166. await self.bot.say("Alert has been removed from this channel.")
  167. dataIO.save_json("data/streams/hitbox.json", self.hitbox_streams)
  168. @streamalert.command(name="mixer", pass_context=True)
  169. async def mixer_alert(self, ctx, stream: str):
  170. """Adds/removes mixer alerts from the current channel"""
  171. stream = escape_mass_mentions(stream)
  172. regex = r'^(https?\:\/\/)?(www\.)?(mixer\.com\/)'
  173. stream = re.sub(regex, '', stream)
  174. channel = ctx.message.channel
  175. try:
  176. await self.mixer_online(stream)
  177. except StreamNotFound:
  178. await self.bot.say("That stream doesn't exist.")
  179. return
  180. except APIError:
  181. await self.bot.say("Error contacting the API.")
  182. return
  183. except OfflineStream:
  184. pass
  185. enabled = self.enable_or_disable_if_active(self.mixer_streams,
  186. stream,
  187. channel)
  188. if enabled:
  189. await self.bot.say("Alert activated. I will notify this channel "
  190. "when {} is live.".format(stream))
  191. else:
  192. await self.bot.say("Alert has been removed from this channel.")
  193. dataIO.save_json("data/streams/beam.json", self.mixer_streams)
  194. @streamalert.command(name="picarto", pass_context=True)
  195. async def picarto_alert(self, ctx, stream: str):
  196. """Adds/removes picarto alerts from the current channel"""
  197. stream = escape_mass_mentions(stream)
  198. regex = r'^(https?\:\/\/)?(www\.)?(picarto\.tv\/)'
  199. stream = re.sub(regex, '', stream)
  200. channel = ctx.message.channel
  201. try:
  202. await self.picarto_online(stream)
  203. except StreamNotFound:
  204. await self.bot.say("That stream doesn't exist.")
  205. return
  206. except APIError:
  207. await self.bot.say("Error contacting the API.")
  208. return
  209. except OfflineStream:
  210. pass
  211. enabled = self.enable_or_disable_if_active(self.picarto_streams,
  212. stream,
  213. channel)
  214. if enabled:
  215. await self.bot.say("Alert activated. I will notify this channel "
  216. "when {} is live.".format(stream))
  217. else:
  218. await self.bot.say("Alert has been removed from this channel.")
  219. dataIO.save_json("data/streams/picarto.json", self.picarto_streams)
  220. @streamalert.command(name="stop", pass_context=True)
  221. async def stop_alert(self, ctx):
  222. """Stops all streams alerts in the current channel"""
  223. channel = ctx.message.channel
  224. streams = (
  225. self.hitbox_streams,
  226. self.twitch_streams,
  227. self.mixer_streams,
  228. self.picarto_streams
  229. )
  230. for stream_type in streams:
  231. to_delete = []
  232. for s in stream_type:
  233. if channel.id in s["CHANNELS"]:
  234. s["CHANNELS"].remove(channel.id)
  235. if not s["CHANNELS"]:
  236. to_delete.append(s)
  237. for s in to_delete:
  238. stream_type.remove(s)
  239. dataIO.save_json("data/streams/twitch.json", self.twitch_streams)
  240. dataIO.save_json("data/streams/hitbox.json", self.hitbox_streams)
  241. dataIO.save_json("data/streams/beam.json", self.mixer_streams)
  242. dataIO.save_json("data/streams/picarto.json", self.picarto_streams)
  243. await self.bot.say("There will be no more stream alerts in this "
  244. "channel.")
  245. @commands.group(pass_context=True)
  246. async def streamset(self, ctx):
  247. """Stream settings"""
  248. if ctx.invoked_subcommand is None:
  249. await self.bot.send_cmd_help(ctx)
  250. @streamset.command()
  251. @checks.is_owner()
  252. async def twitchtoken(self, token : str):
  253. """Sets the Client-ID for Twitch
  254. https://blog.twitch.tv/client-id-required-for-kraken-api-calls-afbb8e95f843"""
  255. self.settings["TWITCH_TOKEN"] = token
  256. dataIO.save_json("data/streams/settings.json", self.settings)
  257. await self.bot.say('Twitch Client-ID set.')
  258. @streamset.command(pass_context=True, no_pm=True)
  259. @checks.admin()
  260. async def mention(self, ctx, *, mention_type : str):
  261. """Sets mentions for stream alerts
  262. Types: everyone, here, none"""
  263. server = ctx.message.server
  264. mention_type = mention_type.lower()
  265. if mention_type in ("everyone", "here"):
  266. self.settings[server.id]["MENTION"] = "@" + mention_type
  267. await self.bot.say("When a stream is online @\u200b{} will be "
  268. "mentioned.".format(mention_type))
  269. elif mention_type == "none":
  270. self.settings[server.id]["MENTION"] = ""
  271. await self.bot.say("Mentions disabled.")
  272. else:
  273. await self.bot.send_cmd_help(ctx)
  274. dataIO.save_json("data/streams/settings.json", self.settings)
  275. @streamset.command(pass_context=True, no_pm=True)
  276. @checks.admin()
  277. async def autodelete(self, ctx):
  278. """Toggles automatic notification deletion for streams that go offline"""
  279. server = ctx.message.server
  280. settings = self.settings[server.id]
  281. current = settings.get("AUTODELETE", True)
  282. settings["AUTODELETE"] = not current
  283. if settings["AUTODELETE"]:
  284. await self.bot.say("Notifications will be automatically deleted "
  285. "once the stream goes offline.")
  286. else:
  287. await self.bot.say("Notifications won't be deleted anymore.")
  288. dataIO.save_json("data/streams/settings.json", self.settings)
  289. async def hitbox_online(self, stream):
  290. url = "https://api.hitbox.tv/media/live/" + stream
  291. async with aiohttp.get(url) as r:
  292. data = await r.json(encoding='utf-8')
  293. if "livestream" not in data:
  294. raise StreamNotFound()
  295. elif data["livestream"][0]["media_is_live"] == "0":
  296. raise OfflineStream()
  297. elif data["livestream"][0]["media_is_live"] == "1":
  298. return self.hitbox_embed(data)
  299. raise APIError()
  300. async def twitch_online(self, stream):
  301. session = aiohttp.ClientSession()
  302. url = "https://api.twitch.tv/kraken/streams/" + stream
  303. header = {
  304. 'Client-ID': self.settings.get("TWITCH_TOKEN", ""),
  305. 'Accept': 'application/vnd.twitchtv.v5+json'
  306. }
  307. async with session.get(url, headers=header) as r:
  308. data = await r.json(encoding='utf-8')
  309. await session.close()
  310. if r.status == 200:
  311. if data["stream"] is None:
  312. raise OfflineStream()
  313. return self.twitch_embed(data)
  314. elif r.status == 400:
  315. raise InvalidCredentials()
  316. elif r.status == 404:
  317. raise StreamNotFound()
  318. else:
  319. raise APIError()
  320. async def mixer_online(self, stream):
  321. url = "https://mixer.com/api/v1/channels/" + stream
  322. async with aiohttp.get(url) as r:
  323. data = await r.json(encoding='utf-8')
  324. if r.status == 200:
  325. if data["online"] is True:
  326. return self.mixer_embed(data)
  327. else:
  328. raise OfflineStream()
  329. elif r.status == 404:
  330. raise StreamNotFound()
  331. else:
  332. raise APIError()
  333. async def picarto_online(self, stream):
  334. url = "https://api.picarto.tv/v1/channel/name/" + stream
  335. async with aiohttp.get(url) as r:
  336. data = await r.text(encoding='utf-8')
  337. if r.status == 200:
  338. data = json.loads(data)
  339. if data["online"] is True:
  340. return self.picarto_embed(data)
  341. else:
  342. raise OfflineStream()
  343. elif r.status == 404:
  344. raise StreamNotFound()
  345. else:
  346. raise APIError()
  347. async def fetch_twitch_ids(self, *streams, raise_if_none=False):
  348. def chunks(l):
  349. for i in range(0, len(l), 100):
  350. yield l[i:i + 100]
  351. base_url = "https://api.twitch.tv/kraken/users?login="
  352. header = {
  353. 'Client-ID': self.settings.get("TWITCH_TOKEN", ""),
  354. 'Accept': 'application/vnd.twitchtv.v5+json'
  355. }
  356. results = []
  357. for streams_list in chunks(streams):
  358. session = aiohttp.ClientSession()
  359. url = base_url + ",".join(streams_list)
  360. async with session.get(url, headers=header) as r:
  361. data = await r.json(encoding='utf-8')
  362. if r.status == 200:
  363. results.extend(data["users"])
  364. elif r.status == 400:
  365. raise InvalidCredentials()
  366. else:
  367. raise APIError()
  368. await session.close()
  369. if not results and raise_if_none:
  370. raise StreamNotFound()
  371. return results
  372. def twitch_embed(self, data):
  373. channel = data["stream"]["channel"]
  374. url = channel["url"]
  375. logo = channel["logo"]
  376. if logo is None:
  377. logo = "https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png"
  378. status = channel["status"]
  379. if not status:
  380. status = "Untitled broadcast"
  381. embed = discord.Embed(title=status, url=url)
  382. embed.set_author(name=channel["display_name"])
  383. embed.add_field(name="Followers", value=channel["followers"])
  384. embed.add_field(name="Total views", value=channel["views"])
  385. embed.set_thumbnail(url=logo)
  386. if data["stream"]["preview"]["medium"]:
  387. embed.set_image(url=data["stream"]["preview"]["medium"] + self.rnd_attr())
  388. if channel["game"]:
  389. embed.set_footer(text="Playing: " + channel["game"])
  390. embed.color = 0x6441A4
  391. return embed
  392. def hitbox_embed(self, data):
  393. base_url = "https://edge.sf.hitbox.tv"
  394. livestream = data["livestream"][0]
  395. channel = livestream["channel"]
  396. url = channel["channel_link"]
  397. embed = discord.Embed(title=livestream["media_status"], url=url)
  398. embed.set_author(name=livestream["media_name"])
  399. embed.add_field(name="Followers", value=channel["followers"])
  400. #embed.add_field(name="Views", value=channel["views"])
  401. embed.set_thumbnail(url=base_url + channel["user_logo"])
  402. if livestream["media_thumbnail"]:
  403. embed.set_image(url=base_url + livestream["media_thumbnail"] + self.rnd_attr())
  404. embed.set_footer(text="Playing: " + livestream["category_name"])
  405. embed.color = 0x98CB00
  406. return embed
  407. def mixer_embed(self, data):
  408. default_avatar = ("https://mixer.com/_latest/assets/images/main/"
  409. "avatars/default.jpg")
  410. user = data["user"]
  411. url = "https://mixer.com/" + data["token"]
  412. embed = discord.Embed(title=data["name"], url=url)
  413. embed.set_author(name=user["username"])
  414. embed.add_field(name="Followers", value=data["numFollowers"])
  415. embed.add_field(name="Total views", value=data["viewersTotal"])
  416. if user["avatarUrl"]:
  417. embed.set_thumbnail(url=user["avatarUrl"])
  418. else:
  419. embed.set_thumbnail(url=default_avatar)
  420. if data["thumbnail"]:
  421. embed.set_image(url=data["thumbnail"]["url"] + self.rnd_attr())
  422. embed.color = 0x4C90F3
  423. if data["type"] is not None:
  424. embed.set_footer(text="Playing: " + data["type"]["name"])
  425. return embed
  426. def picarto_embed(self, data):
  427. avatar = ("https://picarto.tv/user_data/usrimg/{}/dsdefault.jpg{}"
  428. "".format(data["name"].lower(), self.rnd_attr()))
  429. url = "https://picarto.tv/" + data["name"]
  430. thumbnail = data["thumbnails"]["web"]
  431. embed = discord.Embed(title=data["title"], url=url)
  432. embed.set_author(name=data["name"])
  433. embed.set_image(url=thumbnail + self.rnd_attr())
  434. embed.add_field(name="Followers", value=data["followers"])
  435. embed.add_field(name="Total views", value=data["viewers_total"])
  436. embed.set_thumbnail(url=avatar)
  437. embed.color = 0x132332
  438. data["tags"] = ", ".join(data["tags"])
  439. if not data["tags"]:
  440. data["tags"] = "None"
  441. if data["adult"]:
  442. data["adult"] = "NSFW | "
  443. else:
  444. data["adult"] = ""
  445. embed.color = 0x4C90F3
  446. embed.set_footer(text="{adult}Category: {category} | Tags: {tags}"
  447. "".format(**data))
  448. return embed
  449. def enable_or_disable_if_active(self, streams, stream, channel, _id=None):
  450. """Returns True if enabled or False if disabled"""
  451. for i, s in enumerate(streams):
  452. stream_id = s.get("ID")
  453. if stream_id and _id: # ID is available, matching by ID is
  454. if stream_id != _id: # preferable
  455. continue
  456. else: # ID unavailable, matching by name
  457. if s["NAME"] != stream:
  458. continue
  459. if channel.id in s["CHANNELS"]:
  460. streams[i]["CHANNELS"].remove(channel.id)
  461. if not s["CHANNELS"]:
  462. streams.remove(s)
  463. return False
  464. else:
  465. streams[i]["CHANNELS"].append(channel.id)
  466. return True
  467. data = {"CHANNELS": [channel.id],
  468. "NAME": stream,
  469. "ALREADY_ONLINE": False}
  470. if _id:
  471. data["ID"] = _id
  472. streams.append(data)
  473. return True
  474. async def stream_checker(self):
  475. CHECK_DELAY = 60
  476. try:
  477. await self._migration_twitch_v5()
  478. except InvalidCredentials:
  479. print("Error during convertion of twitch usernames to IDs: "
  480. "invalid token")
  481. except Exception as e:
  482. print("Error during convertion of twitch usernames to IDs: "
  483. "{}".format(e))
  484. while self == self.bot.get_cog("Streams"):
  485. save = False
  486. streams = ((self.twitch_streams, self.twitch_online),
  487. (self.hitbox_streams, self.hitbox_online),
  488. (self.mixer_streams, self.mixer_online),
  489. (self.picarto_streams, self.picarto_online))
  490. for streams_list, parser in streams:
  491. if parser == self.twitch_online:
  492. _type = "ID"
  493. else:
  494. _type = "NAME"
  495. for stream in streams_list:
  496. if _type not in stream:
  497. continue
  498. key = (parser, stream[_type])
  499. try:
  500. embed = await parser(stream[_type])
  501. except OfflineStream:
  502. if stream["ALREADY_ONLINE"]:
  503. stream["ALREADY_ONLINE"] = False
  504. save = True
  505. await self.delete_old_notifications(key)
  506. except: # We don't want our task to die
  507. continue
  508. else:
  509. if stream["ALREADY_ONLINE"]:
  510. continue
  511. save = True
  512. stream["ALREADY_ONLINE"] = True
  513. messages_sent = []
  514. for channel_id in stream["CHANNELS"]:
  515. channel = self.bot.get_channel(channel_id)
  516. if channel is None:
  517. continue
  518. mention = self.settings.get(channel.server.id, {}).get("MENTION", "")
  519. can_speak = channel.permissions_for(channel.server.me).send_messages
  520. message = mention + " {} is live!".format(stream["NAME"])
  521. if channel and can_speak:
  522. m = await self.bot.send_message(channel, message, embed=embed)
  523. messages_sent.append(m)
  524. self.messages_cache[key] = messages_sent
  525. await asyncio.sleep(0.5)
  526. if save:
  527. dataIO.save_json("data/streams/twitch.json", self.twitch_streams)
  528. dataIO.save_json("data/streams/hitbox.json", self.hitbox_streams)
  529. dataIO.save_json("data/streams/beam.json", self.mixer_streams)
  530. dataIO.save_json("data/streams/picarto.json", self.picarto_streams)
  531. await asyncio.sleep(CHECK_DELAY)
  532. async def delete_old_notifications(self, key):
  533. for message in self.messages_cache[key]:
  534. server = message.server
  535. settings = self.settings.get(server.id, {})
  536. is_enabled = settings.get("AUTODELETE", True)
  537. try:
  538. if is_enabled:
  539. await self.bot.delete_message(message)
  540. except:
  541. pass
  542. del self.messages_cache[key]
  543. def rnd_attr(self):
  544. """Avoids Discord's caching"""
  545. return "?rnd=" + "".join([choice(ascii_letters) for i in range(6)])
  546. async def _migration_twitch_v5(self):
  547. # Migration of old twitch streams to API v5
  548. to_convert = []
  549. for stream in self.twitch_streams:
  550. if "ID" not in stream:
  551. to_convert.append(stream["NAME"])
  552. if not to_convert:
  553. return
  554. results = await self.fetch_twitch_ids(*to_convert)
  555. for stream in self.twitch_streams:
  556. for result in results:
  557. if stream["NAME"].lower() == result["name"].lower():
  558. stream["ID"] = result["_id"]
  559. # We might as well delete the invalid / renamed ones
  560. self.twitch_streams = [s for s in self.twitch_streams if "ID" in s]
  561. dataIO.save_json("data/streams/twitch.json", self.twitch_streams)
  562. def check_folders():
  563. if not os.path.exists("data/streams"):
  564. print("Creating data/streams folder...")
  565. os.makedirs("data/streams")
  566. def check_files():
  567. stream_files = (
  568. "twitch.json",
  569. "hitbox.json",
  570. "beam.json",
  571. "picarto.json"
  572. )
  573. for filename in stream_files:
  574. if not dataIO.is_valid_json("data/streams/" + filename):
  575. print("Creating empty {}...".format(filename))
  576. dataIO.save_json("data/streams/" + filename, [])
  577. f = "data/streams/settings.json"
  578. if not dataIO.is_valid_json(f):
  579. print("Creating empty settings.json...")
  580. dataIO.save_json(f, {})
  581. def setup(bot):
  582. logger = logging.getLogger('aiohttp.client')
  583. logger.setLevel(50) # Stops warning spam
  584. check_folders()
  585. check_files()
  586. n = Streams(bot)
  587. loop = asyncio.get_event_loop()
  588. loop.create_task(n.stream_checker())
  589. bot.add_cog(n)