Files
vnctalk-prosody/vnctalk/mod_vnc_lastactivity.lua
T

371 lines
14 KiB
Lua

-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require "util.stanza";
local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local base64 = require"util.encodings".base64;
local sha1 = require"util.hashes".sha1;
local my_host = module:get_host();
local my_host_type = module:get_host_type();
local avt_hash_table = {};
local remote_act_cache = {};
local remote_act_sessions = {};
module:depends"vcard";
local vcard_storage = module:open_store("vcard");
local activity_store = module:open_store("activity", "map");
local remote_activity_store = module:open_store("remote_activity", "map");
local remote_avatar_update_store = module:open_store("remote_avatar", "map");
local function dumpTable(t, depth)
local result = "";
if (not depth) then depth = 0 end;
local prefix = string.rep(" ", depth);
if (type(t) ~= "table") then
result = result.." "..tostring(t);
else
for key,value in pairs(t) do
result = result..prefix..tostring(key).." => "..dumpTable(value, depth+1).."\n"
end
end
return result;
end
local function get_vcard(username)
local vcard, err = vcard_storage:get(username);
if vcard then
vcard = st.deserialize(vcard);
end
if not vcard then
vcard = st.stanza("vCard", { xmlns = "vcard-temp" });
end
return vcard, err;
end
local function calculate_avatar_hash(username)
local vcard = get_vcard(username)
local photo = vcard and vcard:get_child("PHOTO")
if photo then
local photo_type = photo:get_child_text("TYPE");
local photo_b64 = photo:get_child_text("BINVAL");
local photo_raw = photo_b64 and base64.decode(photo_b64);
if photo_raw and photo_type then
return sha1(photo_raw, true);
end
end
return ""
end
local function get_avatar_hash(username, host)
if (my_host ~= host) then
return nil
end
local session = nil
if (prosody.hosts[host].sessions ~= nil) then
session = prosody.hosts[host] and prosody.hosts[host].sessions[username]
end
local avt_hash = nil;
if not avt_hash_table[username] then
avt_hash = calculate_avatar_hash(username);
avt_hash_table[username] = avt_hash;
-- -- module:log("info", "DEBUG get_avatar_hash returning result after new calc");
else
-- -- module:log("info", "DEBUG get_avatar_hash returning result from cache");
avt_hash = avt_hash_table[username];
end;
return avt_hash
end
module:add_feature("jabber:iq:last");
module:add_feature("jabber:iq:batch");
local map = {};
module:hook("authentication-success", function(event)
local session = event.session;
if session.username then
local pers = activity_store:set(session.username, "last", 0);
end
end, 10);
module:hook("presence/bare", function(event)
local origin, stanza = event.origin, event.stanza;
local origin, stanza = event.origin, event.stanza;
local typ = stanza.attr.type;
-- module:log("info", "Cache hook, got %s from a %s", stanza:top_tag(), origin.type);
if origin.type == "s2sin" and ( typ == nil or typ == "unavailable" ) then
local contact_full = stanza.attr.from;
local contact_bare = jid_bare(contact_full);
local contact_sessions = remote_act_sessions[contact_bare] or {};
-- module:log("info", "contact_sessions from %s is %s", contact_bare, dumpTable(remote_act_sessions[contact_bare]));
if typ == "unavailable" then -- set current time in cache
local t = os.time();
local s = "";
if contact_sessions[contact_full] then
contact_sessions[contact_full] = nil;
remote_act_sessions[contact_bare] = contact_sessions;
if ((#remote_act_sessions[contact_bare]) == 0 ) then
remote_act_sessions[contact_bare] = nil;
remote_act_cache[contact_bare] = t;
-- module:log("info", "remote_act_cache for %s is now %s", contact_bare, remote_act_cache[contact_bare]);
remote_activity_store:set(contact_bare, "last", t);
end
end
else
-- set 0 (online) in cache
-- module:log("info", "storing online state for %s in remote_act_cache", contact_bare);
remote_act_cache[contact_bare] = 0;
local contact_sessions = remote_act_sessions[contact_bare] or {};
contact_sessions[contact_full] = 1;
remote_act_sessions[contact_bare] = contact_sessions;
local x_update = stanza:get_child("x", "vcard-temp:x:update") or nil;
if (x_update) then
local x_photo_update = x_update:get_child_text("photo") or nil;
-- module:log("info", "Cache hook1 debug: %s", x_photo_update);
if (x_photo_update) then
module:log("info", "avatarupdate - need to store %s update for %s", x_photo_update, contact_bare);
local t = os.time();
remote_avatar_update_store:set(contact_bare, "avatar", t);
end
end
end
end
end, 10);
module:hook("pre-presence/bare", function(event)
local stanza = event.stanza;
if not(stanza.attr.to) and stanza.attr.type == "unavailable" then
-- module:log("info", "unavailable hook - event.origin.username: %s", event.origin.username);
local s = os.time();
local t = stanza:get_child_text("status");
map[event.origin.username] = {s = s, t = t};
local lastsession = true;
local luser, lhost = jid_split(stanza.attr.from);
if luser and lhost then
local user_sessions = hosts[lhost] and hosts[lhost].sessions[luser];
if user_sessions then
lastsession = false;
end
end
if lastsession then
-- module:log("info", "last session gone - storing to activity_store for %s => %s", event.origin.username, s);
local pers = activity_store:set(event.origin.username, "last", s);
end
end
end, 10);
module:hook("iq/bare/jabber:iq:batch:query", function(event)
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type == "get" then
-- -- module:log("info", "got stanza: %s", dumpTable(stanza));
local querychild = stanza:get_child("query", "jabber:iq:batch");
local reply = st.reply(stanza):tag('query', {xmlns='jabber:iq:last'});
-- :childtags("jid", "jabber:iq:batch");
-- -- module:log("info", "querychilds: %s", dumpTable(querychild));
for ajid in querychild:childtags("jid") do
local abjid = ajid:get_text();
-- module:log("info", "got jid: %s", abjid);
local username = jid_split(abjid);
local luser, lhost = jid_split(abjid);
local seconds, text = "-1", "";
local hash = "";
-- -- module:log("info", "map is now: %s", dumpTable(map));
if map[username] then
-- module:log("info", "found map[ %s ] => t: %s", username, map[username].t );
-- module:log("info", "found map[ %s ] => s: %s", username, map[username].s );
local mt = tonumber(map[username].s);
seconds = tostring(os.difftime(os.time(), mt));
text = map[username].s;
end
if luser and lhost then
-- -- module:log("info", "luser: %s ++ lhost %s", luser, lhost);
local sessions_avail = false;
if (hosts[lhost]) then
-- module:log("info", "lhost exists: %s", lhost);
if (hosts[lhost].sessions) then
-- module:log("info", "got session object for lhost %s", lhost);
sessions_avail = true;
end
end
if sessions_avail then
local user_sessions = hosts[lhost] and hosts[lhost].sessions[luser];
local latest_hibernated_stamp = 0;
local all_hibernated = true;
if user_sessions then
for x,s in pairs(user_sessions.sessions) do
if (s.hibernating) then
-- module:log("info", "user_sessions s.hibernated %s is %s", s, s.hibernating);
if (s.hibernating > latest_hibernated_stamp) then
latest_hibernated_stamp = s.hibernating;
end
else
-- module:log("info", "user_sessions %s is active", s);
all_hibernated = false;
end
end
-- module:log("info","latest_hibernated_stamp is %s", latest_hibernated_stamp);
-- module:log("info","all_hibernated is %s", all_hibernated);
if (latest_hibernated_stamp > 0 and all_hibernated) then
seconds = os.difftime(os.time(), latest_hibernated_stamp)
else
seconds = 0
end
end
end
if not (prosody.hosts[lhost]) then
local contact_bare = luser.."@"..lhost;
-- module:log("info","getting act for remote %s", contact_bare);
if remote_act_cache[contact_bare] then
if ( remote_act_cache[contact_bare] > 0) then
seconds = tostring(os.difftime(os.time(), remote_act_cache[contact_bare]));
else
seconds = 0;
end
end
end
end
if (seconds == "-1") then
if (prosody.hosts[lhost]) then
local stored_seconds = activity_store:get(luser, "last") or false;
if stored_seconds then
-- module:log("info", "read persistant lastactivity for %s as %s", luser, dumpTable(stored_seconds));
local nseconds = tonumber(stored_seconds);
if (nseconds == 0) then
-- module:log("info","using restored for %s @ %s ", luser, lhost);
map[event.origin.username] = {s = "0", t = text};
else
seconds = tostring(os.difftime(os.time(), nseconds));
-- module:log("info","using restored for %s @ %s => %s", luser, lhost, nseconds);
map[event.origin.username] = {s = nseconds, t = text};
end
end
end
end
-- hash = get_avatar_hash(luser, lhost);
-- reply:add_child():tag("result", {jid=abjid, seconds=seconds, photo=hash}):up();
-- module:log("info","determined for %s => %s seconds", abjid, seconds);
-- module:log("info","determined for %s => %s seconds", abjid, tostring(seconds));
-- module:log("info","type jid: %s", type(abjid));
-- module:log("info","type seconds: %s", type(seconds));
-- module:log("info"," reply before add: %s", reply);
-- reply:add_child():tag("result", { jid=abjid; seconds=tostring(seconds); } ):up();
local rtag = st.stanza("result", { jid=abjid; seconds=tostring(seconds); });
reply:add_child(rtag);
end
-- module:log("info", "sending reply %s", reply);
origin.send(reply);
end
return true;
end);
module:hook("iq/bare/jabber:iq:last:query", function(event)
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type == "get" then
local username = jid_split(stanza.attr.to) or origin.username;
local luser, lhost = jid_split(stanza.attr.to);
-- if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
if stanza.attr.to then
local seconds, text = "-1", "";
local hash = "";
if map[username] then
mseconds = map[username].s or 0;
-- module:log("info", "mseconds: %s", mseconds);
if (mseconds == 0) then
seconds=0;
map[username].s=os.time();
else
-- module:log("info", "difftime from map for user %s is %s", username, dumpTable(map[username]));
-- module:log("info", "difftime will be called for %s == type %s", map[username].s, type(map[username].s));
seconds = tostring(os.difftime(os.time(), map[username].s));
-- module:log("info","using2 for %s @ %s => %s --- stored was %s", luser, lhost, seconds, map[username].s);
-- text = map[username].s;
end
end
if luser and lhost then
local user_sessions = hosts[lhost] and hosts[lhost].sessions[luser];
if user_sessions then
local latest_hibernated_stamp = 0;
local all_hibernated = true;
for x,s in pairs(user_sessions.sessions) do
if (s.hibernating) then
-- module:log("info", "user_sessions s.hibernated %s is %s", s, s.hibernating);
if (s.hibernating > latest_hibernated_stamp) then
latest_hibernated_stamp = s.hibernating;
end
else
-- module:log("info", "user_sessions %s is active", s);
all_hibernated = false;
end
end
-- module:log("info","latest_hibernated_stamp is %s", latest_hibernated_stamp);
-- module:log("info","all_hibernated is %s", all_hibernated);
if (latest_hibernated_stamp > 0 and all_hibernated) then
seconds = os.difftime(os.time(), latest_hibernated_stamp)
else
seconds = 0
end
end
end
if (seconds == "-1") then
local stored_seconds = activity_store:get(luser, "last") or -1;
if (stored_seconds ~= 0) then
-- -- module:log("info", "read persistant lastactivity for %s as %s", luser, dumpTable(stored_seconds));
if (stored_seconds == -1) then
seconds = -1
else
seconds = tostring(os.difftime(os.time(), stored_seconds));
end;
-- module:log("info","using4 for %s @ %s => %s", luser, lhost, seconds);
if event.origin.username ~= nil then
map[event.origin.username] = {s = seconds, t = text};
end
else
-- local now = os.time();
-- module:log("info", "no stored data for %s @ %s", luser, lhost);
-- local upers = activity_store:set(luser, "last", now);
-- seconds = 0;
end
end
-- module:log("info","sending reply with: seconds=%s text=%s", seconds, text);
origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=tostring(seconds)}):text(tostring(text)));
else
origin.send(st.error_reply(stanza, 'auth', 'forbidden'));
end
return true;
end
end);
module.save = function()
return {map = map};
end
module.restore = function(data)
map = data.map or {};
end