Compare commits

...

2 Commits

Author SHA1 Message Date
dave 9e3a7b68fe Add stockindex module 2020-07-02 12:03:53 -07:00
dave 235921fbab add replyto field to privmsg ircevent messages 2020-07-02 11:20:00 -07:00
5 changed files with 158 additions and 16 deletions

View File

@ -0,0 +1,56 @@
:mod:`StockIndex` --- DJIA and NASDAQ Quotes
============================================
This module provides quotes for the DJIA and NASDAQ indexes. It requires a free API key from
https://financialmodelingprep.com/
Commands
--------
.. cmdoption:: .djia
Display the DJIA index
.. cmdoption:: .nasdaq
Display the NASDAQ index
Config
------
.. code-block:: json
{
"apikey": "xxxxxxxxxxxxx",
"cache_update_interval": 600,
"cache_update_timeout": 10,
"warning_thresh": 1800
}
.. cmdoption:: apikey
API ley obtained from https://financialmodelingprep.com/
.. cmdoption:: cache_update_interval
How many seconds between fetching new index quotes from the API.
.. cmdoption:: cache_update_timeout
Maximum seconds to wait on the HTTP request sent to the API
.. cmdoption:: warning_thresh
A warning will be shown that the quote is out-of-date if the last successful fetch was longer ago than this
setting's number of seconds.
Class Reference
---------------
.. automodule:: pyircbot.modules.StockIndex
:members:
:undoc-members:
:show-inheritance:

View File

@ -9,7 +9,7 @@ from msgbus.client import MsgbusSubClient
import pyircbot
import traceback
from pyircbot.pyircbot import PrimitiveBot
from pyircbot.irccore import IRCEvent, UserPrefix
from pyircbot.irccore import IRCEvent, UserPrefix, IRCCore
from pyircbot.common import TouchReload
from json import dumps
@ -57,12 +57,10 @@ class PyIRCBotSub(PrimitiveBot):
args, sender, trailing, extras = loads(rest)
nick, username, hostname = extras["prefix"]
msg = IRCEvent(command.upper(),
args,
UserPrefix(nick,
username,
hostname),
trailing)
msg = IRCCore.packetAsObject(command.upper(),
args,
f"{nick}!{username}@{hostname}", # hack
trailing)
for module_name, module in self.moduleInstances.items():
for hook in module.irchooks:

View File

@ -18,7 +18,7 @@ from io import StringIO
from time import time
IRCEvent = namedtuple("IRCEvent", "command args prefix trailing")
IRCEvent = namedtuple("IRCEvent", "command args prefix trailing replyto")
UserPrefix = namedtuple("UserPrefix", "nick username hostname")
ServerPrefix = namedtuple("ServerPrefix", "hostname")
@ -258,6 +258,7 @@ class IRCCore(object):
self.log.warning("Invalid hook - %s" % command)
return False
@staticmethod
def packetAsObject(command, args, prefix, trailing):
"""Given an irc message's args, prefix, and trailing data return an object with these properties
@ -269,9 +270,15 @@ class IRCCore(object):
:type trailing: str
:returns: object -- a IRCEvent object with the ``args``, ``prefix``, ``trailing``"""
return IRCEvent(command, args,
IRCCore.decodePrefix(prefix) if prefix else None,
trailing)
prefix = IRCCore.decodePrefix(prefix) if prefix else None
replyto = None
if command == "PRIVMSG":
# prefix will always be set for PRIVMSG
# TODO server side fuzzing
replyto = args[0] if args[0].startswith("#") else prefix.nick
return IRCEvent(command, args, prefix, trailing, replyto)
" Utility methods "
@staticmethod

View File

@ -0,0 +1,81 @@
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
from threading import Thread
from time import sleep, time
import requests
import traceback
API_URL = "https://financialmodelingprep.com/api/v3/quotes/index?apikey={apikey}"
def bits(is_gain):
if is_gain:
return ("\x0303", "⬆", "+", )
return ("\x0304", "⬇", "-", )
class StockIndex(ModuleBase):
def __init__(self, bot, moduleName):
super().__init__(bot, moduleName)
self.session = requests.session()
self.updater = None
self.running = True
self.last_update = 0
self.start_cache_updater()
def start_cache_updater(self):
self.updater = Thread(target=self.cache_updater, daemon=True)
self.updater.start()
def ondisable(self):
self.running = False
self.updater.join()
def cache_updater(self):
while self.running:
try:
self.update_cache()
except:
traceback.print_exc()
delay = self.config.get("cache_update_interval", 600)
while self.running and delay > 0:
delay -= 1
sleep(1)
def update_cache(self):
data = self.session.get(API_URL.format(**self.config),
timeout=self.config.get("cache_update_timeout", 10)).json()
self.cache = {item["symbol"]: item for item in data}
self.last_update = time()
@info("djia", "get the current value of the DJIA", cmds=["djia"])
@command("djia", allow_private=True)
def cmd_djia(self, message, command):
self.send_quote("^DJI", "DJIA", message.replyto)
@info("nasdaq", "get the current value of the NASDAQ composite index", cmds=["nasdaq"])
@command("nasdaq", allow_private=True)
def cmd_nasdaq(self, message, command):
self.send_quote("^IXIC", "NASDAQ", message.replyto)
def send_quote(self, key, symbol, to):
index = self.cache[key]
is_gain = index["price"] >= index["previousClose"]
color, arrow, plusmin = bits(is_gain)
change = float(index["price"]) - float(index["previousClose"])
percentchange = float(change) / float(index["previousClose"]) * 100
warn_thresh = self.config.get("warning_thresh", 1800)
warning = "" if time() - self.last_update < warn_thresh else " \x030(quote is out-of-date)"
self.bot.act_PRIVMSG(to, "{}{} ${:.2f} {}{:.2f} ({:.2f}%){}{}".format(color,
symbol,
index["price"],
plusmin,
change,
percentchange,
arrow,
warning))

View File

@ -5,7 +5,7 @@ from threading import Thread
from random import randint
from pyircbot import PyIRCBot
from pyircbot.pyircbot import PrimitiveBot
from pyircbot.irccore import IRCEvent, UserPrefix
from pyircbot.irccore import IRCEvent, UserPrefix, IRCCore
from unittest.mock import MagicMock
from tests.miniircd import Server as MiniIrcServer
@ -27,10 +27,10 @@ class FakeBaseBot(PrimitiveBot):
"""
Feed a message into the bot.
"""
msg = IRCEvent(cmd,
args,
UserPrefix(*sender),
trailing)
msg = IRCCore.packetAsObject(cmd,
args,
f"{sender[0]}!{sender[1]}@{sender[2]}", # hack
trailing)
for module_name, module in self.moduleInstances.items():# TODO dedupe this block across the various base classes
for hook in module.irchooks: