mirror of https://github.com/ekimekim/wubloader
Implement twitch bot that notifies zulip on recieving a twitch DM
parent
e9b99a6774
commit
e12191686f
@ -0,0 +1,115 @@
|
||||
|
||||
import argh
|
||||
import yaml
|
||||
from mastodon import Mastodon
|
||||
|
||||
import zulip
|
||||
|
||||
cli = argh.EntryPoint()
|
||||
|
||||
|
||||
def get_config(conf_file):
|
||||
with open(conf_file) as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
def format_account(account):
|
||||
return f"**[{account['display_name']}]({account['url']})**"
|
||||
|
||||
|
||||
def format_status(status):
|
||||
sender = format_account(status["account"])
|
||||
url = status["url"]
|
||||
visibility = status["visibility"]
|
||||
reply = status["in_reply_to_id"]
|
||||
reblog = status["reblog"]
|
||||
|
||||
# private messages should not show content.
|
||||
if visibility not in ("public", "unlisted"):
|
||||
kind = "message"
|
||||
if reblog is not None:
|
||||
kind = "boost"
|
||||
if reply is not None:
|
||||
kind = "reply"
|
||||
return f"{sender} sent [a {visibility} {kind}]({url})"
|
||||
|
||||
if reblog is not None:
|
||||
boostee = format_account(reblog["account"])
|
||||
boost_url = reblog["url"]
|
||||
content = format_content(reblog["content"])
|
||||
return f"{sender} reblogged {boostee}'s [post]({boost_url})\n{content}"
|
||||
|
||||
return f"{"
|
||||
|
||||
|
||||
class Listener(Mastodon.StreamListener):
|
||||
def __init__(self, zulip_client, stream, post_topic, notification_topic):
|
||||
self.zulip_client = zulip_client
|
||||
self.stream = stream
|
||||
self.post_topic = post_topic
|
||||
self.notification_topic = notification_topic
|
||||
|
||||
def send(self, topic, content):
|
||||
logging.info(f"Sending message to {self.stream}/{topic}: {content!r}")
|
||||
self.zulip_client.send_to_stream(self.stream, topic, content)
|
||||
|
||||
def on_update(self, status):
|
||||
logging.info(f"Got update: {status!r}")
|
||||
self.send(self.post_topic, format_status(status))
|
||||
|
||||
def on_delete(self, status_id):
|
||||
logging.info(f"Got delete: {status_id}")
|
||||
self.send(self.post_topic, f"*Status with id {status_id} was deleted*")
|
||||
|
||||
def on_status_update(self, status):
|
||||
logging.info(f"Got status update: {status!r}")
|
||||
self.send(self.post_topic, f"*The following status has been updated*\n{format_status(status)}")
|
||||
|
||||
def on_notification(self, notification):
|
||||
logging.info(f"Got {notification['type']} notification: {notification!r}")
|
||||
if notification["type"] != "mention":
|
||||
return
|
||||
self.send(self.notification_topic, format_status(status))
|
||||
|
||||
|
||||
@cli
|
||||
def main(conf_file, stream="bot-spam", post_topic="Toots from Desert Bus", notification_topic="Mastodon Notifications"):
|
||||
"""
|
||||
Run the actual bot.
|
||||
|
||||
Config, in json or yaml format:
|
||||
zulip:
|
||||
url
|
||||
email
|
||||
api_key
|
||||
mastodon:
|
||||
url
|
||||
client_id # only required for get-access-token
|
||||
client_secret # only required for get-access-token
|
||||
access_token # only required for main
|
||||
"""
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
conf = get_config(conf_file)
|
||||
zc = conf["zulip"]
|
||||
mc = conf["mastodon"]
|
||||
|
||||
zulip_client = zulip.Client(zc["url"], zc["email"], zc["api_key"])
|
||||
mastodon = Mastodon(api_base_url=mc["url"], access_token=mc["access_token"])
|
||||
listener = Listener(zulip_client, stream, post_topic, notification_topic)
|
||||
|
||||
logging.info("Starting")
|
||||
mastodon.stream_user(listener)
|
||||
|
||||
|
||||
@cli
|
||||
def get_access_token(conf_file):
|
||||
"""Do OAuth login flow and obtain an access token."""
|
||||
mc = get_config(conf_file)["mastodon"]
|
||||
mastodon = Mastodon(client_id=mc["client_id"], client_secret=mc["client_secret"], api_base_url=mc["url"])
|
||||
print("Go to the following URL to obtain an access token:")
|
||||
print(mastodon.auth_request_url(scopes=["read:notifications", "read:statuses"]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
@ -0,0 +1,70 @@
|
||||
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
|
||||
import logging
|
||||
|
||||
import argh
|
||||
import girc
|
||||
import yaml
|
||||
|
||||
import zulip
|
||||
|
||||
|
||||
def run(zulip_client, nick, oauth_token, stream, topic):
|
||||
chat_client = girc.Client(
|
||||
hostname="irc.chat.twitch.tv",
|
||||
port=6697,
|
||||
ssl=True,
|
||||
nick=nick,
|
||||
password=oauth_token,
|
||||
twitch=True,
|
||||
)
|
||||
|
||||
@chat_client.handler() # handle all messages
|
||||
def log_message(chat_client, message):
|
||||
logging.info(f"Got message: {message}")
|
||||
|
||||
@chat_client.handler(command="WHISPER")
|
||||
def handle_whisper(chat_client, message):
|
||||
display_name = message.tags["display-name"]
|
||||
user = message.sender
|
||||
logging.info(f"Got whisper from {display_name!r} (username {user!r})")
|
||||
zulip_client.send_to_stream(stream, topic, f"**{nick}** received a Twitch DM from [{display_name}](https://twitch.tv/{user})")
|
||||
|
||||
chat_client.start()
|
||||
logging.info("Chat client connected")
|
||||
chat_client.wait_for_stop()
|
||||
logging.warning("Chat client disconnected")
|
||||
|
||||
|
||||
def main(conf_file, stream="bot-spam", topic="Twitch DMs", retry_interval=10):
|
||||
"""
|
||||
config, in json or yaml format:
|
||||
twitch_username
|
||||
twitch_token
|
||||
zulip_url
|
||||
zulip_email
|
||||
zulip_api_key
|
||||
"""
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
with open(conf_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
zulip_client = zulip.Client(config["zulip_url"], config["zulip_email"], config["zulip_api_key"])
|
||||
|
||||
while True:
|
||||
try:
|
||||
run(zulip_client, config["twitch_username"], config["twitch_oauth_token"], stream, topic)
|
||||
except Exception:
|
||||
logging.exception("Chat client failed")
|
||||
|
||||
# We might get here either from an error, or because client disconnected.
|
||||
# Either way, try to re-connect.
|
||||
logging.info(f"Retrying in {retry_interval} seconds")
|
||||
gevent.sleep(retry_interval)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
argh.dispatch_command(main)
|
Loading…
Reference in New Issue