psyced/world/net/jabber/mixin_render.c
psyc://psyced.org/~lynX 13b4af8098 support for XEP-0203
2010-03-17 10:19:14 +01:00

582 lines
20 KiB
C
Raw Blame History

#include "jabber.h"
#include <net.h>
#include <uniform.h>
// just renderMembers
#include NET_PATH "members.i"
int msg(string source, string mc, string data,
mapping vars, int showingLog, string target) {
mixed t;
switch (mc) {
case "_status_description_time":
case "_status_person_away":
case "_status_person_present":
case "_status_person_present_implied":
#if 0
if (strstr(vars["_INTERNAL_target_jabber"], "@") == -1) {
P3(("skipping status person* to %O\n", vars["_INTERNAL_target_jabber"]))
return 1;
}
#endif
// reply to a presence probe
if (member(vars, "_time_idle")) {
t = vars["_time_idle"];
if (stringp(t)) {
t = to_int(t);
PT(("_time_idle %O == %O, right?\n", vars["_time_idle"], t))
}
t = gmtime(time() - t);
vars["_INTERNAL_time_jabber_legacy"] = JABBERTIMELEGACY(t);
vars["_INTERNAL_time_jabber"] = JABBERTIME(t);
} else {
return 1;
}
break;
case "_status_legacy_CTCP":
case "_request_legacy_CTCP":
// ignore these
return 1;
break;
case "_request_examine": // don't use this, please remove in 2009
case "_request_description": // this is the one.
mc = "_request_description_vCard"; // pending rename.. TODO
unless (vars["_tag"]) vars["_tag"] = RANDHEXSTRING;
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result") {
mixed mvars = convert_profile($3["/vCard"], "jCard");
return ({ $1, "_status_description_person", 0, mvars + $2 });
}
else {
string err_mc;
if (xmpp_error($3["/error"],
"service-unavailable")) {
err_mc = "_failure_unavailable_service_description";
} else {
err_mc = "_error_unknown_name_user";
}
return ({ $1, err_mc, "Received no description from [_nick].", $2 });
}
:));
break;
case "_request_version":
unless (vars["_tag"]) vars["_tag"] = RANDHEXSTRING;
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result") {
// string v = "unknown";
XMLNode helper = $3["/query"];
if (helper["/name"])
$2["_version_description"] = helper["/name"][Cdata];
else
$2["_version_description"] = "-";
if (helper["/version"])
$2["_version"] = helper["/version"][Cdata];
else
$2["_version"] = "-";
/*
if (helper["/os"]) {
v += " on " + (helper["/os"][Cdata] || "unknown OS");
} */
return ({ $1, "_status_version",
"Version: [_nick] is using \"[_version_description]\" ([_version]).",
$2 });
}
else
return ({ $1, "_failure_unavailable_version", "Received no version from [_nick].", $2 });
:));
break;
case "_request_user_amount":
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
XMLNode helper;
helper = $3["/query"];
if (helper["/stat"] && nodelistp(helper["/stat"]))
foreach(helper : helper["/stat"]) {
switch(helper["@name"]) {
case "users/total":
$2["_amount_users_registered"] = helper["@value"];
break;
case "users/online":
$2["_amount_users_loaded"] = helper["@value"];
break;
}
}
return ({ $1, "_status_user_amount", 0,
// generic formats shouldn't be hanging around in net/jabber...!?
// "There are [_amount_users_loaded] people loaded, [_amount_users_registered] accounts on this server.",
$2 });
} else
return ({ $1, "_error_request_users_amount",
"[_nick] does not support querying users amount.",
$2 });
:));
break;
case "_request_list_feature":
if (vars["_node"]) mc += "_node";
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
// string variant;
XMLNode helper = $3["/query"];
if (helper["/identity"]) {
if (nodelistp(helper["/identity"])) {
// FIXME
} else {
$2["_identity"] = helper["/identity"]["@category"] + "/" + helper["/identity"]["@type"];
$2["_name"] = helper["/identity"]["@name"];
#if 0 // nice variable, but please do something with it sometime soon :)
switch(helper["/identity"]["@category"]) {
case "account":
variant = "_person";
break;
case "conference":
variant = "_place";
break;
case "headline":
variant = "_newsfeed";
break;
case "server":
variant = "_server";
break;
}
#endif
}
}
$2["_list_feature"] = ({ });
if (helper["/feature"] && nodelistp(helper["/feature"]))
foreach(helper : helper["/feature"]) {
$2["_list_feature"] += ({ helper["@var"] });
}
return ({ $1, "_notice_list_feature",
"[_nick] is a [_identity] called [_name] offering features [_list_feature].",
$2 });
} else
return ({ $1, "_error_request_list_feature",
"[_nick] does not support querying features.",
$2 });
:));
break;
case "_request_list_item":
if (vars["_node"]) mc += "_node";
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
XMLNode helper;
helper = $3["/query"];
if (helper["@node"]) $2["_node"] = helper["@node"];
$2["_list_item"] = ({ });
$2["_list_item_description"] = ({ });
$2["_list_item_node"] = ({ });
if (helper["/item"] && nodelistp(helper["/item"]))
foreach(helper : helper["/item"]) {
$2["_list_item"] += ({ XMPP + helper["@jid"] });
$2["_list_item_description"] += ({ helper["@name"] });
$2["_list_item_node"] += ({ helper["@node"] });
}
// die darstellung ist fuer telnetter suboptimal...
return ({ $1, "_notice_list_item",
"[_nick] has lots of items: [_list_item_description].",
$2 });
} else
return ({ $1, "_error_request_list_item",
"[_nick] does not support querying items.",
$2 });
:));
break;
case "_request_registration":
if (!vars["_username"]) {
mc = "_request_registration_query";
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
XMLNode helper;
helper = $3["/query"];
if (helper["/instructions"])
$2["_instructions"] = helper["/instructions"][Cdata];
else
$2["_instructions"] = "No instructions available.";
return ({ $1, "_notice_registration",
"[_nick] provides the following registration instructions: [_instructions]",
$2 });
} else
return ({ $1, "_error_query_registration",
"[_nick] does not support registration.",
$2 });
:));
} else {
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result") {
return ({ $1, "_status_registration",
"Your registration at [_nick] was successful.",
$2 });
} else
return ({ $1, "_error_query_registration",
"[_nick] does not support registration.",
$2 });
:));
}
break;
case "_request_ping":
source->chain_callback(vars["_tag"], (:
$2["_time_ping"] = vars["_time_ping"];
if ($3["@type"] == "result")
return ({ $1, "_echo_ping",
"[_nick] pongs you.", $2 });
else
return ({ $1, "_failure_unsupported_ping",
"[_nick] does not support ping.", $2 });
:));
break;
case "_request_description_time":
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result" && $3["/query"]) {
$2["_time_idle"] = $3["/query"]["@@seconds"];
return ({ $1, "_status_description_time",
"[_nick] has been alive about [_time_idle] ago.", $2 });
} else
return ({ $1, "_failure_unsupported_description_time",
"[_nick] does not support querying idle time.",
$2 });
:));
break;
#ifndef _flag_disable_module_authentication
case "_request_authentication":
// TODO: XEP 0070 says we should use <message/> when the recipient is a bare jid
// but I prefer the iq method
source->chain_callback(vars["_tag"], (:
if ($3["@type"] == "result")
return ({ $1, "_notice_authentication", 0, $2 });
else
return ({ $1, "_error_invalid_authentication", 0, $2 });
:));
break;
#endif
#ifndef _flag_disable_query_server
case "_notice_list_feature":
case "_notice_list_feature_person":
case "_notice_list_feature_place":
case "_notice_list_feature_server":
case "_notice_list_feature_newsfeed":
mixed id2jabber = shared_memory("disco_identity");
mixed feat2jabber = shared_memory("disco_features");
vars["_identity"] = id2jabber[vars["_identity"]] || vars["_identity"];
vars["_list_feature"] = implode(map(vars["_list_feature"],
(: return "<feature var='" + feat2jabber[$1] + "'/>"; :)), "");
break;
#endif
case "_notice_list_item":
t = "";
// same stuff in user.c (what happened to code sharing?)
for (int i = 0; i < sizeof(vars["_list_item"]); i++) {
t += "<item name='" + xmlquote(vars["_list_item_description"][i]) + "'";
if (vars["_list_item"] && vars["_list_item"][i])
t += " jid='" + mkjid(vars["_list_item"][i]) + "'";
if (vars["_list_item_node"] && vars["_list_item_node"][i])
t += " node='" + vars["_list_item_node"][i] + "'";
t += "/>";
}
vars["_list_item"] = t;
break;
case "_notice_headline_news":
vars["_page_news"] = xmlquote(vars["_page_news"]);
break;
// case "_notice_received_email":
// vars["_subject"] = xmlquote(vars["_subject"]);
// vars["_origin"] = xmlquote(vars["_origin"]);
// break;
case "_status_place_description_news_rss":
vars["_link_news_rss"] = xmlquote(vars["_link_news_rss"]);
break;
case "_status_description_person":
mc = "_status_description_vCard";
vars["_INTERNAL_data_XML"] = convert_profile(vars, 0, "jCard");
break;
#ifndef ENTER_MEMBERS
case "_status_place_members":
string skip, clashnick, placejid;
array(string) rendered;
mixed u;
int i;
P2(("_status_place_members from %O to %O\n", source, target))
u = parse_uniform(target);
if (u) clashnick = u[UUser];
else clashnick = 0;
// PARANOID? noooo
rendered = ({array(string)})renderMembers(vars["_list_members"], vars["_list_members_nicks"], 1);
placejid = mkjid(source);
for(i = 0; i < sizeof(vars["_list_members"]); i++) {
// see JEP-0045 6.3.3 last paragraph
// probably their assumption is about a linear
// list where this is always sent to the last
// (ie newest member)
# ifdef USE_THE_RESOURCE
if (stringp(vars["_list_members"][i])
&& abbrev(vars["_list_members"][i], target))
# else
if (target == vars["_list_members"][i])
# endif
{
skip = vars["_list_members_nicks"][i];
continue;
}
// here we also have to do collision decection
// (local user with the same nick as remote user)
// and adjust rendered[i] accordingly
if (rendered[i] == clashnick) {
// this only happens with local users
rendered[i] = SERVER_UNIFORM +"~"+ clashnick;
}
render("_status_place_members_each", "", ([
"_INTERNAL_target_jabber": vars["_INTERNAL_target_jabber"],
"_INTERNAL_source_jabber": placejid + "/" + RESOURCEPREP(rendered[i]),
"_nick": rendered[i],
"_source_relay": mkjid(vars["_list_members"][i]),
"_duty": "none" ]), source);
}
vars["_INTERNAL_source_jabber"] += "/" + skip;
vars["_source_relay"] = mkjid(target);
vars["_duty"] = "none";
render("_status_place_members_self", "", vars, source);
return 1;
#endif
case "_status_presence_here":
case "_notice_presence_here":
// _notice_presence_here requires an additional jabberig
// messsage at least if this is the login announce
// TODO: this one is temporary
case "_notice_presence_here_quiet":
#ifdef XMPPERIMENTAL
emit("<presence from='" + vars["_INTERNAL_source_jabber"] + "'/>"
"<presence from='" + vars["_INTERNAL_source_jabber"] +
"' type='probe'/>");
return 1;
#else
// notiz: _request_status_person muss von der UNI erfolgen,
// nicht von der UNL, damit die Antwort auch wieder
// an diese geht
// und eigentlich muss das nur beim logon erfolgen...
P4(("%O _request_status_person to be done: %O,%O,%O,%O.. %O\n", ME, source,mc,data,target, vars))
//
// is this correct here, or do we need something smarter? -lynX
vars["_INTERNAL_target_jabber_bare"] = vars["_INTERNAL_target_jabber"];
// apparently we get the bare one here, anyway.. so we could
// throw out this special case of *_bare variable. then again,
// maybe i am wrong. how can i ensure? TODO -lynX
msg(source, "_request_status_person", "", vars, showingLog, target);
#endif
// TODO: we dont explicitly need this for CACHE PRESENCE
// on the other hand syncing with jabber is much harder O(n)
// than it is with psyc O(1)
break;
case "_message_public":
// TODO: this needs to be applied to questions & actions also
if (vars["_time_place"]) {
// see JEP-0045, 6.3.11 Discussion History
// and JEP-0091 Delayed Delivery
mc = "_message_public_history";
t = gmtime(vars["_time_place"]);
vars["_INTERNAL_time_place_jabber_legacy"] = JABBERTIMELEGACY(t);
vars["_INTERNAL_time_place_jabber"] = JABBERTIME(t);
} else if (!vars["_context"]) {
mc = "_request_message_public";
}
break;
case "_request_execute":
// oh yes, this is the jabber way to do it!!!!111!
// taken from the xep 0045 "irc command mapping"
mixed args = explode(data, " ");
switch(args[0]) {
case "topic":
// <lynX> might as well convert to _request_do_topic but
// (a) we don't have that yet
// (b) might even share code with place/basic to do that
// as any place needs to be able to understand both
// spec'd commands (_do) and ad hoc commands (_execute)
mc = "_request_execute_topic";
data = ARGS(1);
break;
case "kick": // TODO: we could add a callback for this
mc = "_request_execute_kick";
vars["_nick_target"] = is_formal(args[1]) ? parse_uniform(args[1])[UNick] : args[1];
vars["_reason"] = ARGS(2);
break;
case "ban": // TODO: we could add a callback for this
// mh... we have a hard time finding out the real jid
// of the participant, so this is deactivated for now
return 0;
case "invite":
// TODO: we should do invite via the places!
return 0;
default:
return 0; // pushback
}
break;
default:
// reihenfolge bitte nach wahrscheinlichkeit der mc TODO ;)
if (abbrev("_message_private", mc)) {
// generate echo here as jabber does not provide echo (apart from MUC)
sendmsg(source, "_message_echo" + mc[8..], data, vars,
target);
} else if (abbrev("_notice_place_leave", mc)) {
/*
if (mc == "_notice_place_leave_invalid")
vars["_INTERNAL_source_jabber"] = vars["_INTERNAL_source_jabber_bare"] + "/" + vars["_nick_local"];
*/
mc = "_notice_place_leave"; // remove this if textdb inheritance works
} else if (abbrev("_notice_place_enter", mc)) {
vars["_source_relay"] = mkjid(source);
mc = "_notice_place_enter";
} else if (abbrev("_echo_place_enter", mc)) {
#if 0
vars["_source_relay"] = mkjid(target);
mc = "_echo_place_enter";
vars["_duty"] = "none";
#else
# ifdef ENTER_MEMBERS
if (vars["_list_members"]) {
string placejid = mkjid(source);
string *rendered;
// i wish we could leave the clash strategy to mkjid...
// PARANOID? noooo
rendered = ({array(string)})renderMembers(vars["_list_members"], vars["_list_members_nicks"], 1);
for(int i = 0; i < sizeof(vars["_list_members"]); i++) {
// here we also have to do collision decection
// (local user with the same nick as remote user)
// and adjust rendered[i] accordingly
if (rendered[i] == vars["_nick"]) {
// this only happens with local users
rendered[i] = SERVER_UNIFORM +"~"+ rendered[i];
}
render("_notice_place_enter", "",
([ "_INTERNAL_target_jabber": vars["_INTERNAL_target_jabber"],
"_INTERNAL_source_jabber" : placejid + "/" + RESOURCEPREP(rendered[i]),
"_nick" : rendered[i],
"_source_relay" : mkjid(vars["_list_members"][i]),
"_duty" : "none" ]),
source);
}
}
vars["_source_relay"] = mkjid(target);
vars["_duty"] = "none";
/*
vars["_INTERNAL_source_jabber"] = mkjid(source,
([ "_nick" : vars["_nick_local"] || vars["_nick"] ]),
0, 0, target),
*/
render("_echo_place_enter", "", vars, source);
# endif
return 1;
#endif
} else if (abbrev("_error_place_enter", mc)) {
mc = "_error_place_enter";
// can we render data string here?
data = psyctext("", vars, data, source);
// vielleicht sollte man hier das ganze nochmal als
// msg() hinterherschieben, irgendwie finden jabba-clients
// es nicht notwendig, den text zu zeigen...
// traurig eigentlich
} else if (abbrev("_notice_presence_away", mc)) {
// mh... for muve2muve it would be better to
// extend jabber with an 'automatic' flag
// but they could use psyc anyway :)
mc = "_notice_presence_away";
}
else if (abbrev("_request_enter", mc) /* remote jabber join */
|| abbrev("_request_context_enter", mc)) {
mc = "_request_enter";
#ifdef MUCSUC
t = XMPP+ vars["_INTERNAL_target_jabber_bare"]
+MUCSUC_SEP+ vars["_nick"];
P4(("MUCSUC/render1: %O of %O\n", t, 0 && vars))
set_context(clone_object(JABBER_PATH "remotemuc"), t);
#endif
source->chain_callback(vars["_tag"], (:
P3(("remote jabber part echo with %O!\n", $2))
// FIXME:
// if there is a 201 status code we need
// to send extra mumbo jumbo
$2["_tag_reply"] = $3["@id"];
$2["_nick_place"] = $2["_INTERNAL_identification"];
#ifdef MUCSUC
$2["_source_relay"] = source;
t = XMPP+ vars["_INTERNAL_target_jabber_bare"]
+MUCSUC_SEP+ vars["_nick"];
P4(("MUCSUC/render2: %O of %O\n", t, 0 && vars))
// echo place enter has not context (yet)
//$2["_context"] = vars["_INTERNAL_target_jabber_bare"] + ...
// m_delete($2, "_nick");
#else
t = $1;
#endif
if ($3["@type"] == "error") {
// FIXME: could remove context
//
// also, we should implement the full choice of errors and
// map them to appropriate psyc errors.. instead we just
// have this silly lazy coder's message:
return ({ t, "_failure_place_enter_XMPP",
"[_nick_place] could not be entered for jabberish reasons.",
$2 });
} else {
#ifdef MUCSUC
return ({ t, "_echo_place_enter", 0, $2 });
#else // ugly old code
// since we fake _context we also have to use _notice, not _echo
// can we send _echo_place_enter_unicast and instead
// make sure we don't provide a _context?
return ({ $1, "_notice_place_enter_unicast_INTERNAL_ECHO",
"[_nick] enters a unicast chatroom at [_nick_place].",
$2 });
#endif
}
:));
} else if (abbrev("_request_leave", mc)
|| abbrev("_request_context_leave", mc)) {
mc = "_request_leave";
source->chain_callback(vars["_tag"], (:
P3(("remote jabber part echo with %O!\n", $2))
$2["_tag_reply"] = $3["@id"];
$2["_nick_place"] = $2["_INTERNAL_identification"];
$2["_nick"] = $2["_INTERNAL_source_resource"];
#ifdef MUCSUC
// single-user channels on top of xmpp URI scheme.. funny
// whatever does the job.
$2["_context"] = XMPP+ vars["_INTERNAL_target_jabber_bare"]
// should i try $2["_nick"] instead?
+MUCSUC_SEP+ vars["_nick"];
P3(("MUCSUC/render3: %O\n", $2["_context"]))
#else
$2["_context"] = $2["_INTERNAL_identification"];
#endif
return ({ $1,
"_notice_place_leave",
"You leave [_nick_place].",
$2 });
:));
} /* remote jabber join */
else if (abbrev("_message_announcement", mc)) return 1;
else if (abbrev("_failure_redirect", mc)) {
if (vars["_tag_reply"]) { // wild guess that it is an iq then
mc = "_jabber_iq_error";
// <lynX> was spricht dagegen _failure_redirect als <redirect/> auszugeben?
// <fippo> ich denke nicht, dass es irgendwer vern<72>nftig implementiert...
// au<61>erdem musst du die jid des raumes in dem konkreten fall rausfinden
vars["_jabber_XML"] = "<error type='modify'><gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/><text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang='en'>" + psyctext(data, vars) + "</text></error>";
}
}
}
render(mc, data, vars, source);
return 1;
}