-
-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathmod_vcard_peertubelivechat.lua
More file actions
139 lines (124 loc) · 5.63 KB
/
Copy pathmod_vcard_peertubelivechat.lua
File metadata and controls
139 lines (124 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
-- SPDX-FileCopyrightText: 2024-2025 John Livingston <https://www.john-livingston.fr/>
--
-- SPDX-License-Identifier: AGPL-3.0-only
local st = require "util.stanza";
local http = require "net.http";
local gettime = require 'socket'.gettime;
local async = require "util.async";
local promise = require "util.promise";
local b64 = require "util.encodings".base64.encode;
local jid_split = require "util.jid".split;
local json = require "util.json";
local uh = require "util.http";
module:add_feature("vcard-temp");
local CACHE_EXPIRY = 3600;
local cache_user = {}; -- keeps track of vCards requests. Foreach "who", keeps the last query time (so we can expire it), and a promise that resolves with a vCards, or rejects if no vCard
local peertube_url = assert(module:get_option_string("peertubelivechat_vcard_peertube_url", nil), "'peertubelivechat_vcard_peertube_url' is a required option");
if peertube_url:sub(-1,-1) == "/" then peertube_url = peertube_url:sub(1,-2); end
local function read_avatar_url(ret)
-- Note:
-- * before Peertube v6.0.0: using ret.avatar
-- * after Peertube v6.0.0: using ret.avatars, searching for width 48, or for the smallest width
if ret.avatar and ret.avatar.path then
module:log("debug", "User avatar path (Peertube < v6): %s", peertube_url .. ret.avatar.path);
return peertube_url .. ret.avatar.path;
end
local min_width = 100000;
local min_path = nil;
if ret.avatars and type(ret.avatars) == "table" then
for _, avatar in ipairs(ret.avatars) do
if avatar.path and avatar.width then
if (avatar.width == 48) then
module:log("debug", "User avatar path (Peertube >= v6, width 48): %s", peertube_url .. avatar.path);
return peertube_url .. avatar.path;
end
-- https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/736
-- seems that in some case, avatar.width is a table... So we check that it is a number.
if (type(avatar.width) == 'number' and avatar.width < min_width) then;
min_path = avatar.path;
min_width = avatar.width;
end
end
end
if min_path then
module:log("debug", "User avatar path (Peertube >= v6, minimal width): %s", peertube_url .. min_path);
return peertube_url .. min_path;
end
end
module:log("debug", "Cant find user avatar url");
return nil;
end
module:hook("iq-get/bare/vcard-temp:vCard", function (event)
local origin, stanza = event.origin, event.stanza;
local who = jid_split(stanza.attr.to) or origin.username
module:log("debug", "vCard request for %s", who);
local from_cache = cache_user[who];
if from_cache and from_cache["last_fetch_time"] and from_cache["last_fetch_time"] + CACHE_EXPIRY < gettime() then
module:log("debug", "vCard promise for %s was in cache but is expired.", who);
cache_user[who] = nil;
from_cache = nil;
end
if not from_cache then
module:log("debug", "vCard for %s is not in cache, loading it.", who);
local p = promise.new(function(resolve, reject)
local url = peertube_url .. '/api/v1/accounts/' .. uh.urlencode(who);
module:log("debug", "Calling Peertube API: %s", url);
http.request(url, { accept = "application/json" }, function (body, code)
if not(math.floor(code / 100) == 2) then
reject("Rejected by API for " .. who .. ": " .. body);
return;
end
local parsed, parse_err = json.decode(body);
if not parsed then
reject("Got invalid JSON for " .. who .. " from " .. url .. ': ' .. parse_err);
return;
end
module:log("debug", "Got valid JSON, so far everything is ok.");
local avatar_url = read_avatar_url(parsed);
if not avatar_url then
reject("No avatar url for user " .. who .. ", so ignoring vCard");
return;
end
module:log("debug", "Downloading user avatar using %s", avatar_url);
http.request(avatar_url, {}, function (body, code, response)
if not(math.floor(code / 100) == 2) then
reject("Cant load avatar for " .. who .. ": " .. body);
return;
end
if not (response and response.headers and response.headers["content-type"]) then
reject("Avatar for " .. who .. " has no content-type.");
return;
end
module:log("debug", "Avatar found for %s, with content-type: %s", who, response.headers["content-type"]);
local vcard_temp = st.stanza("vCard", { xmlns = "vcard-temp" });
vcard_temp:text_tag("FN", parsed.displayName);
vcard_temp:text_tag("NICKNAME", parsed.displayName);
vcard_temp:text_tag("URL", parsed.url);
vcard_temp:tag("PHOTO");
vcard_temp:text_tag("TYPE", response.headers["content-type"]);
vcard_temp:text_tag("BINVAL", b64(body));
vcard_temp:up();
resolve(vcard_temp);
return;
end)
end);
end);
from_cache = { last_fetch_time = gettime(), promise = p };
cache_user[who] = from_cache;
end
if not (from_cache and from_cache["promise"] and promise.is_promise(from_cache["promise"])) then
module:log("error", "Missing from_cache promise (user %s).", who);
origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
return true;
end
module:log("debug", "There is a vCard promise for %s.", who);
local vcard, err = async.wait_for(from_cache["promise"])
if (vcard) then
module:log("debug", "vCard for %s loaded.", who);
origin.send(st.reply(stanza):add_child(vcard));
else
module:log("debug", "no vCard for %s (%s).", who, err);
origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
end
return true;
end);