Compare commits
9 Commits
python3
...
extra-modu
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e4b3889d4 | |||
| 12c02eed22 | |||
| dbc60d2e30 | |||
| ad9b90c064 | |||
| 0e70126c7a | |||
| ed3a3c5595 | |||
| 2ae965df4a | |||
| 7f888a33b4 | |||
| b6383f2314 |
21
modules-available/autochannel.ini
Normal file
21
modules-available/autochannel.ini
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[autochannel]
|
||||||
|
; Comma seperated list of servers to operate on, leave empty for all
|
||||||
|
servers =
|
||||||
|
|
||||||
|
; Number of spare channel
|
||||||
|
spare_channel = 1
|
||||||
|
|
||||||
|
; Number of channels taken from top100 steam should be created
|
||||||
|
top_games_limit = 10
|
||||||
|
|
||||||
|
; Name used for root channel for newly created channels
|
||||||
|
root_channel_name = Endless
|
||||||
|
|
||||||
|
; Interval in sec between channel checks
|
||||||
|
check_timer_interval = 10
|
||||||
|
|
||||||
|
; Additional game channels specified with steam AppIDs, comma seperated
|
||||||
|
game_channel_permanent =
|
||||||
|
|
||||||
|
; Name of top games root channel
|
||||||
|
game_channel_name = Games
|
||||||
12
modules-available/chatparser.ini
Normal file
12
modules-available/chatparser.ini
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
[chatparser]
|
||||||
|
; Comma seperated list of servers to operate on, leave empty for all
|
||||||
|
servers =
|
||||||
|
; YouTube API key
|
||||||
|
youtube_api_key =
|
||||||
|
|
||||||
|
; Amazon API stuff
|
||||||
|
amazon_access_key =
|
||||||
|
amazon_secret_key =
|
||||||
|
amazon_assoc_tag =
|
||||||
|
amazon_region =
|
||||||
169
modules/autochannel.py
Normal file
169
modules/autochannel.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import random
|
||||||
|
from threading import Timer
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from mumo_module import (commaSeperatedIntegers, MumoModule)
|
||||||
|
from tools.SteamApps import SteamApps
|
||||||
|
from tools.Utils import find_create_channel, get_empty_channels, get_subchannels, get_user_for_channel
|
||||||
|
|
||||||
|
|
||||||
|
class autochannel(MumoModule):
|
||||||
|
default_config = {'autochannel': (
|
||||||
|
('servers', commaSeperatedIntegers, []),
|
||||||
|
('spare_channel', int, 1),
|
||||||
|
('top_games_limit', int, 10),
|
||||||
|
('root_channel_name', str, "Endless"),
|
||||||
|
('check_timer_interval', int, 10),
|
||||||
|
('game_channel_permanent', str, ""),
|
||||||
|
('game_channel_name', str, "Games")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, name, manager, configuration=None):
|
||||||
|
MumoModule.__init__(self, name, manager, configuration)
|
||||||
|
self.murmur = manager.getMurmurModule()
|
||||||
|
self.spare_channel = self.cfg().autochannel.spare_channel
|
||||||
|
self.top_games_limit = self.cfg().autochannel.top_games_limit
|
||||||
|
self.root_channel_name = self.cfg().autochannel.root_channel_name
|
||||||
|
self.timer_interval = self.cfg().autochannel.check_timer_interval
|
||||||
|
self.game_channel_permanent = self.cfg().autochannel.game_channel_permanent.replace(" ", "").split(',')
|
||||||
|
self.game_channel_name = self.cfg().autochannel.game_channel_name
|
||||||
|
self.random_root = None
|
||||||
|
self.game_root = None
|
||||||
|
self.servertimer = {}
|
||||||
|
|
||||||
|
# Load steam top 100 last two weeks
|
||||||
|
r = requests.get("http://steamspy.com/api.php?request=top100in2weeks")
|
||||||
|
|
||||||
|
if r.status_code == 200:
|
||||||
|
self.top_list = r.json()
|
||||||
|
self.log().info("Loaded steam Top100")
|
||||||
|
else:
|
||||||
|
self.top_list = None
|
||||||
|
|
||||||
|
self.glist = SteamApps.get_instance()
|
||||||
|
|
||||||
|
Timer(60 * 60, self.update_timer).start()
|
||||||
|
|
||||||
|
def connected(self):
|
||||||
|
self.log().debug("Register timer for running servers")
|
||||||
|
|
||||||
|
servers = self.cfg().autochannel.servers
|
||||||
|
if not servers:
|
||||||
|
servers = self.manager().SERVERS_ALL
|
||||||
|
|
||||||
|
self.manager().subscribeMetaCallbacks(self, servers)
|
||||||
|
|
||||||
|
for server in self.manager().getMeta().getBootedServers():
|
||||||
|
if server.id in self.servertimer:
|
||||||
|
self.servertimer[server.id].cancel()
|
||||||
|
self.servertimer[server.id] = Timer(self.timer_interval, self.handle_timer, [server])
|
||||||
|
self.servertimer[server.id].start()
|
||||||
|
|
||||||
|
def handle_timer(self, server):
|
||||||
|
self.check_random_channel(server)
|
||||||
|
self.check_game_channel(server)
|
||||||
|
|
||||||
|
self.servertimer[server.id] = Timer(self.timer_interval, self.handle_timer, [server])
|
||||||
|
self.servertimer[server.id].start()
|
||||||
|
|
||||||
|
def update_timer(self):
|
||||||
|
r = requests.get("http://steamspy.com/api.php?request=top100in2weeks")
|
||||||
|
|
||||||
|
if r.status_code == 200 and r.json():
|
||||||
|
self.top_list = r.json()
|
||||||
|
self.log().info("Reloaded steam top100")
|
||||||
|
Timer(60 * 60, self.update_timer).start()
|
||||||
|
else:
|
||||||
|
self.log().warn("Failed to reload top100 - HTTP {} (len={})".format(r.status_code, len(r.json())))
|
||||||
|
Timer(10, self.update_timer).start()
|
||||||
|
|
||||||
|
def disconnected(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_random_channel(self, server):
|
||||||
|
self.random_root = find_create_channel(self.root_channel_name, server, 0)
|
||||||
|
empty_channels = get_empty_channels(server, self.random_root.id)
|
||||||
|
|
||||||
|
for cid in empty_channels[1:]:
|
||||||
|
server.removeChannel(cid)
|
||||||
|
|
||||||
|
if len(empty_channels) == 0:
|
||||||
|
self.add_random_channel(server)
|
||||||
|
|
||||||
|
def add_random_channel(self, server):
|
||||||
|
word = chr(945 + random.randint(0, 24))
|
||||||
|
|
||||||
|
self.log().info("Added new channel " + word)
|
||||||
|
server.addChannel(word, self.random_root.id)
|
||||||
|
|
||||||
|
def check_game_channel(self, server):
|
||||||
|
self.game_root = find_create_channel(self.game_channel_name, server, 0)
|
||||||
|
|
||||||
|
if not self.top_list:
|
||||||
|
return
|
||||||
|
|
||||||
|
games = {}
|
||||||
|
for idx, app in enumerate(self.top_list.values()):
|
||||||
|
if idx < self.top_games_limit:
|
||||||
|
games[app["name"]] = app
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
for pgame in self.game_channel_permanent:
|
||||||
|
if pgame:
|
||||||
|
if str(pgame) in self.top_list:
|
||||||
|
game = self.top_list[str(pgame)]
|
||||||
|
|
||||||
|
if game:
|
||||||
|
games[game["name"]] = game
|
||||||
|
else:
|
||||||
|
game = self.glist.appid2game(pgame)
|
||||||
|
if game:
|
||||||
|
games[game["name"]] = game
|
||||||
|
|
||||||
|
channels = get_subchannels(server, self.game_root.id)
|
||||||
|
games_matched = []
|
||||||
|
|
||||||
|
for channel in channels:
|
||||||
|
if len(get_user_for_channel(channel.id, server)) == 0 and channel.name not in games:
|
||||||
|
server.removeChannel(channel.id)
|
||||||
|
elif channel.name in games:
|
||||||
|
games_matched.append(channel.name)
|
||||||
|
|
||||||
|
channel_tbc = {*games} - set(games_matched)
|
||||||
|
|
||||||
|
if games_matched:
|
||||||
|
for game in games_matched:
|
||||||
|
ch = find_create_channel(game, server, self.game_root.id)
|
||||||
|
if ch.position is not list(self.top_list).index(str(games[game]["appid"])):
|
||||||
|
ch.description = 'Top {0} on <a href="http://store.steampowered.com/app/{1}">Steam</a>'.format(
|
||||||
|
list(self.top_list).index(str(games[game]["appid"])) + 1, games[game]["appid"])
|
||||||
|
ch.position = list(self.top_list).index(str(games[game]["appid"]))
|
||||||
|
server.setChannelState(ch)
|
||||||
|
|
||||||
|
for game in channel_tbc:
|
||||||
|
nc = server.getChannelState(server.addChannel(game, self.game_root.id))
|
||||||
|
|
||||||
|
if nc:
|
||||||
|
nc.description = 'Top {0} on <a href="http://store.steampowered.com/app/{1}">Steam</a>'.format(
|
||||||
|
list(self.top_list).index(str(games[nc.name]["appid"])) + 1, games[nc.name]["appid"])
|
||||||
|
nc.position = list(self.top_list).index(str(games[nc.name]["appid"]))
|
||||||
|
server.setChannelState(nc)
|
||||||
|
|
||||||
|
#
|
||||||
|
# --- Meta callback functions
|
||||||
|
#
|
||||||
|
|
||||||
|
def started(self, server):
|
||||||
|
self.log().debug("Register timer for new server")
|
||||||
|
self.servertimer[server.id] = Timer(self.timer_interval, self.handle_timer, [server])
|
||||||
|
self.servertimer[server.id].start()
|
||||||
|
|
||||||
|
def stopped(self, server):
|
||||||
|
self.log().debug("Stopped timer for server")
|
||||||
|
if server.id in self.servertimer:
|
||||||
|
self.servertimer[server.id].cancel()
|
||||||
187
modules/chatparser.py
Normal file
187
modules/chatparser.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
import amazon
|
||||||
|
import isodate
|
||||||
|
import requests
|
||||||
|
from amazon.api import AmazonAPI
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from mumo_module import (commaSeperatedIntegers,
|
||||||
|
MumoModule)
|
||||||
|
from tools.SteamApps import SteamApps
|
||||||
|
|
||||||
|
# YT
|
||||||
|
youtube_api_url = "https://www.googleapis.com/youtube/v3/videos?part=contentDetails%2Csnippet%2Cstatistics&id={0}&fields=items(contentDetails(definition%2Cduration)%2Clocalizations%2Csnippet%2Ftitle%2Cstatistics)%2CpageInfo%2FtotalResults&key={1}"
|
||||||
|
youtube_regex = re.compile(
|
||||||
|
r"(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?.*v=|embed/|v/|.+\?v=)?([^&=%\?]{11})")
|
||||||
|
|
||||||
|
# STEAM
|
||||||
|
steam_regex = re.compile(r"(http|https)://store.steampowered.com/app/([0-9]+)[/\[]?")
|
||||||
|
steam_genre_regex = re.compile("Genre:</b>.*?<a.*?>(.+?)</a>")
|
||||||
|
|
||||||
|
# AMAZON
|
||||||
|
amazon_regex = re.compile(r"ama?zo?n\.(?:de|com)/.*(?:dp|gp/product)/([A-Z0-9]{10})(?:(?:/+)|$)?")
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
move_regex = re.compile(r"^moveall (.*)$")
|
||||||
|
tsp_cmd_regex = re.compile(r"^!(.+?)(?: (.+?))?(?: (.*))?$")
|
||||||
|
|
||||||
|
# Stuff
|
||||||
|
headers = {
|
||||||
|
'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.49 Safari/537.36"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class chatparser(MumoModule):
|
||||||
|
default_config = {'chatparser': (
|
||||||
|
('servers', commaSeperatedIntegers, []),
|
||||||
|
('youtube_api_key', str, ''),
|
||||||
|
('amazon_access_key', str, ''),
|
||||||
|
('amazon_secret_key', str, ''),
|
||||||
|
('amazon_assoc_tag', str, ''),
|
||||||
|
('amazon_region', str, '')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, name, manager, configuration=None):
|
||||||
|
MumoModule.__init__(self, name, manager, configuration)
|
||||||
|
self.murmur = manager.getMurmurModule()
|
||||||
|
self.youtube_api_key = self.cfg().chatparser.youtube_api_key
|
||||||
|
self.amazon_access_key = self.cfg().chatparser.amazon_access_key
|
||||||
|
self.amazon_secret_key = self.cfg().chatparser.amazon_secret_key
|
||||||
|
self.amazon_assoc_tag = self.cfg().chatparser.amazon_assoc_tag
|
||||||
|
self.amazon_region = self.cfg().chatparser.amazon_region
|
||||||
|
|
||||||
|
self.amazon_api = AmazonAPI(self.amazon_access_key, self.amazon_secret_key,
|
||||||
|
self.amazon_assoc_tag, region=self.amazon_region)
|
||||||
|
|
||||||
|
self.glist = SteamApps.get_instance()
|
||||||
|
|
||||||
|
def connected(self):
|
||||||
|
manager = self.manager()
|
||||||
|
log = self.log()
|
||||||
|
log.debug("Register for Server callbacks")
|
||||||
|
|
||||||
|
servers = self.cfg().chatparser.servers
|
||||||
|
if not servers:
|
||||||
|
servers = manager.SERVERS_ALL
|
||||||
|
|
||||||
|
manager.subscribeServerCallbacks(self, servers)
|
||||||
|
|
||||||
|
def disconnected(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sendMessage(self, server, user, message, msg):
|
||||||
|
if message.channels:
|
||||||
|
server.sendMessageChannel(user.channel, False, msg)
|
||||||
|
else:
|
||||||
|
server.sendMessage(user.session, msg)
|
||||||
|
server.sendMessage(message.sessions[0], msg)
|
||||||
|
|
||||||
|
#
|
||||||
|
# --- Server callback functions
|
||||||
|
#
|
||||||
|
|
||||||
|
def userTextMessage(self, server, user, message, current=None):
|
||||||
|
if len(message.sessions) == 1 or (len(message.channels) == 1 and message.channels[0] == user.channel):
|
||||||
|
yt = youtube_regex.search(message.text)
|
||||||
|
|
||||||
|
if yt:
|
||||||
|
self.parse_youtube(yt.group(6), user, server, message)
|
||||||
|
return
|
||||||
|
|
||||||
|
stm = steam_regex.search(message.text)
|
||||||
|
|
||||||
|
if stm:
|
||||||
|
self.parse_steam(int(stm.group(2)), user, server, message)
|
||||||
|
return
|
||||||
|
|
||||||
|
amazon = amazon_regex.search(message.text)
|
||||||
|
|
||||||
|
if amazon:
|
||||||
|
self.parse_amazon(amazon.group(1), user, server, message)
|
||||||
|
|
||||||
|
def parse_youtube(self, yid, user, server, message):
|
||||||
|
ytparse = requests.get(youtube_api_url.format(yid, self.youtube_api_key))
|
||||||
|
|
||||||
|
self.log().info("YT: %s", yid)
|
||||||
|
if ytparse.status_code == 200:
|
||||||
|
if ytparse.json()["pageInfo"]["totalResults"] > 0:
|
||||||
|
v = ytparse.json()["items"][0]
|
||||||
|
t = isodate.parse_duration(v["contentDetails"]["duration"])
|
||||||
|
try:
|
||||||
|
rate = "%.2f %%" % (
|
||||||
|
(((float(v["statistics"]["dislikeCount"]) / float(
|
||||||
|
v["statistics"]["likeCount"])) - 1.0) * -1.0) * 100.0)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
rate = "No rating possible"
|
||||||
|
self.sendMessage(server, user, message,
|
||||||
|
'<a href="https://youtu.be/{3}">{0}</a> | {1} | {2}'.format(
|
||||||
|
v["snippet"]["title"], t, rate, yid))
|
||||||
|
|
||||||
|
def parse_steam(self, appid, user, server, message):
|
||||||
|
self.log().info("STEAM: %s", str(appid))
|
||||||
|
|
||||||
|
steampage = requests.get("http://store.steampowered.com/app/" + str(appid) + "/?l=en", headers=headers)
|
||||||
|
price = None
|
||||||
|
genre = None
|
||||||
|
|
||||||
|
if steampage.status_code == 200:
|
||||||
|
soap = BeautifulSoup(steampage.text, "html.parser")
|
||||||
|
sprice = soap.find("div", {"class": "game_purchase_price price"})
|
||||||
|
|
||||||
|
if sprice:
|
||||||
|
price = sprice.get_text().strip()
|
||||||
|
else:
|
||||||
|
price = "Unkown Price"
|
||||||
|
|
||||||
|
reg_genre = steam_genre_regex.search(steampage.text)
|
||||||
|
|
||||||
|
if reg_genre:
|
||||||
|
genre = reg_genre.group(1)
|
||||||
|
else:
|
||||||
|
genre = "Unkown Genre"
|
||||||
|
|
||||||
|
if self.glist:
|
||||||
|
game = self.glist.appid2game(appid)
|
||||||
|
self.sendMessage(server, user, message,
|
||||||
|
'<a href="http://store.steampowered.com/app/{0}">{1}</a> | {2} | {3}'.format(
|
||||||
|
appid, game["name"], genre, price))
|
||||||
|
|
||||||
|
def parse_amazon(self, item_id, user, server, message):
|
||||||
|
self.log().info("AMAZON: %s", item_id)
|
||||||
|
try:
|
||||||
|
product = self.amazon_api.lookup(ItemId=item_id)
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
self.log().warning("[AMAZON] request failure: %s", e)
|
||||||
|
product = None
|
||||||
|
except amazon.api.AsinNotFound as e2:
|
||||||
|
self.log().warning("[AMAZON] Article not found: %s", e2)
|
||||||
|
product = None
|
||||||
|
|
||||||
|
if product:
|
||||||
|
self.sendMessage(server, user, message,
|
||||||
|
'<a href="https://amazon.de/dp/{0}">{1}</a> | {2} € | {3}'.format(
|
||||||
|
item_id, product.title, product.price_and_currency[0],
|
||||||
|
product.get_attribute("ProductGroup")))
|
||||||
|
|
||||||
|
def userConnected(self, server, state, context=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def userDisconnected(self, server, state, context=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def userStateChanged(self, server, state, context=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def channelCreated(self, server, state, context=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def channelRemoved(self, server, state, context=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def channelStateChanged(self, server, state, context=None):
|
||||||
|
pass
|
||||||
32
tools/SteamApps.py
Normal file
32
tools/SteamApps.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class SteamApps:
|
||||||
|
__instance = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_instance():
|
||||||
|
""" Static access method. """
|
||||||
|
if not SteamApps.__instance:
|
||||||
|
SteamApps()
|
||||||
|
return SteamApps.__instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
""" Virtually private constructor. """
|
||||||
|
if SteamApps.__instance:
|
||||||
|
raise Exception("This class is a singleton!")
|
||||||
|
else:
|
||||||
|
SteamApps.__instance = self
|
||||||
|
|
||||||
|
# Load steam appid <-> game list
|
||||||
|
r = requests.get("http://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=json")
|
||||||
|
|
||||||
|
if r.status_code == 200:
|
||||||
|
self.glist = r.json()
|
||||||
|
else:
|
||||||
|
self.glist = None
|
||||||
|
|
||||||
|
def appid2game(self, appid):
|
||||||
|
for game in self.glist["applist"]["apps"]:
|
||||||
|
if appid == game["appid"]:
|
||||||
|
return game
|
||||||
47
tools/Utils.py
Normal file
47
tools/Utils.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
def find_create_channel(name: str, server, parent: int = None):
|
||||||
|
channels = server.getChannels()
|
||||||
|
|
||||||
|
for cid, channel in channels.items():
|
||||||
|
if parent:
|
||||||
|
if channel.name == name and channel.parent == parent:
|
||||||
|
return channel
|
||||||
|
else:
|
||||||
|
if channel.name == name:
|
||||||
|
return channel
|
||||||
|
|
||||||
|
new_cid = server.addChannel(name, parent if parent else 0)
|
||||||
|
return server.getChannelState(new_cid)
|
||||||
|
|
||||||
|
|
||||||
|
def get_empty_channels(server, parent: int = None):
|
||||||
|
empty_channels = []
|
||||||
|
channels = server.getChannels()
|
||||||
|
|
||||||
|
for cid, channel in channels.items():
|
||||||
|
if parent:
|
||||||
|
if channel.parent == parent and len(get_user_for_channel(cid, server)) == 0:
|
||||||
|
empty_channels.append(cid)
|
||||||
|
|
||||||
|
return empty_channels
|
||||||
|
|
||||||
|
|
||||||
|
def get_subchannels(server, parent: int):
|
||||||
|
sub_channel = []
|
||||||
|
channels = server.getChannels()
|
||||||
|
|
||||||
|
for cid, channel in channels.items():
|
||||||
|
if channel.parent == parent:
|
||||||
|
sub_channel.append(channel)
|
||||||
|
|
||||||
|
return sub_channel
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_for_channel(cid, server):
|
||||||
|
users = server.getUsers()
|
||||||
|
users_in_channel = {}
|
||||||
|
|
||||||
|
for uid, user in users.items():
|
||||||
|
if user.channel == cid:
|
||||||
|
users_in_channel[uid] = user
|
||||||
|
|
||||||
|
return users_in_channel
|
||||||
Reference in New Issue
Block a user