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