// twister_newmsgs.js // 2013 Miguel Freitas // // Periodically check for new mentions and private messages (DMs) // Update UI counters in top bar. Load/save state to localStorage. // --- mentions --- var groupChatAliases = [] function saveMentionsToStorage() { var twists = [], length = 0; for (var j in twister.mentions.twists.cached) { for (var i = 0; i < length; i++) if (twister.mentions.twists.cached[j].userpost.time > twists[i].userpost.time) { twists.splice(i, 0, twister.mentions.twists.cached[j]); break; } if (length === twists.length) twists.push(twister.mentions.twists.cached[j]); length++; } $.initNamespaceStorage(defaultScreenName).localStorage .set('mentions', { twists: twists.slice(0, 100), // TODO add an option to specify number of mentions to cache lastTime: twister.mentions.lastTime, lastTorrentId: twister.mentions.lastTorrentId }) ; } function loadMentionsFromStorage() { var storage = $.initNamespaceStorage(defaultScreenName).localStorage; if (storage.isSet('mentions')) { var mentions = storage.get('mentions'); if (typeof mentions === 'object') { for (var i = 0; i < mentions.twists.length; i++) { var j = mentions.twists[i].userpost.n + '/' + mentions.twists[i].userpost.time; if (typeof twister.mentions.twists.cached[j] === 'undefined') { twister.mentions.twists.cached[j] = mentions.twists[i]; twister.mentions.lengthCached++; if (twister.mentions.twists.cached[j].isNew) twister.mentions.lengthNew++; twister.mentions.lengthFromTorrent++; } } twister.mentions.lastTime = mentions.lastTime; twister.mentions.lastTorrentId = mentions.lastTorrentId; } } // WARN all following storage keys are deprecated (see commit dc8cfc20ef10ff3008b4abfdb30d31e7fcbec0cd) if (storage.isSet('knownMentions')) { var mentions = storage.get('knownMentions'); if (typeof mentions === 'object') for (var i in mentions) { var j = mentions[i].data.userpost.n + '/' + mentions[i].mentionTime; if (typeof twister.mentions.twists.cached[j] === 'undefined') { twister.mentions.twists.cached[j] = mentions[i].data; twister.mentions.lengthCached++; if (twister.mentions.twists.cached[j].isNew) twister.mentions.lengthNew++; twister.mentions.lengthFromTorrent++; } } storage.remove('knownMentions'); } if (storage.isSet('lastMentionTime')) { twister.mentions.lastTime = storage.get('lastMentionTime'); storage.remove('lastMentionTime'); } if (storage.isSet('lastLocalMentionId')) { twister.mentions.lastTorrentId = storage.get('lastLocalMentionId'); storage.remove('lastLocalMentionId'); } if (storage.isSet('newMentions')) storage.remove('newMentions'); } function queryPendingPushMentions(req, res) { var lengthNew = 0; var lengthPending = twister.res[req].twists.pending.length; var timeCurrent = new Date().getTime() / 1000 + 7200; // 60 * 60 * 2 var timeLastMention = twister.res[req].lastTime; for (var i = 0; i < res.length; i++) { if (res[i].userpost.time > timeCurrent) { console.warn('ignoring mention from the future:'); console.log(res[i]); continue; } if (res[i].id) { twister.res[req].lastTorrentId = Math.max(twister.res[req].lastTorrentId, res[i].id); delete res[i].id; twister.res[req].lengthFromTorrent++; } var j = res[i].userpost.n + '/' + res[i].userpost.time; if (typeof twister.res[req].twists.cached[j] === 'undefined') { twister.res[req].twists.cached[j] = res[i]; twister.res[req].lengthCached++; twister.res[req].twists.pending.push(j); // mention must be somewhat recent compared to last known one to be considered new if (res[i].userpost.time + 259200 > timeLastMention) { // 3600 * 24 * 3 lengthNew++; twister.res[req].lastTime = Math.max(res[i].userpost.time, twister.res[req].lastTime); twister.res[req].twists.cached[j].isNew = true; } } } if (lengthNew) twister.res[req].lengthNew += lengthNew; if (twister.res[req].twists.pending.length > lengthPending) saveMentionsToStorage(); return lengthNew; } function resetMentionsCount() { twister.mentions.lengthNew = 0; for (var j in twister.mentions.twists.cached) if (twister.mentions.twists.cached[j].isNew) delete twister.mentions.twists.cached[j].isNew; saveMentionsToStorage(); $.MAL.updateNewMentionsUI(twister.mentions.lengthNew); } function initMentionsCount() { var req = queryStart('', defaultScreenName, 'mention', [10000, 2000, 3], 10000, { lastTime: 0, lastTorrentId: -1, lengthNew: 0, ready: function (req) { twister.mentions = twister.res[req]; twister.mentions.lengthFromTorrent = 0; loadMentionsFromStorage(); }, skidoo: function () {return false;} }); $.MAL.updateNewMentionsUI(twister.mentions.lengthNew); } function handleMentionsModalScroll(event) { if (!event || twister.mentions.scrollQueryActive) return; var elem = $(event.target); if (elem.scrollTop() >= elem[0].scrollHeight - elem.height() - 50) { twister.mentions.scrollQueryActive = true; twisterRpc('getmentions', [twister.mentions.query, postsPerRefresh, {max_id: twister.mentions.lastTorrentId - twister.mentions.lengthFromTorrent}], function (req, res) { twister.res[req].scrollQueryActive = false; twister.res[req].boardAutoAppend = true; // FIXME all pending twists will be appended queryProcess(req, res); twister.res[req].boardAutoAppend = false; }, twister.mentions.query + '@' + twister.mentions.resource, function () {console.warn('getmentions API requires twister-core > 0.9.27');} ); } } // --- direct messages --- function saveDMsToStorage() { var pool = {}; for (var peerAlias in twister.DMs) { var twists = [], length = 0; for (var j in twister.DMs[peerAlias].twists.cached) { for (var i = 0; i < length; i++) if (twister.DMs[peerAlias].twists.cached[j].id > twists[i].id) { twists.splice(i, 0, twister.DMs[peerAlias].twists.cached[j]); break; } if (length === twists.length) twists.push(twister.DMs[peerAlias].twists.cached[j]); length++; } pool[peerAlias] = { twists: twists.slice(0, 100), // TODO add an option to specify number of DMs to cache lastId: twister.DMs[peerAlias].lastId, }; } if ($.Options.get('dmEncryptCache') === 'enable') { pool = twister.var.key.pub.encrypt(JSON.stringify(pool)); delete pool.orig; // WORKAROUND the decrypt function does .slice(0, orig) but something goes wrong in process of buffer decoding (if original string contains non-ASCII characters) and orig may be smaller than the actual size, if it is undefined .slice gets it whole } $.initNamespaceStorage(defaultScreenName).localStorage.set('DMs', pool); } function loadDMsFromStorage() { var storage = $.initNamespaceStorage(defaultScreenName).localStorage; if (storage.isSet('DMs')) { var pool = storage.get('DMs'); if (pool.key && pool.body && pool.mac) { if (pool = twister.var.key.decrypt(pool)) pool = JSON.parse(pool.toString()); else console.warn('can\'t decrypt DMs\' data cache'); } if (typeof pool === 'object') { for (var peerAlias in pool) { if (!twister.DMs[peerAlias]) twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', {boardAutoAppend: true, lastId: 0, lengthNew: 0}); for (var i = 0; i < pool[peerAlias].twists.length; i++) { var j = pool[peerAlias].twists[i].from + '/' + pool[peerAlias].twists[i].time; if (typeof twister.DMs[peerAlias].twists.cached[j] === 'undefined') { twister.DMs[peerAlias].twists.cached[j] = pool[peerAlias].twists[i]; twister.DMs[peerAlias].lengthCached++; if (twister.DMs[peerAlias].twists.cached[j].isNew) twister.DMs[peerAlias].lengthNew++; } } twister.DMs[peerAlias].lastId = pool[peerAlias].lastId; } } } // WARN all following storage keys are deprecated (see commit FIXME) if (storage.isSet('lastDMIdPerUser')) { var pool = storage.get('lastDMIdPerUser'); if (typeof pool === 'object') for (var peerAlias in pool) { if (!twister.DMs[peerAlias]) twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', {boardAutoAppend: true, lastId: 0, lengthNew: 0}); twister.DMs[peerAlias].lastId = pool[peerAlias]; } storage.remove('lastDMIdPerUser'); } if (storage.isSet('newDMsPerUser')) { var pool = storage.get('newDMsPerUser'); if (typeof pool === 'object') for (var peerAlias in pool) { if (!twister.DMs[peerAlias]) twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', {boardAutoAppend: true, lastId: 0, lengthNew: 0}); twister.DMs[peerAlias].lengthNew = pool[peerAlias]; } storage.remove('newDMsPerUser'); } } function queryPendingPushDMs(res) { var lengthNew = 0; var lengthPending = 0; for (var peerAlias in res) { if (!res[peerAlias] || !res[peerAlias].length || !twister.DMs[peerAlias]) continue; for (var i = 0; i < res[peerAlias].length; i++) { var j = res[peerAlias][i].from + '/' + res[peerAlias][i].time; if (typeof twister.DMs[peerAlias].twists.cached[j] === 'undefined') { twister.DMs[peerAlias].twists.cached[j] = res[peerAlias][i]; twister.DMs[peerAlias].lengthCached++; twister.DMs[peerAlias].twists.pending.push(j); lengthPending++; if (twister.DMs[peerAlias].lastId < res[peerAlias][i].id) { twister.DMs[peerAlias].lastId = res[peerAlias][i].id; if ((!twister.DMs[peerAlias].board || !twister.DMs[peerAlias].board.is('html *')) && !res[peerAlias][i].fromMe && res[peerAlias][i].from !== defaultScreenName) { lengthNew++; twister.DMs[peerAlias].lengthNew += 1; twister.DMs[peerAlias].twists.cached[j].isNew = true; } } } } } if (lengthPending) saveDMsToStorage(); return lengthNew; } function requestDMsCount() { var list = []; for (var i = 0; i < followingUsers.length; i++) list.push({username: followingUsers[i]}); for (var i = 0; i < groupChatAliases.length; i++) list.push({username: groupChatAliases[i]}); twisterRpc('getdirectmsgs', [defaultScreenName, 1, list], function (req, res) { var lengthNew = 0, lengthNewMax = 0; var list = []; for (var peerAlias in res) { if (!res[peerAlias] || !res[peerAlias].length) continue; if (!twister.DMs[peerAlias]) twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct', {boardAutoAppend: true, lastId: 0, lengthNew: 0}); if (res[peerAlias][0].id > twister.DMs[peerAlias].lastId) { lengthNew = res[peerAlias][0].id - twister.DMs[peerAlias].lastId; if (lengthNewMax < lengthNew) lengthNewMax = lengthNew; list.push({username: peerAlias}); } else if (!twister.DMs[peerAlias].lengthCached) queryPendingPushDMs(res); } if (list.length === 1) queryProcess(list[0].username + '@direct', res); else if (lengthNewMax === 1) { if (queryPendingPushDMs(res)) DMsSummaryProcessNew(); } else if (lengthNewMax) { twisterRpc('getdirectmsgs', [defaultScreenName, lengthNewMax, list], function (req, res) { if (typeof res !== 'object' || $.isEmptyObject(res)) return; if (queryPendingPushDMs(res)) DMsSummaryProcessNew(); }, undefined, function (req, res) { console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res})); } ); } }, undefined, function (req, res) { console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res})); } ); } function DMsSummaryProcessNew() { var lengthNew = getNewDMsCount(); if (lengthNew) { $.MAL.updateNewDMsUI(lengthNew); $.MAL.soundNotifyDM(); if (!$.mobile) { if ($.Options.showDesktopNotifDMs.val === 'enable') { $.MAL.showDesktopNotification({ body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', lengthNew) + '.', tag: 'twister_notification_new_DMs', timeout: $.Options.showDesktopNotifDMsTimer.val, funcClick: function () {$.MAL.showDMchat();} }); } var elem = getElem('.directMessages .direct-messages-list'); if (isModalWithElemExists(elem)) modalDMsSummaryDraw(elem); } else if ($.mobile.activePage.attr('id') !== 'directmsg') modalDMsSummaryDraw($('#directmsg .direct-messages-list')); } lengthNew = getNewGroupDMsCount(); if (lengthNew) { $.MAL.updateNewGroupDMsUI(lengthNew); $.MAL.soundNotifyDM(); if (!$.mobile) { if ($.Options.showDesktopNotifDMs.val === 'enable') { $.MAL.showDesktopNotification({ body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', lengthNew) + '.', tag: 'twister_notification_new_DMs', timeout: $.Options.showDesktopNotifDMsTimer.val, funcClick: function () {$.MAL.showDMchat({group: true});} }); } var elem = getElem('.groupMessages .direct-messages-list'); if (isModalWithElemExists(elem)) modalDMsSummaryDraw(elem, true); } else if ($.mobile.activePage.attr('id') !== 'directmsg') modalDMsSummaryDraw($('#directmsg .direct-messages-list'), true); } } function getNewDMsCount() { var lengthNew = 0; for (var peerAlias in twister.DMs) if (peerAlias[0] !== '*' && twister.DMs[peerAlias].lengthNew) lengthNew += twister.DMs[peerAlias].lengthNew; return lengthNew; } function getNewGroupDMsCount() { var lengthNew = 0; for (var peerAlias in twister.DMs) if (peerAlias[0] === '*' && twister.DMs[peerAlias].lengthNew) lengthNew += twister.DMs[peerAlias].lengthNew; return lengthNew; } function resetNewDMsCount() { for (var peerAlias in twister.DMs) if (peerAlias[0] !== '*') { twister.DMs[peerAlias].lengthNew = 0; for (var j in twister.DMs[peerAlias].twists.cached) delete twister.DMs[peerAlias].twists.cached[j].isNew; } saveDMsToStorage(); $.MAL.updateNewDMsUI(getNewDMsCount()); } function resetNewDMsCountGroup() { for (var peerAlias in twister.DMs) if (peerAlias[0] === '*') { twister.DMs[peerAlias].lengthNew = 0; for (var j in twister.DMs[peerAlias].twists.cached) delete twister.DMs[peerAlias].twists.cached[j].isNew; } saveDMsToStorage(); $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); } function resetNewDMsCountForPeer(peerAlias) { twister.DMs[peerAlias].lengthNew = 0; for (var j in twister.DMs[peerAlias].twists.cached) delete twister.DMs[peerAlias].twists.cached[j].isNew; saveDMsToStorage(); if (peerAlias[0] !== '*') $.MAL.updateNewDMsUI(getNewDMsCount()); else $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); } function updateGroupList() { twisterRpc('listgroups', [], function(req, ret) {groupChatAliases = ret;}, null, function(req, ret) {console.warn('twisterd >= 0.9.30 required for listgroups');}, null ); } function initDMsCount() { twister.DMs = {}; dumpPrivkey(defaultScreenName, function (req, res) { twister.var.key = TwisterCrypto.PrivKey.fromWIF(res); loadDMsFromStorage(); $.MAL.updateNewDMsUI(getNewDMsCount()); $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); //quick hack to obtain list of group chat aliases updateGroupList(); setInterval(updateGroupList, 60000); setTimeout(requestDMsCount, 200); //polling not needed: processNewPostsConfirmation will call requestDMsCount. //setInterval('requestDMsCount()', 5000); }); } function newmsgsChangedUser() { clearInterval(twister.mentions.interval); } function handleDMsModalScroll(event) { if (!event || !event.data.req || !twister.DMs[event.data.req] || twister.DMs[event.data.req].scrollQueryActive) return; var length = twister.DMs[event.data.req].lastId - twister.DMs[event.data.req].lengthCached + 1; if (!length) return; var elem = $(event.target); if (elem.scrollTop() < 100) { twister.DMs[event.data.req].scrollQueryActive = true; twisterRpc('getdirectmsgs', [defaultScreenName, Math.min(length, postsPerRefresh), [{username: twister.DMs[event.data.req].query, max_id: length - 1}]], function (req, res) { twister.res[req.k].scrollQueryActive = false; //twister.res[req.k].boardAutoAppend = true; // FIXME all pending twists will be appended queryProcess(req.k, res); //twister.res[req.k].boardAutoAppend = false; if (req.container[0].scrollHeight !== req.containerScrollHeightPrev) req.container.scrollTop(req.container[0].scrollHeight - req.containerScrollHeightPrev); }, { k: twister.DMs[event.data.req].query + '@' + twister.DMs[event.data.req].resource, container: elem, containerScrollHeightPrev: elem[0].scrollHeight }, function (req, res) { console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res})); } ); } }