weechatRN

Weechat relay client for iOS using websockets https://github.com/mhoran/weechatRN
git clone http://git.hanabi.in/repos/weechatRN.git
Log | Files | Refs | README | LICENSE

commit 5cce4b630f7de49bf0ec8bdafcabb8efa2c31502
parent aaa65c104eaf662c2baaaa3aedec33436c3557a3
Author: Matthew Horan <matt@matthoran.com>
Date:   Sun, 13 Oct 2019 20:03:44 -0400

First pass at push notification support

* Requires a script be loaded in WeeChat
* Will notify for any message in a PM or highlights in channels

Diffstat:
Ascripts/weechatrn.py | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/helpers/push-notifications.ts | 33+++++++++++++++++++++++++++++++++
Msrc/usecase/App.tsx | 3+++
Msrc/usecase/Root.tsx | 8++++++++
4 files changed, 117 insertions(+), 0 deletions(-)

diff --git a/scripts/weechatrn.py b/scripts/weechatrn.py @@ -0,0 +1,73 @@ +import weechat +import json + +weechat.register("WeechatRN", "mhoran", "1.0", "MIT", + "WeechatRN push notification plugin", "", "") + +# Plugin options +# /set plugins.var.python.weechatrn.push_token +script_options = { + "push_token": "" +} + +for option, default_value in script_options.items(): + if weechat.config_is_set_plugin(option): + script_options[option] = weechat.config_get_plugin(option) + else: + weechat.config_set_plugin(option, default_value) + +# Register a custom command so the relay can set the token if the relay is +# configured to blacklist certain commands (like /set). +def weechatrn_cb(data, buffer, args): + weechat.config_set_plugin("push_token", args) + return weechat.WEECHAT_RC_OK + +hook = weechat.hook_command("weechatrn", "", "", "", "", "weechatrn_cb", "") + +# Reset in-memory push token on config change. +def config_cb(data, option, value): + if option == "plugins.var.python.weechatrn.push_token": + script_options["push_token"] = value + return weechat.WEECHAT_RC_OK + +weechat.hook_config("plugins.var.python.weechatrn.*", "config_cb", "") + +# Only notify for PMs or highlights if message is not tagged with notify_none +# (ignores messages from ourselves). +def priv_msg_cb(data, buffer, date, tags, displayed, highlight, prefix, + message): + if "notify_none" in tags.split(","): + return weechat.WEECHAT_RC_OK + + body = u"<%s> %s" % (prefix, message) + is_pm = weechat.buffer_get_string(buffer, "localvar_type") == "private" + if is_pm: + send_push(title="Private message from %s" % prefix, body=body) + elif int(highlight) and weechat.current_buffer() != buffer: + buffer_name = (weechat.buffer_get_string(buffer, "short_name") or + weechat.buffer_get_string(buffer, "name")) + send_push(title="Highlight in %s" % buffer_name, body=body) + + return weechat.WEECHAT_RC_OK + +weechat.hook_print("", "irc_privmsg", "", 1, "priv_msg_cb", "") + +# Send push notification to Expo server. Message JSON encoded in the format: +# { "to": "EXPO_PUSH_TOKEN", +# "title": "Notification title", +# "body": "Notification body" } +def process_expo_cb(data, command, return_code, out, err): + return weechat.WEECHAT_RC_OK + +def send_push(title, body): + push_token = script_options["push_token"] + if push_token == "": + return + + post_body = { "to": push_token, "title": title, "body": body } + options = { + "httpheader": "Content-Type: application/json", + "postfields": json.dumps(post_body) } + weechat.hook_process_hashtable( + "url:https://exp.host/--/api/v2/push/send", + options, 20000, "process_expo_cb", "") diff --git a/src/lib/helpers/push-notifications.ts b/src/lib/helpers/push-notifications.ts @@ -0,0 +1,33 @@ +import { Notifications } from 'expo'; +import * as Permissions from 'expo-permissions'; + +export const registerForPushNotificationsAsync = async () => { + const { status: existingStatus } = await Permissions.getAsync( + Permissions.NOTIFICATIONS + ); + + // only ask if permissions have not already been determined, because + // iOS won't necessarily prompt the user a second time. + if (existingStatus !== 'granted') { + // Android remote notification permissions are granted during the app + // install, so this will only ask on iOS + await Permissions.askAsync(Permissions.NOTIFICATIONS); + } +} + +export const getPushNotificationStatusAsync = async () => { + const { status: existingStatus } = await Permissions.getAsync( + Permissions.NOTIFICATIONS + ); + let finalStatus = existingStatus; + + // Stop here if the user did not grant permissions + if (finalStatus !== 'granted') { + return; + } + + // Get the token that uniquely identifies this device + let token = await Notifications.getExpoPushTokenAsync(); + + return token; +} diff --git a/src/usecase/App.tsx b/src/usecase/App.tsx @@ -21,6 +21,7 @@ import BufferContainer from "./buffers/ui/BufferContainer"; import BufferList from "./buffers/ui/BufferList"; import { StoreState } from "../store"; import { renderWeechatFormat } from "../lib/weechat/color-formatter"; +import { registerForPushNotificationsAsync } from "../lib/helpers/push-notifications" interface Props { buffers: { [key: string]: WeechatBuffer }; @@ -109,6 +110,8 @@ class App extends React.Component<Props, State> { } else { this.drawer.openDrawer(); } + + registerForPushNotificationsAsync(); } componentWillUnmount() { diff --git a/src/usecase/Root.tsx b/src/usecase/Root.tsx @@ -8,6 +8,7 @@ import { store, persistor } from "../store"; import App from "./App"; import ConnectionGate from "./ConnectionGate"; +import { getPushNotificationStatusAsync } from "../lib/helpers/push-notifications" interface State { connecting: boolean; @@ -25,6 +26,12 @@ export default class WeechatNative extends React.Component<{}, State> { this.connection = new WeechatConnection(store.dispatch); } + setNotificationToken = async () => { + let token = await getPushNotificationStatusAsync(); + if (token) + this.sendMessageToBuffer("core.weechat", "/weechatrn " + token); + } + onConnectionSuccess = connection => { this.setState({ connecting: false }); connection.send("(hotlist) hdata hotlist:gui_hotlist(*)"); @@ -33,6 +40,7 @@ export default class WeechatNative extends React.Component<{}, State> { ); // connection.send("(nicklist) nicklist"); connection.send("sync"); + this.setNotificationToken(); }; onConnectionError = reconnect => {