parent
a54a61d576
commit
ce99d7416c
4 changed files with 430 additions and 0 deletions
66
.gitignore
vendored
Normal file
66
.gitignore
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# my other cruft
|
||||||
|
*.yaml
|
||||||
|
*.db
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
.vscode/
|
21
database.py
Normal file
21
database.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
with open("config.yaml", "rb") as config_file:
|
||||||
|
config = yaml.load(config_file)
|
||||||
|
|
||||||
|
engine = create_engine(config['SERVICE_DB'])
|
||||||
|
db_session = scoped_session(sessionmaker(bind=engine))
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
Base.query = db_session.query_property()
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
# import all modules here that might define models so that
|
||||||
|
# they will be registered properly on the metadata. Otherwise
|
||||||
|
# you will have to import them first before calling init_db()
|
||||||
|
import models
|
||||||
|
Base.metadata.create_all(bind=engine)
|
25
models.py
Normal file
25
models.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||||
|
from database import Base
|
||||||
|
|
||||||
|
class Optout(Base):
|
||||||
|
__tablename__ = 'optout'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
userid = Column(Integer, unique=True)
|
||||||
|
|
||||||
|
class Karma(Base):
|
||||||
|
__tablename__ = 'karma'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
userid = Column(Integer)
|
||||||
|
chanid = Column(Integer)
|
||||||
|
karma = Column(Integer)
|
||||||
|
|
||||||
|
class Queue(Base):
|
||||||
|
__tablename__ = 'queue'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
msg = Column(String(2048))
|
||||||
|
|
||||||
|
class Preferences(Base):
|
||||||
|
__tablename__ = 'preferences'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
userid = Column(Integer, unique=True)
|
||||||
|
chimpnut = Column(Boolean)
|
318
partybot-pnut.py
Normal file
318
partybot-pnut.py
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
import yaml
|
||||||
|
import requests
|
||||||
|
import pnutpy
|
||||||
|
import websocket
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
from database import db_session, init_db
|
||||||
|
from sqlalchemy import and_
|
||||||
|
from models import Karma, Optout, Queue, Preferences
|
||||||
|
|
||||||
|
_shutdown = threading.Event()
|
||||||
|
_connected = threading.Event()
|
||||||
|
|
||||||
|
def subscribe(connection_id):
|
||||||
|
url = f"https://api.pnut.io/v0/channels/{config['CHANNEL']}/messages"
|
||||||
|
url += "?connection_id=" + connection_id
|
||||||
|
headers = {'Authorization': "Bearer " + config['ACCESS_TOKEN']}
|
||||||
|
r = requests.get(url, headers=headers)
|
||||||
|
if r.status_code == 200:
|
||||||
|
_connected.set()
|
||||||
|
send(config['CHANNEL'], "Partybot online and ready! \o/")
|
||||||
|
else:
|
||||||
|
logger.error(r)
|
||||||
|
|
||||||
|
def send(room, text):
|
||||||
|
pnutpy.api.create_message(room, data={'text': text})
|
||||||
|
|
||||||
|
def echo(room, text):
|
||||||
|
logger.debug(text)
|
||||||
|
send(room, text)
|
||||||
|
|
||||||
|
def help(room):
|
||||||
|
reply = "You can upvote or downvote participants by mentioning them with the following symbols\n\n"
|
||||||
|
reply += " to vote up: +1, ++, \U0001F44D \n"
|
||||||
|
reply += " to vote down: -1, --, \U0001F44E \n"
|
||||||
|
reply += "\n"
|
||||||
|
reply += "You can use the following commands\n\n"
|
||||||
|
reply += "!karma -- show the current karma ratings\n"
|
||||||
|
reply += "!optout -- remove yourself from karma ratings\n"
|
||||||
|
reply += "!optin -- add yourself to karma ratings\n"
|
||||||
|
reply += "!chimpnut -- toggle special ChimPnut alerts\n"
|
||||||
|
reply += "!botsnack -- give @partybot a special treat\n"
|
||||||
|
send(room, reply)
|
||||||
|
|
||||||
|
def botsnack(room):
|
||||||
|
replies = [
|
||||||
|
"Nom nom nom.",
|
||||||
|
"Ooooh",
|
||||||
|
"Yummi!",
|
||||||
|
"Delightful.",
|
||||||
|
"That makes me happy",
|
||||||
|
"How kind!",
|
||||||
|
"Sweet.",
|
||||||
|
"*burp*",
|
||||||
|
]
|
||||||
|
send(room, random.choice(replies))
|
||||||
|
|
||||||
|
def botdrink(room):
|
||||||
|
replies = [
|
||||||
|
"Hold my beer.",
|
||||||
|
"Ooooh",
|
||||||
|
"Delightful.",
|
||||||
|
"That makes me happy",
|
||||||
|
"How kind!",
|
||||||
|
"Sweet.",
|
||||||
|
"*burp*",
|
||||||
|
"Ah.. Hiccup!"
|
||||||
|
]
|
||||||
|
send(room, random.choice(replies))
|
||||||
|
|
||||||
|
def optout(msg):
|
||||||
|
karma = Karma.query.filter(Karma.userid == msg.user.id).one_or_none()
|
||||||
|
if karma:
|
||||||
|
db_session.delete(karma)
|
||||||
|
entry = Optout.query.filter(Optout.userid == msg.user.id).one_or_none()
|
||||||
|
if entry is None:
|
||||||
|
entry = Optout(userid=msg.user.id)
|
||||||
|
db_session.add(entry)
|
||||||
|
db_session.commit()
|
||||||
|
reply = "@" + msg.user.username
|
||||||
|
reply += " you have been removed from the karma table"
|
||||||
|
send(msg.channel_id, reply)
|
||||||
|
|
||||||
|
def optin(msg):
|
||||||
|
entry = Optout.query.filter(Optout.userid == msg.user.id).one_or_none()
|
||||||
|
if entry:
|
||||||
|
db_session.delete(entry)
|
||||||
|
reply = "@" + msg.user.username
|
||||||
|
reply += " you are able to earn karma"
|
||||||
|
send(msg.channel_id, reply)
|
||||||
|
|
||||||
|
def upvote(room, user, prefs):
|
||||||
|
karma = Karma.query.filter(Karma.userid == user.id).one_or_none()
|
||||||
|
if karma is None:
|
||||||
|
karma = Karma(userid=user.id, chanid=room, karma=1)
|
||||||
|
db_session.add(karma)
|
||||||
|
else:
|
||||||
|
karma.karma = karma.karma + 1
|
||||||
|
db_session.commit()
|
||||||
|
if prefs.chimpnut:
|
||||||
|
prefix = "/karma "
|
||||||
|
else:
|
||||||
|
prefix = ""
|
||||||
|
reply = prefix + "@" + user.username
|
||||||
|
reply += " now has " + str(karma.karma) + " karma in this channel"
|
||||||
|
send(room, reply)
|
||||||
|
|
||||||
|
def downvote(room, user, prefs):
|
||||||
|
karma = Karma.query.filter(Karma.userid == user.id).one_or_none()
|
||||||
|
if karma is None:
|
||||||
|
karma = Karma(userid=user.id, chanid=room, karma=-1)
|
||||||
|
db_session.add(karma)
|
||||||
|
else:
|
||||||
|
karma.karma = karma.karma - 1
|
||||||
|
db_session.commit()
|
||||||
|
if prefs.chimpnut:
|
||||||
|
prefix = "/karma "
|
||||||
|
else:
|
||||||
|
prefix = ""
|
||||||
|
reply = prefix + "@" + user.username
|
||||||
|
reply += " now has " + str(karma.karma) + " karma in this channel"
|
||||||
|
send(room, reply)
|
||||||
|
|
||||||
|
def karma(room):
|
||||||
|
reply = "Karma standings\n\n"
|
||||||
|
results = Karma.query.filter(Karma.chanid == room).order_by(Karma.karma.desc()).all()
|
||||||
|
for entry in results:
|
||||||
|
user, meta = pnutpy.api.get_user(entry.userid)
|
||||||
|
reply += user.username + ": " + str(entry.karma) + "\n"
|
||||||
|
send(room, reply)
|
||||||
|
|
||||||
|
def chimpnut(msg):
|
||||||
|
prefs = Preferences.query.filter(Preferences.userid == msg.user.id).one_or_none()
|
||||||
|
if prefs is None:
|
||||||
|
prefs = Preferences(userid=msg.user.id, chimpnut=False)
|
||||||
|
db_session.add(prefs)
|
||||||
|
|
||||||
|
if prefs.chimpnut:
|
||||||
|
prefs.chimpnut = False
|
||||||
|
reply = "@" + msg.user.username + " ChimPnut alert is now disabled"
|
||||||
|
else:
|
||||||
|
prefs.chimpnut = True
|
||||||
|
reply = "/Mac @" + msg.user.username + " ChimPnut alert is now enabled"
|
||||||
|
db_session.commit()
|
||||||
|
send(msg.channel_id, reply)
|
||||||
|
|
||||||
|
def on_command(msg):
|
||||||
|
room = msg.channel_id
|
||||||
|
args = msg.content.text.split(' ', 1)
|
||||||
|
|
||||||
|
if args[0] == "!help":
|
||||||
|
help(msg.channel_id)
|
||||||
|
|
||||||
|
elif args[0] == "!botsnack":
|
||||||
|
botsnack(msg.channel_id)
|
||||||
|
|
||||||
|
elif args[0] == "!botdrink":
|
||||||
|
botdrink(msg.channel_id)
|
||||||
|
|
||||||
|
elif args[0] == "!optout":
|
||||||
|
optout(msg)
|
||||||
|
|
||||||
|
elif args[0] == "!optin":
|
||||||
|
optin(msg)
|
||||||
|
|
||||||
|
elif args[0] == "!chimpnut":
|
||||||
|
chimpnut(msg)
|
||||||
|
|
||||||
|
elif args[0] == "!karma":
|
||||||
|
karma(msg.channel_id)
|
||||||
|
|
||||||
|
def on_vote(msg, matcher):
|
||||||
|
|
||||||
|
canidate = matcher.group(1)
|
||||||
|
vote = matcher.group(2)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pnutuser, meta = pnutpy.api.get_user("@" + canidate)
|
||||||
|
if msg.user.username == pnutuser.username:
|
||||||
|
logger.debug(pnutuser.username)
|
||||||
|
logger.debug(canidate)
|
||||||
|
reply = "@" + msg.user.username
|
||||||
|
reply += " silly human, your karma must be decided by others!"
|
||||||
|
send(msg.channel_id, reply)
|
||||||
|
return
|
||||||
|
|
||||||
|
except pnutpy.errors.PnutMissing:
|
||||||
|
reply = "@" + msg.user.username
|
||||||
|
reply += " I do not know who that is"
|
||||||
|
send(msg.channel_id, reply)
|
||||||
|
return
|
||||||
|
|
||||||
|
optout = Optout.query.filter(Optout.userid == pnutuser.id).one_or_none()
|
||||||
|
if optout:
|
||||||
|
reply = "@" + msg.user.username
|
||||||
|
reply += " user has chosen not to receive karma"
|
||||||
|
send(msg.channel_id, reply)
|
||||||
|
return
|
||||||
|
|
||||||
|
prefs = Preferences.query.filter(Preferences.userid == pnutuser.id).one_or_none()
|
||||||
|
if prefs is None:
|
||||||
|
prefs = Preferences(userid=pnutuser.id, chimpnut=True)
|
||||||
|
db_session.add(prefs)
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.debug("-- VOTING " + vote)
|
||||||
|
|
||||||
|
upvotes = [
|
||||||
|
"++",
|
||||||
|
":thumbsup:",
|
||||||
|
":+1:",
|
||||||
|
"+1",
|
||||||
|
"\U0001F44D"
|
||||||
|
]
|
||||||
|
downvotes = [
|
||||||
|
"--",
|
||||||
|
":thumbsdown:",
|
||||||
|
":-1:",
|
||||||
|
"-1",
|
||||||
|
"\U0001F44E"
|
||||||
|
]
|
||||||
|
if vote in upvotes:
|
||||||
|
upvote(msg.channel_id, pnutuser, prefs)
|
||||||
|
|
||||||
|
elif vote in downvotes:
|
||||||
|
downvote(msg.channel_id, pnutuser, prefs)
|
||||||
|
|
||||||
|
def on_mention(msg):
|
||||||
|
# TODO: use for even more magic
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_message(ws, message):
|
||||||
|
logger.debug("on_message: " + message)
|
||||||
|
msg = json.loads(message)
|
||||||
|
|
||||||
|
if not _connected.isSet() and 'connection_id' in msg['meta']:
|
||||||
|
send(config['CHANNEL'], "...connecting circuits...")
|
||||||
|
logger.debug("connection_id: " + msg['meta']['connection_id'])
|
||||||
|
subscribe(msg['meta']['connection_id'])
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'data' in msg:
|
||||||
|
|
||||||
|
if "channel_type" in msg['meta'] and msg['meta']['channel_type'] == "io.pnut.core.chat":
|
||||||
|
|
||||||
|
pmsg = pnutpy.models.Message.from_response_data(msg['data'])
|
||||||
|
|
||||||
|
if 'is_deleted' in msg['meta']:
|
||||||
|
return
|
||||||
|
|
||||||
|
vpattern = r"([\w]+)\s?(\-\-|\+\+|\U0001F44D|\U0001F44E|\:thumbsup\:|\:\+1\:|\:thumbsdown\:|\:-1\:|\+1|-1)"
|
||||||
|
votes = re.search(vpattern, pmsg.content.text)
|
||||||
|
|
||||||
|
if pmsg.user.username == config['USERNAME']:
|
||||||
|
return
|
||||||
|
|
||||||
|
if config['USERNAME'] in [e.text for e in pmsg.content.entities.mentions]:
|
||||||
|
on_mention(pmsg)
|
||||||
|
|
||||||
|
elif pmsg.content.text.startswith('!'):
|
||||||
|
on_command(pmsg)
|
||||||
|
|
||||||
|
elif votes:
|
||||||
|
on_vote(pmsg, votes)
|
||||||
|
|
||||||
|
def on_error(ws, error):
|
||||||
|
logger.error("on_error: !!! ERROR !!!")
|
||||||
|
logger.error(error)
|
||||||
|
_shutdown.set()
|
||||||
|
|
||||||
|
def on_close(ws):
|
||||||
|
send(config['CHANNEL'], "...shutdown initiated...")
|
||||||
|
logger.debug("on_close: ### CLOSED ###")
|
||||||
|
_shutdown.set()
|
||||||
|
|
||||||
|
def on_open(ws):
|
||||||
|
|
||||||
|
def run(*args):
|
||||||
|
while not _shutdown.isSet():
|
||||||
|
qmsg = Queue.query.one_or_none()
|
||||||
|
if qmsg:
|
||||||
|
send(config['CHANNEL'], qmsg.msg)
|
||||||
|
db_session.delete(qmsg)
|
||||||
|
db_session.commit()
|
||||||
|
time.sleep(3)
|
||||||
|
ws.send(".")
|
||||||
|
time.sleep(1)
|
||||||
|
ws.close()
|
||||||
|
logger.debug("*** terminate ***")
|
||||||
|
|
||||||
|
t = threading.Thread(target=run)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
with open("config.yaml", "rb") as config_file:
|
||||||
|
config = yaml.load(config_file)
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
|
||||||
|
pnutpy.api.add_authorization_token(config['ACCESS_TOKEN'])
|
||||||
|
|
||||||
|
ws_url = "wss://stream.pnut.io/v0/user"
|
||||||
|
ws_url += "?access_token=" + config['ACCESS_TOKEN']
|
||||||
|
|
||||||
|
# setup the websocket connection
|
||||||
|
ws = websocket.WebSocketApp(ws_url, on_message=on_message,
|
||||||
|
on_error=on_error, on_close=on_close)
|
||||||
|
ws.on_open = on_open
|
||||||
|
ws.run_forever()
|
Loading…
Reference in a new issue