Skip to content
This repository has been archived by the owner on Nov 24, 2019. It is now read-only.

Commit

Permalink
Release v0.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sco1 committed Sep 29, 2018
2 parents 511d4b5 + f848bfd commit 1255110
Show file tree
Hide file tree
Showing 32 changed files with 700 additions and 447 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Expand Up @@ -16,4 +16,4 @@ RUN pip install -r requirements.txt

RUN apk del .pynacl_deps

CMD ["python", "wumbotLogin.py"]
CMD ["python", "-m", "bot"]
13 changes: 13 additions & 0 deletions bot/__init__.py
@@ -0,0 +1,13 @@
import logging
import time

# Force UTC Timestamps
# From the logging cookbook: https://docs.python.org/3/howto/logging-cookbook.html
class UTCFormatter(logging.Formatter):
converter = time.gmtime

logformat = '%(asctime)s %(levelname)s:%(module)s:%(message)s'
dateformat = '%Y-%m-%d %H:%M:%S'
logging.basicConfig(filename='./log/wumbot.log', filemode='a', level=logging.INFO,
format=logformat, datefmt=dateformat
)
24 changes: 7 additions & 17 deletions wumbotLogin.py → bot/__main__.py
Expand Up @@ -5,19 +5,9 @@

from discord.ext import commands

from cogs import overwatch, mhw, wumbopresence, rocketleague
from .cogs import mhw, overwatch, rocketleague, wumbopresence


# Force UTC Timestamps
# From the logging cookbook: https://docs.python.org/3/howto/logging-cookbook.html
class UTCFormatter(logging.Formatter):
converter = time.gmtime

logformat = '%(asctime)s %(levelname)s:%(module)s:%(message)s'
dateformat = '%Y-%m-%d %H:%M:%S'
logging.basicConfig(filename='./log/wumbot.log', filemode='a', level=logging.INFO,
format=logformat, datefmt=dateformat
)
class WumbotClient(commands.Bot):
def __init__(self, *args, **kwargs):
super(WumbotClient, self).__init__(*args, **kwargs)
Expand All @@ -42,14 +32,14 @@ def loadCredentials(credentialJSON) -> str:
client = WumbotClient(command_prefix='~')

# Load cogs
client.load_extension("cogs.bot")
client.load_extension("cogs.reddit")
client.load_extension("cogs.overwatch")
client.load_extension("cogs.mhw")
client.load_extension("cogs.rocketleague")
client.load_extension("bot.cogs.bot")
client.load_extension("bot.cogs.reddit")
client.load_extension("bot.cogs.overwatch")
client.load_extension("bot.cogs.mhw")
client.load_extension("bot.cogs.rocketleague")

# Setup event loops
client.loop.create_task(wumbopresence.randWumboTimer(client, wumboJSON='wumbolist.JSON'))
client.loop.create_task(wumbopresence.randWumboTimer(client, wumboJSON='./bot/wumbolist.JSON'))
client.loop.create_task(overwatch.patchchecktimer(client))
client.loop.create_task(mhw.patchchecktimer(client))
client.loop.create_task(rocketleague.patchchecktimer(client))
Expand Down
File renamed without changes.
File renamed without changes.
27 changes: 2 additions & 25 deletions cogs/bot.py → bot/cogs/bot.py
Expand Up @@ -7,6 +7,8 @@
import git
from discord.ext import commands

from bot.utils import Helpers


class MainCommands():
def __init__(self, bot):
Expand Down Expand Up @@ -138,30 +140,5 @@ def _buildletterunicode():
return {letter:chr(ID) for letter, ID in zip(string.ascii_uppercase, range(127462, 127488))}


class Helpers:
@staticmethod
def isOwner(user: discord.User) -> bool:
"""
Check to see if the input User's ID matches the Owner ID
"""
ownerID = 129606635545952258
return user.id == ownerID

@staticmethod
def isDM(channel: discord.TextChannel) -> bool:
"""
Check to see if a channel is a DM
A DM is either an instance of DMChannel or GroupChannel
"""
return isinstance(channel, (discord.DMChannel, discord.GroupChannel))

def isWumbologist(member: discord.Member) -> bool:
"""
Check to see if a discord.Member has the 'Wumbologists' role
"""
return 'Wumbologists' in [str(role) for role in member.roles]


def setup(bot):
bot.add_cog(MainCommands(bot))
7 changes: 4 additions & 3 deletions cogs/mhw.py → bot/cogs/mhw.py
Expand Up @@ -10,8 +10,9 @@
from discord.ext import commands
from yarl import URL

from .bot import Helpers
from .steam import SteamNewsPost
from bot.models.Steam import SteamNewsPost
from bot.utils import Helpers


class MHWNewsParser:
def __init__(self, bot):
Expand All @@ -28,7 +29,7 @@ async def getofficialnews(self, appID: int=None) -> typing.List:
"""
appID = appID if appID is not None else self.appID

news = await SteamNewsPost.asyncgetnewsforapp(appID=appID, count=15, maxlength=500)
news = await SteamNewsPost.asyncgetnewsforapp(appID=appID, count=15, maxlength=600)
logging.info(f"{len(news)} MHW news posts returned by Steam's API")
officialnews = [item for item in news if self.MHWnewsfilter(item, self.officialaccount)]

Expand Down
113 changes: 3 additions & 110 deletions cogs/overwatch.py → bot/cogs/overwatch.py
Expand Up @@ -3,18 +3,15 @@
import logging
import re
import typing
from datetime import datetime
from pathlib import Path

import aiohttp
import discord
import requests
from bs4 import BeautifulSoup
from discord.ext import commands
from yarl import URL

from .bot import Helpers
from .reddit import RedditPost, RedditPRAW, RedditJSON
from bot.models.Overwatch import OWPatch
from bot.models.Reddit import RedditJSON, RedditPost, RedditPRAW
from bot.utils import Helpers


class PatchGifParser:
Expand Down Expand Up @@ -118,110 +115,6 @@ def gfygif(inURL: typing.Union[str, URL]) -> URL:
return URL.build(scheme="https", host="giant.gfycat.com", path=f"{gfyID}.gif")


class OWPatch():
def __init__(self, patchref: str=None, ver: str=None, patchdate: datetime=None,
patchURL: URL=None, bannerURL: URL=None
):
defaultpatchURL = URL('https://playoverwatch.com/en-us/news/patch-notes/pc')
defaultbannerURL = URL('https://gear.blizzard.com/media/wysiwyg/default/logos/ow-logo-white-nds.png')

self.patchref = patchref
self.ver = ver
self.patchdate = patchdate
self.patchURL = patchURL if patchURL is not None else defaultpatchURL
self.bannerURL = bannerURL if bannerURL is not None else defaultbannerURL

def __repr__(self):
return f"OWPatch: v{self.ver}, Released: {datetime.strftime(self.patchdate, '%Y-%m-%d')}"

@staticmethod
def fromURL(inURL: typing.Union[str, URL]=URL('https://playoverwatch.com/en-us/news/patch-notes/pc')) -> typing.List:
"""
Return a list of OWPatch objects from Blizzard's Patch Notes
"""
if not inURL:
raise ValueError("No URL provided")
inURL = URL(inURL)

r = requests.get(inURL).text

return OWPatch._parseOWpatchHTML(r)

@staticmethod
async def asyncfromURL(inURL: typing.Union[str, URL]=URL('https://playoverwatch.com/en-us/news/patch-notes/pc')) -> typing.List:
"""
This function is a coroutine
Return a list of OWPatch objects from Blizzard's Patch Notes
"""
if not inURL:
raise ValueError("No URL provided")
inURL = URL(inURL)

async with aiohttp.ClientSession() as session:
async with session.get(inURL) as resp:
r = await resp.text()

return OWPatch._parseOWpatchHTML(r)

@staticmethod
def _parseOWpatchHTML(inHTML: str) -> typing.List:
soup = BeautifulSoup(inHTML, 'html.parser')

# Iterate over patches
patches = soup.find_all('div', class_='patch-notes-patch')

patchobjs = []
for patch in patches:
# Get patch reference ID
patchref = patch.get('id')
patchref_num = patchref.split('-')[-1] # Get numeric reference to build BlizzTrack link later

# Get version number from sidebar using patch reference ID
sidebaritem = soup.select_one(f"a[href=#{patchref}]").parent
ver = sidebaritem.find('h3').get_text().split()[-1]

# Get date
dateheader = patch.find('h2', class_='HeadingBanner-header')
if dateheader:
patchdate = datetime.strptime(dateheader.get_text(), '%B %d, %Y')
else:
# In the event there is no banner, the date is instead embedded in <h1>Overwatch Patch Notes – June 5, 2018</h1>
# Since we already have the sidebar entry, it's slightly simpler to get the date from that instead
patchdate = datetime.strptime(sidebaritem.find('p').get_text(), '%m/%d/%Y')

# Get patch banner
# If there is a banner for the patch, it's embedded in the 'style' portion of the '.HeadingBanner' div
# e.g. <div class="HeadingBanner" style="background-image: url(https://link/to.jpg);">
patchbannerdiv = patch.select_one('.HeadingBanner')
if patchbannerdiv:
expr = r"url\(\"?([^\"]+)\"?\)"
m = re.search(expr, patchbannerdiv['style'])
if m:
patchbanner = URL(m.group(1))
else:
patchbanner = None
else:
patchbanner = None

patchobjs.append(OWPatch(patchref, ver, patchdate, OWPatch.getblizztrack(patchref_num), patchbanner))

return patchobjs

@staticmethod
def getblizztrack(patchref: str=None) -> URL:
"""
Return BlizzTrack URL to patch notes, built using Blizzard's patchref
e.g. https://blizztrack.com/patch_notes/overwatch/50148
"""
if not patchref:
raise ValueError('No patch reference provided')

baseURL = URL('https://blizztrack.com/patch_notes/overwatch/')
return baseURL / patchref


class PatchNotesParser:
def __init__(self, bot):
self.bot = bot
Expand Down
73 changes: 73 additions & 0 deletions bot/cogs/reddit.py
@@ -0,0 +1,73 @@
import re
import typing

import discord
from discord.ext import commands


class Reddit():
def __init__(self, bot):
self.bot = bot

@staticmethod
def buildSubredditEmbed(subredditlist: typing.List[str], embedlimit: int=3):
"""
Build a message embed from a list of subreddit strings (sans '/r/')
Limit to embedlimit number of subreddits per embed, for brevity. Default is 3
"""
snooURL = "https://images-eu.ssl-images-amazon.com/images/I/418PuxYS63L.png"

embed = discord.Embed(color=discord.Color(0x9c4af7))
embed.set_thumbnail(url=snooURL)
embed.set_author(name='Subreddit Embedder 9000')
embed.set_footer(text='Reddit', icon_url=snooURL)

if len(subredditlist) == 1:
embed.description = "Subreddit detected!"
subreddit = subredditlist[0]
embed.add_field(name=f"/r/{subreddit}", value=f"https://www.reddit.com/r/{subreddit}", inline=False)
else:
embed.description = "Subreddits detected!"
for subreddit in subredditlist[0:embedlimit]:
embed.add_field(name=f"/r/{subreddit}", value=f"https://www.reddit.com/r/{subreddit}", inline=False)

if len(subredditlist) > embedlimit:
embed.add_field(name="Note:", value=f"For brevity, only {embedlimit} subreddits have been embedded. You linked {len(subredditlist)}")

return embed

async def on_message(self, message: discord.Message):
"""
Check messages for:
1. Subreddit reference (/r/subreddit) and reply with a link embed
2. Reddit's image/video hosting adding 'DashPlaylist.mpd' to the end of the file, which
links to nothing. Reply with a link embed to the media without the suffix
"""
# Avoid self-replies
if message.author.id == self.bot.user.id:
return

# Check to see if /r/_subreddit (e.g. /r/python) has been typed & add a Reddit embed
# Ignores regular reddit links (e.g. http://www.reddit.com/r/Python)
testSubreddit = re.findall(r'(?:^|\s)\/?[rR]\/(\w+)', message.content)
if testSubreddit:
logging.info(f"Subreddit(s) detected: '{testSubreddit}'")
logging.info(f"Original message: '{message.content}'")
SubredditEmbed = self.buildSubredditEmbed(testSubreddit)
await message.channel.send(embed=SubredditEmbed)

# Check to see if Reddit's stupid image/video hosting has added 'DashPlaylist.mpd'
# to the end of the URL, which links to a direct download (of nothing) rather
# than the web content
testVreddit = re.search(r'(https?:\/\/v.redd.it\/.*)(DASHPlaylist.*$)', message.content)
if testVreddit:
newURL = testVreddit.group(1)
logging.info(f"VReddit MPD detected: '{testVreddit.group(0)}'")
logging.info(f"Link converted to: {newURL}")
await message.channel.send(f"Here {message.author.mention}, let me fix that v.redd.it link for you: {newURL}")


def setup(bot):
bot.add_cog(Reddit(bot))
11 changes: 5 additions & 6 deletions cogs/rocketleague.py → bot/cogs/rocketleague.py
Expand Up @@ -2,16 +2,15 @@
import json
import logging
import typing
from datetime import datetime
from pathlib import Path

import aiohttp
import discord
from discord.ext import commands
from yarl import URL

from .bot import Helpers
from .steam import SteamNewsPost
from bot.models.Steam import SteamNewsPost
from bot.utils import Helpers


class RLNewsParser:
def __init__(self, bot):
Expand All @@ -28,8 +27,8 @@ async def getofficialnews(self, appID: int=None) -> typing.List:
"""
appID = appID if appID is not None else self.appID

news = await SteamNewsPost.asyncgetnewsforapp(appID=appID, count=15, maxlength=500)
logging.info(f"{len(news)} RLnews posts returned by Steam's API")
news = await SteamNewsPost.asyncgetnewsforapp(appID=appID, count=15, maxlength=600)
logging.info(f"{len(news)} RL news post(s) returned by Steam's API")
officialnews = [item for item in news if self.RLnewsfilter(item, self.psyonixstaff)]

logging.info(f"Found {len(officialnews)} official RL news posts")
Expand Down
2 changes: 1 addition & 1 deletion cogs/wumbopresence.py → bot/cogs/wumbopresence.py
Expand Up @@ -5,7 +5,7 @@

from discord import Game

def randWumbo(wumboJSON=None):
def randWumbo(wumboJSON=None) -> str:
"""
Load list of Wumboisms from input JSON file & return a random string from the list
Expand Down

0 comments on commit 1255110

Please sign in to comment.