371 lines
14 KiB
Lua
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
|