Compare commits
2 Commits
f2c6668e18
...
9e3a7b68fe
Author | SHA1 | Date |
---|---|---|
dave | 9e3a7b68fe | |
dave | 235921fbab |
|
@ -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:
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
10
tests/lib.py
10
tests/lib.py
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue