Skip to content
Permalink
Browse files

add HTTP client for JS plugins, Pushover plugin

  • Loading branch information...
myfreeweb committed Jun 25, 2016
1 parent c47ad86 commit 701908e3325f794f1fba6fcb5cf165f84cbaf76b
@@ -157,9 +157,9 @@ $ http -f post localhost:3000/micropub "Authorization: Bearer $(cat token)" h=en
- [x] call from events
- [x] post category decisions
- [x] config getting
- [ ] HTTP request sending
- [x] HTTP request sending
- [x] example plugin: Pushover notifications
- [ ] HTTP request (webhook) handling
- [ ] example plugin: Pushover notifications
- [ ] example plugin: Telegram bot (posting, webmention notifications, responding to them, deleting them, etc.)
- webmention ([YAY W3C DRAFT](http://webmention.net/draft/)!)
- [ ] moderation tools
@@ -0,0 +1,66 @@
// Pushover plugin for Sweetroll
// Note: only checks top level replies, i.e. does not notify for salmentions (nested replies)

var TOKEN = 'REDACTED -- USE YOUR OWN APP KEY'
var USER = 'REDACTED -- USE YOUR OWN USER KEY'

function presentContent (obj, fallback) {
return _.truncate(_.get(obj, 'properties.name[0]',
_.get(obj, 'properties.summary[0].value',
_.get(obj, 'properties.summary[0].html',
_.get(obj, 'properties.summary[0]',
_.get(obj, 'properties.content[0].value',
_.get(obj, 'properties.content[0].html',
_.get(obj, 'properties.content[0]',
fallback))))))), { length: 140 })
}

function propMentionsUrl (obj, prop, url) {
return _.some(_.get(obj, 'properties.' + prop, []), function (v) {
if (_.isString(v))
return _.isEqual(v, url)
return _.some(_.get(v, 'properties.url', []), _.partial(_.isEqual, url))
})
}

function presentMention (postUrl, obj) {
var result = '<b><a href="' + _.get(obj, 'properties.url[0]') + '">' + _.get(obj, 'properties.author[0].name[0]', _.get(obj, 'properties.author[0].value')) + '</a></b>'
if (propMentionsUrl(obj, 'like-of', postUrl))
result += ' <i>liked this</i> '
else if (propMentionsUrl(obj, 'repost-of', postUrl))
result += ' <i>reposted this</i> '
else if (propMentionsUrl(obj, 'bookmark-of', postUrl))
result += ' <i>bookmarked this</i> '
else if (propMentionsUrl(obj, 'quotation-of', postUrl))
result += ' <i>quoted this and said</i>: ' + presentContent(obj, '//no content//')
else
result += ': ' + presentContent(obj, '//no content//')
return result
}

Sweetroll.addEventListener('update', function (event) {
var oldObjMentions = _.get(event, 'oldObj.properties.comment', [])
var newObjMentions = _.get(event, 'obj.properties.comment', [])
var newMentions = _.filter(newObjMentions, function (cmt) {
var found = _.find(oldObjMentions, _.matches(cmt))
if (!found) return true
// If it's an update, only notify if the content has changed -- don't bother with, like, the author's avatar change
return !_.isEqual(_.get(found, 'properties.content'), _.get(cmt, 'properties.content'))
})
if (_.size(newMentions) < 1) {
print("Pushover Plugin: no new/updated mentions for " + event.url)
} else {
print("Pushover Plugin: " + _.size(newMentions) + " new/updated mention(s) for " + event.url + "!")
var resp = Sweetroll.fetch("https://api.pushover.net/1/messages.json", {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' },
body: 'token=' + encodeURIComponent(TOKEN) +
'&user=' + encodeURIComponent(USER) +
'&title=' + encodeURIComponent('New mention' + (_.size(newMentions) == 1 ? '' : 's') + ' on: ' + presentContent(event.obj, event.url)) +
'&message=' + encodeURIComponent(_.join(_.map(newMentions, _.partial(presentMention, event.url)), '\n\n')) +
'&url=' + encodeURIComponent(event.url) +
'&html=1'
})
print("Pushover Plugin: " + _.size(newMentions) + " new/updated mention(s) for " + event.url + " -- result: " + resp.status + ": " + resp.body)
}
})
@@ -34,6 +34,8 @@ main port = do
_ newStore ref
return ()

-- XXX: move to higher level library https://hackage.haskell.org/package/rapid

update Int IO ()
update port = do
m lookupStore 0
@@ -16,6 +16,7 @@ import qualified Data.Conduit.Combinators as C
import qualified Data.Vector as V
import qualified Data.HashMap.Strict as HMS
import Data.HashMap.Strict (adjust)
import qualified Data.CaseInsensitive as CI
import Data.Microformats2.Parser
import Data.IndieWeb.MicroformatsUtil
import Data.IndieWeb.SiloToMicroformats
@@ -103,3 +104,16 @@ fetchAllReferenceContexts = fetchReferenceContexts "in-reply-to"
>=> fetchReferenceContexts "like-of"
>=> fetchReferenceContexts "repost-of"
>=> fetchReferenceContexts "quotation-of"

jsonFetch Manager Value Value IO Value
jsonFetch mgr (String uri) rdata = do
let setData req = return $ req { method = fromMaybe "GET" $ cs . toUpper <$> rdata ^? key "method" . _String
, requestHeaders = fromMaybe [] $ map (bimap (CI.mk . cs) (cs . fromMaybe "" . (^? _String))) . HMS.toList <$> rdata ^? key "headers" . _Object
, requestBody = RequestBodyBS $ fromMaybe "" $ cs <$> rdata ^? key "body" . _String }
r runReaderT (runHTTP $ reqS uri >>= anyStatus >>= setData >>= performWithBytes) mgr
return $ case r of
Left errmsg object [ "error" .= errmsg ]
Right res object [ "status" .= statusCode (responseStatus res)
, "headers" .= object (map (\(k, v) (asText $ cs $ CI.foldedCase k) .= (String $ cs v)) $ responseHeaders res)
, "body" .= String (cs $ responseBody res) ]
jsonFetch _ _ _ = return $ object [ "error" .= String "The URI must be a string" ]
@@ -10,6 +10,7 @@
module Sweetroll.Monads where

import Sweetroll.Prelude
import Sweetroll.HTTPClient (jsonFetch)
import Control.Monad.Base
import Control.Monad.Reader hiding (forM_)
import System.Process (readProcessWithExitCode)
@@ -66,7 +67,7 @@ initCtx conf secs = do
-- static pool, basically: max Ncpus, don't expire
tplPool createPool createTemplateCtx (\_ return ()) 1 999999999999 cpus
(_, deleted', _) readProcessWithExitCode "git" [ "log", "--all", "--diff-filter=D", "--find-renames", "--name-only", "--pretty=format:" ] ""
plugCtx createPluginsCtx conf
plugCtx createPluginsCtx conf hmg
deleted newTVarIO $ lines deleted'
return SweetrollCtx { _ctxConf = conf
, _ctxSecs = secs
@@ -98,14 +99,15 @@ createTemplateCtx = do
forFileIn "templates" (sortOn notJs . filter (not . ("." `isPrefixOf`))) setTpl
return duk

createPluginsCtx SweetrollConf IO DuktapeCtx
createPluginsCtx conf = do
createPluginsCtx SweetrollConf Manager IO DuktapeCtx
createPluginsCtx conf hmg = do
duk fromJust <$> createDuktapeCtx
void $ evalDuktape duk "var window = this"
void $ evalDuktape duk $(embedFile "bower_components/lodash/dist/lodash.min.js")
void $ evalDuktape duk $(embedFile "bower_components/moment/min/moment-with-locales.min.js")
void $ evalDuktape duk $(embedFile "library/Sweetroll/PluginApi.js")
void $ callDuktape duk (Just "Sweetroll") "_setConf" [ toJSON conf ]
void $ exposeFnDuktape duk (Just "Sweetroll") "fetch" $ jsonFetch hmg
forFileIn "plugins" (filter (not . ("." `isPrefixOf`))) (\_ x void $ evalDuktape duk x)
return duk

@@ -35,14 +35,16 @@ Sweetroll._fireEvent = function (name, data) {
}


/* --- Public API. called by plugins --- */
/* --- Public API. called by plugins ---
* Defined in Haskell:
* * Sweetroll.fetch -- HTTP request */

Sweetroll.addCategoryDecider = function (d) {
if (_.isFunction(d))
Sweetroll._categoryDeciders.push(d)
}

Sweetroll.addEventListener = function(e, l) {
Sweetroll.addEventListener = function (e, l) {
if (_.isFunction(l)) {
Sweetroll._eventListeners[e] = Sweetroll._eventListeners[e] || []
Sweetroll._eventListeners[e].push(l)
@@ -48,6 +48,7 @@ library
, data-default
, string-conversions
, bytestring-conversion
, case-insensitive
, text
, bytestring
, blaze-markup

0 comments on commit 701908e

Please sign in to comment.
You can’t perform that action at this time.