├── examples └── echo │ ├── echo.js │ └── package.json ├── package.json ├── README.md ├── LICENSE └── davecast.js /examples/echo/echo.js: -------------------------------------------------------------------------------- 1 | const davecast = require ("davecast"); 2 | const config = { 3 | incomingMessageCallback: function (jstruct) { 4 | console.log ("davecast message: " + JSON.stringify (jstruct, undefined, 4)); 5 | } 6 | }; 7 | davecast.start (config); 8 | -------------------------------------------------------------------------------- /examples/echo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "Barebones test case for the davecast package.", 4 | "author": "Dave Winer ", 5 | "version": "0.4.0", 6 | "dependencies" : { 7 | "davecast": "*" 8 | }, 9 | "license": "MIT", 10 | "engines": { 11 | "node": "*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "davecast", 3 | "description": "Glue that connects JavaScript apps to the davecast server.", 4 | "author": "Dave Winer ", 5 | "license": "MIT", 6 | "version": "0.4.10", 7 | "main": "davecast.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/scripting/cast.git" 11 | }, 12 | "files": [ 13 | "davecast.js" 14 | ], 15 | "dependencies" : { 16 | "nodejs-websocket": "*", 17 | "request": "*", 18 | "daveutils": "*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## davecast package 2 | 3 | ### How to install 4 | 5 | `npm install davecast` 6 | 7 | ### Story 8 | 9 | davecast is a websockets-based way to get instant notification of messages that emanate from my blog, linkblog, photos, cards and Frontier updates. On the backend is a customized River5 server. 10 | 11 | Until now this flow was only available through Slack. But now it's open to everyone with a Node.js installation through the davecast package. And it might be a prototype for other people's casts. 12 | 13 | ### Hello world 14 | 15 | There's a hello world app in the examples folder. 16 | 17 | ### Updates 18 | 19 | #### v0.4.0 -- 7/21/17 by DW 20 | 21 | First release. 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dave Winer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /davecast.js: -------------------------------------------------------------------------------- 1 | var myProductName = "davecast", myVersion = "0.4.10"; 2 | 3 | /* The MIT License (MIT) 4 | Copyright (c) 2014-2017 Dave Winer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | const utils = require ("daveutils"); 26 | const websocket = require ("nodejs-websocket"); 27 | const request = require ("request"); 28 | const fs = require ("fs"); 29 | 30 | exports.start = startup; 31 | exports.getIconForMessage = getIconForMessage; 32 | exports.getTimeline = function () { 33 | return (timeline); 34 | }; 35 | exports.getFeedlist = function () { 36 | return (feedlist); 37 | } 38 | exports.getConfig = function () { 39 | return (config); 40 | } 41 | exports.simulateMessage = handleIncomingMessage; 42 | 43 | var mySocket = undefined; 44 | var feedlist = new Array (); 45 | const maxTimeline = 100; 46 | var timeline = new Array (); 47 | 48 | var config = { 49 | urlHttpServer: "http://davecast.org/", 50 | urlWebsocketsServer: "ws://davecast.org:5381/", 51 | 52 | flSaveData: true, //if true we keep the timeline, feedlist and each message in the data folder. 53 | dataFolder: "data/", 54 | messagesFolder: "messages/", 55 | iconsFolder: "icons/", 56 | fnameTimeline: "timeline.json", 57 | fnameFeedlist: "feedlist.json", 58 | 59 | incomingMessageCallback: undefined 60 | }; 61 | 62 | function httpReadUrl (url, callback) { 63 | request (url, function (error, response, data) { 64 | if (!error && (response.statusCode == 200)) { 65 | callback (data) 66 | } 67 | else { 68 | callback (undefined); 69 | } 70 | }); 71 | } 72 | function downloadBigFile (url, f, pubDate, callback) { //4/17/17 by DW 73 | utils.sureFilePath (f, function () { 74 | var theStream = fs.createWriteStream (f); 75 | theStream.on ("finish", function () { 76 | if (pubDate === undefined) { 77 | pubDate = new Date (); 78 | } 79 | else { 80 | pubDate = new Date (pubDate); 81 | } 82 | fs.utimes (f, pubDate, pubDate, function () { 83 | }); 84 | if (callback !== undefined) { 85 | callback (); 86 | } 87 | }); 88 | request.get (url) 89 | .on ('error', function (err) { 90 | console.log (err); 91 | }) 92 | .pipe (theStream); 93 | }); 94 | } 95 | function downloadFeedlistIcons () { 96 | if (feedlist !== undefined) { 97 | function downloadOne (ixfeedlist) { 98 | var url = feedlist [ixfeedlist].urlIcon; 99 | var ext = utils.stringLower (utils.stringLastField (url, ".")); 100 | var f = config.dataFolder + config.iconsFolder + utils.padWithZeros (ixfeedlist, 2) + "." + ext; 101 | feedlist [ixfeedlist].f = f; //for later use 102 | downloadBigFile (url, f); 103 | } 104 | for (var i = 0; i < feedlist.length; i++) { 105 | downloadOne (i); 106 | } 107 | } 108 | } 109 | function getListFromServer (callback) { 110 | var apiUrl = config.urlHttpServer + "davecast/feeds"; 111 | httpReadUrl (apiUrl, function (jsontext) { 112 | var jstruct = undefined; 113 | try { 114 | jstruct = JSON.parse (jsontext); 115 | } 116 | catch (err) { 117 | console.log ("getListFromServer: err.message == " + err.message); 118 | } 119 | feedlist = jstruct; 120 | saveFeedlist (); //only saves if enabled 121 | if (callback !== undefined) { 122 | callback (jstruct); 123 | } 124 | downloadFeedlistIcons (); 125 | }); 126 | } 127 | function getTimelineFromServer (callback) { 128 | var apiUrl = config.urlHttpServer + "davecast/timeline"; 129 | httpReadUrl (apiUrl, function (jsontext) { 130 | var jstruct = undefined; 131 | try { 132 | jstruct = JSON.parse (jsontext); 133 | } 134 | catch (err) { 135 | console.log ("getTimelineFromServer: err.message == " + err.message); 136 | } 137 | timeline = jstruct; 138 | saveTimeline (); 139 | if (callback !== undefined) { 140 | callback (jstruct); 141 | } 142 | }); 143 | } 144 | function consoleStatusMsg (s) { 145 | var theMessage = "\n" + myProductName + " v" + myVersion + ": "; 146 | if (s !== undefined) { 147 | theMessage += s 148 | } 149 | console.log (theMessage + "."); 150 | } 151 | function saveMessage (jstruct, callback) { 152 | if (config.flSaveData) { 153 | var f = config.dataFolder + config.messagesFolder + utils.getDatePath (jstruct.when) + jstruct.id + ".json"; 154 | var jsontext = utils.jsonStringify (jstruct); 155 | utils.sureFilePath (f, function () { 156 | fs.writeFile (f, jsontext, function (err) { 157 | if (err) { 158 | console.log ("saveMessage: err.message == " + err.message); 159 | } 160 | if (callback !== undefined) { 161 | callback (); 162 | } 163 | }); 164 | }); 165 | } 166 | else { 167 | if (callback !== undefined) { 168 | callback (); 169 | } 170 | } 171 | } 172 | function saveTimeline (callback) { 173 | if (config.flSaveData) { 174 | var f = config.dataFolder + config.fnameTimeline; 175 | utils.sureFilePath (f, function () { 176 | fs.writeFile (f, utils.jsonStringify (timeline), function (err) { 177 | if (err) { 178 | console.log ("saveTimeline: err.message == " + err.message); 179 | } 180 | if (callback !== undefined) { 181 | callback (); 182 | } 183 | }); 184 | }); 185 | } 186 | else { 187 | if (callback !== undefined) { 188 | callback (); 189 | } 190 | } 191 | } 192 | function saveFeedlist (callback) { 193 | if (config.flSaveData) { 194 | var f = config.dataFolder + config.fnameFeedlist; 195 | utils.sureFilePath (f, function () { 196 | fs.writeFile (f, utils.jsonStringify (feedlist), function (err) { 197 | if (err) { 198 | console.log ("saveFeedlist: err.message == " + err.message); 199 | } 200 | if (callback !== undefined) { 201 | callback (); 202 | } 203 | }); 204 | }); 205 | } 206 | else { 207 | if (callback !== undefined) { 208 | callback (); 209 | } 210 | } 211 | } 212 | function getIconForMessage (jstruct) { 213 | for (var i = 0; i < feedlist.length; i++) { 214 | var item = feedlist [i]; 215 | if (item.urlFeed == jstruct.feedUrl) { 216 | return (item.f); 217 | } 218 | } 219 | return (undefined); 220 | } 221 | function handleIncomingMessage (jstruct) { 222 | timeline.unshift (jstruct); 223 | while (timeline.length > maxTimeline) { 224 | timeline.pop (); 225 | } 226 | saveTimeline (); 227 | saveMessage (jstruct); 228 | if (config.incomingMessageCallback !== undefined) { 229 | config.incomingMessageCallback (jstruct); 230 | } 231 | } 232 | 233 | function startup (userConfig, callback) { 234 | var flEveryMinuteScheduled = false; 235 | 236 | function startWebSocketClient (s) { 237 | mySocket = websocket.connect (config.urlWebsocketsServer); 238 | mySocket.on ("connect", function () { 239 | consoleStatusMsg ("connection opened with " + config.urlWebsocketsServer); 240 | mySocket.send (s); 241 | }); 242 | mySocket.on ("text", function (eventData) { 243 | if (eventData !== undefined) { //no error 244 | var words = eventData.split (" "); 245 | if (words.length >= 2) { 246 | switch (words [0]) { 247 | case "updated": 248 | var listname = utils.trimWhitespace (words [1]); 249 | if (listname == "feeds.json") { 250 | getListFromServer (); 251 | } 252 | break; 253 | case "readfeed": 254 | var urlfeed = utils.trimWhitespace (words [1]); 255 | break; 256 | case "item": 257 | var jsontext = utils.stringDelete (eventData, 1, 5); //pop off "item " 258 | var jstruct = JSON.parse (jsontext); 259 | handleIncomingMessage (jstruct); 260 | break; 261 | default: 262 | break; 263 | } 264 | } 265 | } 266 | }); 267 | mySocket.on ("close", function (code, reason) { 268 | mySocket = undefined; 269 | }); 270 | mySocket.on ("error", function (err) { 271 | }); 272 | } 273 | function everyMinute () { 274 | consoleStatusMsg (new Date ().toLocaleTimeString ()); 275 | } 276 | function everySecond () { 277 | var now = new Date (); 278 | if (mySocket === undefined) { //try to open the connection 279 | startWebSocketClient ("hello world"); 280 | } 281 | if (!flEveryMinuteScheduled) { 282 | if (now.getSeconds () == 0) { 283 | flEveryMinuteScheduled = true; 284 | setInterval (everyMinute, 60000); 285 | everyMinute (); //do one right now 286 | } 287 | } 288 | } 289 | 290 | if (userConfig !== undefined) { 291 | for (x in userConfig) { 292 | config [x] = userConfig [x]; 293 | } 294 | 295 | } 296 | 297 | getListFromServer (function () { 298 | getTimelineFromServer (function () { 299 | setInterval (everySecond, 1000); 300 | if (callback !== undefined) { //initialiation is complete 301 | callback (); 302 | } 303 | }); 304 | }); 305 | } 306 | --------------------------------------------------------------------------------