diff --git a/CHANGESTODO b/CHANGESTODO index d294745..fcad647 100644 --- a/CHANGESTODO +++ b/CHANGESTODO @@ -6,6 +6,9 @@ Essentially: whenever you fix something, move that line to the end of file. ________________________________________________________________________ == NEXT RELEASE ======================================================== ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ +- autorefetch twitter isnt working + http/fetch is too complicated. throw out the queue and callback logic. kiss! + ? support tls multiplexing on all suitable ports ? bugs in psyced install procedure @@ -35,6 +38,28 @@ ________________________________________________________________________ im psyc://psyced.org/~lynx Context festgestellt. ? who's gonna clean up the mess of having too many websites ? + +- when provided with a _focus pointing to yourself, _request_execute will + still try to forward a command to the "current place" - a uniform or + object is passed to parsecmd(data, dest) but dest is ignored later in cmd() + +- _source_relay should point to tg, not foo (only happens with local users) + . + :_source.psyc://x-net.hu/~foo + :_target.psyc://3e44aa49.XXX:-52801/ + :_source_relay.psyc://x-net.hu/~foo + + :_nick.tg + :_time_INTERNAL.1239787956 + :_nick_target.psyc://x-net.hu/~foo + _message_private + halo + . + +- the linking blues... + tgX sagt: on reconnect i didn't get any _echo_place_enter_login's from this room, then no response for _request_do_enter, and after a _request_do_leave i get replies for enter/leave but instead of _echo_place_enter/leave i get _notice_place_enter/leave + +? should /load inform that errors go to the console? ________________________________________________________________________ == psyced 1.0 ========================================================== ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ @@ -3895,3 +3920,12 @@ spyc/* psyc/* install.sh + don't leave _host_IP uncommented + mkdir data/* log/* +=== 200904 ============================================================ +net/twitter ++ polly: periodically fetch updates from a twitter account and distribute to + hundreds of newsfeed contexts ++ listing: provide a listing of subscribed feeds +http/fetch +- minor api cleanups to serve a more generic job +library/dns ++ honor #define _flag_disable_trust_localhost diff --git a/bin/psyconf b/bin/psyconf index 7b4d5ab..e84a32b 100755 --- a/bin/psyconf +++ b/bin/psyconf @@ -489,6 +489,10 @@ fi if test "\$1" = "-m" then + # git is seriously unsuited for this job. we need to "check in" the changes + # to file permissions etc or otherwise we can't update things at all... + (cd $base && git commit -a -m 'automatic by psyced -m') + # merging will of course fail.. sigh (cd $base && git merge -s resolve origin) exit fi diff --git a/world/default/en/html.textdb b/world/default/en/html.textdb index 33b8540..91c5c51 100644 --- a/world/default/en/html.textdb +++ b/world/default/en/html.textdb @@ -1423,6 +1423,24 @@ _HTML_examine_friend_new _HTML_examine_button_call |    {_TEXT_button_call} +_HTML_listing_head_twitter +|twitter polly listing of available feeds +| +|
+|
    + +_HTML_listing_tail_twitter +|
+ +_HTML_listing_item_twitter +|
  • +| +|## [_nick] +|[_name]
    +|[_description]
    +|[_uniform_context]
    +|
  • + _HTML_call | | diff --git a/world/default/en/plain.textdb b/world/default/en/plain.textdb index dcdb5ba..23a3a5e 100644 --- a/world/default/en/plain.textdb +++ b/world/default/en/plain.textdb @@ -696,6 +696,9 @@ _message_public _message_friends |[_nick] {_TEXT_action_shouts}: [_data] +_message_twitter +|[_nick] ... [_data] + _message_echo_private |You tell [_nick_target]: [_data] diff --git a/world/drivers/ldmud/include/interface.h b/world/drivers/ldmud/include/interface.h index a1dd9e0..493f7df 100644 --- a/world/drivers/ldmud/include/interface.h +++ b/world/drivers/ldmud/include/interface.h @@ -95,6 +95,7 @@ // nosave? static? volatile. only for variables, not methods! // another nice word for the opposite of persistent would be "shed" #define volatile nosave +#define persistent // every lpc dialect has its own foreach syntax. aint that cute? #define each(ITERATOR, LIST) foreach(ITERATOR : LIST) diff --git a/world/drivers/ldmud/master/accept.c b/world/drivers/ldmud/master/accept.c index 00fff09..429a3fb 100644 --- a/world/drivers/ldmud/master/accept.c +++ b/world/drivers/ldmud/master/accept.c @@ -372,6 +372,8 @@ object compile_object(string file) { // match both http:/ and https:/ objects ;D if (abbrev("http", file)) { rob = clone_object(HTTP_PATH "fetch"); + // driver has the habit of removing double slash in object name + file = replace(file, ":/", "://"); if (rob) rob->fetch(file[..<3]); return rob; } diff --git a/world/drivers/pike/include/interface.h b/world/drivers/pike/include/interface.h index 2bfbefa..2fbd5a4 100644 --- a/world/drivers/pike/include/interface.h +++ b/world/drivers/pike/include/interface.h @@ -83,6 +83,7 @@ // nosave? static? volatile. no idea if pike has something like this #define volatile static +#define persistent // every lpc dialect has its own foreach syntax. aint that cute? #define each(ITEM, LIST) foreach(LIST; ; ITEM) diff --git a/world/net/connect.c b/world/net/connect.c index f7c9301..34c70e2 100644 --- a/world/net/connect.c +++ b/world/net/connect.c @@ -23,6 +23,7 @@ protected int block() { destruct(ME); return 0; } protected connect_failure(mc, text) { is_connecting = 0; + P1(("connect failure %O in %O, %O.\n", mc, ME, text)) monitor_report("_failure_network_connect"+ mc, object_name(ME) +" · "+ text); } @@ -42,7 +43,7 @@ protected int logon(int failure) { } is_connecting = 0; if (failure == -1 || !interactive(ME)) { - PT(("Warning: Failed connect attempt in %O\n", ME)) + P3(("Warning: Failed connect attempt in %O\n", ME)) #if __EFUN_DEFINED__(errno) connect_failure("_attempt", sprintf("connect failed: errno %s", errno())); diff --git a/world/net/http/fetch.c b/world/net/http/fetch.c index 5800b77..4f3ebb0 100644 --- a/world/net/http/fetch.c +++ b/world/net/http/fetch.c @@ -2,8 +2,13 @@ // // generic HTTP GET client, mostly used for RSS - // but we could fetch any page or data with it, really -// tobij even made the object have the URL as its object name. fancy! ;) -// +// tobij even allowed the object to have the URL as its object name. fancy! ;) + +#ifdef Dfetch +# undef DEBUG +# define DEBUG Dfetch +#endif + #include #include #include @@ -24,7 +29,7 @@ volatile string modificationtime, etag, http_message; volatile string useragent = SERVER_VERSION; volatile int http_status, port, fetching, ssl; volatile string buffer, thehost, url, fetched, host, resource; -volatile string basicauth; +volatile string basicauth = ""; int parse_status(string all); int parse_header(string all); @@ -33,22 +38,28 @@ int buffer_content(string all); string qHost() { return thehost; } void fetch(string murl) { - if (url) return; - url = replace(murl, ":/", "://"); - P3(("%O: fetch(%O)\n", ME, url)) - connect(); + if (url != murl) { + // accept.c does this for us: + //url = replace(murl, ":/", "://"); + // so we can use this method also in a normal way + url = murl; + // resource may need to be re-parsed (other params) + resource = 0; + // re-parse the hostname? + //thehost = port = 0; + } + P3(("%O: fetch(%O)\n", ME, url)) + unless (fetching) connect(); } object load() { return ME; } -void setHTTPBasicAuth(string user, string password) { - basicauth = "Authorization: Basic " + encode_base64(user + ":" + password) + "\r\n"; +void sAuth(string user, string password) { + basicauth = "Authorization: Basic "+ + encode_base64(user +":"+ password) +"\r\n"; } -string sAgent(string a) { - PT(("sAgent(%O) in %O\n", a, ME)) - return useragent = a; -} +string sAgent(string a) { return useragent = a; } // net/place/news code follows. @@ -65,7 +76,7 @@ void connect() { thehost = lower_case(thehost); ssl = t == "s"; } - P3(("URL, THEHOST: %O, %O\n", url, thehost)) + P4(("URL, THEHOST: %O, %O\n", url, thehost)) unless (port) unless (sscanf(thehost, "%s:%d", thehost, port) == 2) port = ssl? HTTPS_SERVICE: HTTP_SERVICE; @@ -73,18 +84,18 @@ void connect() { ::connect(thehost, port); } -varargs int real_logon(int arg) { +varargs int real_logon(int failure) { string scheme; headers = ([ ]); http_status = 500; http_message = "(failure)"; // used by debug only - unless(::logon(arg)) return -1; + unless(::logon(failure)) return -1; unless (url) return -3; unless (resource) sscanf(url, "%s://%s/%s", scheme, host, resource); - buffer = ""; + buffer = basicauth; if (modificationtime) buffer += "If-Modified-Since: "+ modificationtime + "\r\n"; if (useragent) buffer += "User-Agent: "+ useragent +"\r\n"; @@ -92,7 +103,8 @@ varargs int real_logon(int arg) { // emit("If-None-Match: " + etag + "\r\n"); // we won't need connection: close w/ http/1.0 //emit("Connection: close\r\n\r\n"); - PT(("%O using %O\n", ME, buffer)) + P1(("%O fetching /%s from %O\n", ME, resource, host)) + P4(("%O using %O\n", ME, buffer)) emit("GET /"+ resource +" HTTP/1.0\r\n" "Host: "+ host +"\r\n" + buffer + @@ -103,7 +115,7 @@ varargs int real_logon(int arg) { return 0; // duh. } -varargs int logon(int arg, int sub) { +varargs int logon(int failure, int sub) { // net/connect disables telnet for all robots and circuits #if 0 //__EFUN_DEFINED__(enable_telnet) // when fetching the spiegel rss feed, telnet_neg() occasionally @@ -112,9 +124,9 @@ varargs int logon(int arg, int sub) { enable_telnet(0); #endif // when called from xmlrpc.c we can't do TLS anyway - if (sub) return ::logon(arg); + if (sub) return ::logon(failure); if (ssl) tls_init_connection(ME, #'real_logon); - else real_logon(arg); + else real_logon(failure); return 0; // duh. } @@ -124,8 +136,9 @@ int parse_status(string all) { sscanf(all, "%s%t%s", prot, state); sscanf(state, "%d%t%s", http_status, http_message); - P3(("%O got %O %O from %O\n", ME, http_status, http_message, host)); if (http_status != R_OK) { + P1(("%O got %O %O from %O\n", ME, + http_status, http_message, host)); monitor_report("_failure_unsupported_code_HTTP", S("http/fetch'ing %O returned %O %O", url || ME, http_status, http_message)); @@ -136,9 +149,9 @@ int parse_status(string all) { int parse_header(string all) { string key, val; - D2(D("htroom::parse is: " + all + "\n");) // TODO: parse status code if (all != "") { + P2(("http/fetch::parse_header %O\n", all)) if (sscanf(all, "%s:%1.0t%s", key, val) == 2) { headers[lower_case(key)] = val; // P2(("ht head: %O = %O\n", key, val)) @@ -147,6 +160,7 @@ int parse_header(string all) { return 1; } else { // das wollen wir nur bei status 200 + P2(("%O now waiting for http body\n", ME)) next_input_to(#'buffer_content); return 1; } @@ -154,70 +168,72 @@ int parse_header(string all) { } int buffer_content(string all) { + P2(("%O body %O\n", ME, all)) buffer += all + "\n"; next_input_to(#'buffer_content); return 1; } disconnected(remainder) { - headers["_fetchtime"] = isotime(ctime(time()), 1); - if (headers["last-modified"]) + P2(("%O got disconnected.. %O\n", ME, remainder)) + headers["_fetchtime"] = isotime(ctime(time()), 1); + if (headers["last-modified"]) modificationtime = headers["last-modified"]; - if (headers["etag"]) + if (headers["etag"]) etag = headers["etag"]; // heise does not work with etag - fetched = buffer; - if (remainder) fetched += remainder; - fheaders = headers; - buffer = headers = 0; - switch (http_status) { - case R_OK: - mixed *waiter; - while (qSize(ME)) { - waiter = shift(ME); - funcall(waiter[0], fetched, waiter[1] ? fheaders : copy(fheaders)); + fetched = buffer; + if (remainder) fetched += remainder; + fheaders = headers; + buffer = headers = 0; + switch (http_status) { + case R_OK: + mixed *waiter; + while (qSize(ME)) { + waiter = shift(ME); + P2(("%O calls back.. body is %O\n", ME, fetched)) + funcall(waiter[0], fetched, waiter[1] ? fheaders : copy(fheaders)); + } + break; + default: + // doesn't seem to get here when HTTP returns 301 or 302. strange. + // fall thru + case R_NOTMODIFIED: + qDel(ME); + qInit(ME, 150, 5); } - break; - default: - // doesn't seem to get here when HTTP returns 301 or 302. strange. - // fall thru - case R_NOTMODIFIED: - qDel(ME); - qInit(ME, 150, 5); - } - - fetching = 0; - return 1; // presume this disc was expected + fetching = 0; + return 1; // presume this disc was expected } varargs string content(closure cb, int force, int willbehave) { - if (cb) { - if (fetched) { - if (force) { - funcall(cb, fetched, willbehave ? fheaders : copy(fheaders)); + if (cb) { + if (fetched) { + if (force) { + funcall(cb, fetched, willbehave ? fheaders : copy(fheaders)); + } + } else { + enqueue(ME, ({ cb, willbehave })); } - } else { - enqueue(ME, ({ cb, willbehave })); } - } - return fetched; + return fetched; } varargs mapping headers(int willbehave) { - return willbehave ? fheaders : copy(fheaders); + return willbehave ? fheaders : copy(fheaders); } string qHeader(mixed key) { - if (mappingp(fheaders)) return fheaders[key]; - return 0; + if (mappingp(fheaders)) return fheaders[key]; + return 0; } varargs void refetch(closure cb, int willbehave) { - enqueue(ME, ({ cb, willbehave })); - unless (fetching) connect(); + enqueue(ME, ({ cb, willbehave })); + unless (fetching) connect(); } protected create() { - qCreate(); - qInit(ME, 150, 5); + qCreate(); + qInit(ME, 150, 5); } diff --git a/world/net/http/xmlrpc.c b/world/net/http/xmlrpc.c index 75c8a67..e65292d 100644 --- a/world/net/http/xmlrpc.c +++ b/world/net/http/xmlrpc.c @@ -189,6 +189,7 @@ mixed unMarshal(XMLNode parsed) { } } +// why is this almost the same code as in http/fetch? int disconnected(string remainder) { mixed *args; diff --git a/world/net/irc/user.c b/world/net/irc/user.c index 93fc161..edbd371 100644 --- a/world/net/irc/user.c +++ b/world/net/irc/user.c @@ -414,12 +414,20 @@ w(string mc, string data, mapping vars, mixed source) { # ifdef ALIASES if (raliases[source]) { nick2 = raliases[source]; +# if 0 vars["_INTERNAL_source_IRC"] = nick2 +"!"+ u[UNick]? u[UNick] +"@"+ u[UHost] : (vars["_nick_long"] || vars["_INTERNAL_nick_plain"] || vars["_nick"]) +"@alias.undefined"; +# else + vars["_INTERNAL_source_IRC"] = nick2 +"!"+ + (u[UNick]? u[UNick] +"@"+ u[UHost] + : (vars["_nick_long"] + || vars["_INTERNAL_nick_plain"] + || vars["_nick"])); +# endif } unless (nick2) { diff --git a/world/net/library/dns.c b/world/net/library/dns.c index b558379..cd96d42 100644 --- a/world/net/library/dns.c +++ b/world/net/library/dns.c @@ -107,7 +107,7 @@ int legal_host(string ip, int port, string scheme, int udpflag) { // proto.h! // we sincerely hope our kernel will drop udp packets that // spoof they are coming from localhost.. would be ridiculous if not // if (ip == "127.0.0.1" || ip == "127.1" || ip == "0") return 9; -#ifndef DONT_TRUST_LOCALHOST +#ifndef _flag_disable_trust_localhost if (localhosts[ip]) return 9; if (ip == myIP || !ip) return 8; #endif @@ -123,7 +123,7 @@ int legal_domain(string host, int port, string scheme, int udpflag) { // spoof they are coming from localhost.. would be ridiculous if not // // TODO: use is_localhost here? -#ifndef DONT_TRUST_LOCALHOST +#ifndef _flag_disable_trust_localhost if (host == "localhost" || host == SERVER_HOST) return 9; #endif // if (host == myLowerCaseHost) return 8; diff --git a/world/net/library/jsonparser.pike b/world/net/library/jsonparser.pike index 0cd2b75..dacd0d7 100644 --- a/world/net/library/jsonparser.pike +++ b/world/net/library/jsonparser.pike @@ -367,6 +367,7 @@ mapping jsonObject() } if (nextClean() != ':') { + PT(("jsonFAIL: '%c' at %O\n", nextClean(), myIndex)) THROW("Expected a ':' after a key.\n"); } diff --git a/world/net/person.c b/world/net/person.c index eec58b2..2b80bc0 100644 --- a/world/net/person.c +++ b/world/net/person.c @@ -1315,6 +1315,7 @@ case "_message_echo_private": // fall thru case "_message_echo_public": case "_message_echo": +case "_message_twitter": case "_message_public": // avoid treating this as _message here break; @@ -1323,6 +1324,9 @@ case "_message_audio": // not being displayed to users other than psyc clients data = 0; break; +// we should judge our messages by their routing method, not by their +// name! thus, the _public and _private distinction has to exist only +// for display. FIXME case "_message": // this is only visible in person.c, not user.c // therefore probably useless @@ -1372,24 +1376,33 @@ case "_request_execute": // our places if (places[t]) { vSet("place", place = t); + PT(("REQ-EX place %O\n", t)) } else { // see if it is a local object object o = psyc_object(t); - - // object one of our places? - if (o && places[o]) { - place = o; - vSet("place", o->qName()); + if (o) { + // object one of our places? + if (places[o]) { + place = o; + vSet("place", o->qName()); + PT(("REQ-EX o'place %O\n", o)) + } else { + PT(("REQ-EX object %O not found in %s's places %O\n", o, MYNICK, places)) + } } else unless (t2) { // must be a person then // ME->parsecmd(data, t); - parsecmd(data, t); + PT(("REQ-EX person %O vs %O\n", t, o)) // should be able to put o||t // here.. TODO + parsecmd(data, t); return 0; } } } + else { + PT(("REQ-EX non-string %O\n", t)) + } // ME->parsecmd(data); if (t2) { unless (request(source, t2, vars, data)) { @@ -1632,8 +1645,12 @@ case "_notice_invitation": vars["_nick_place"] : vars["_place"]); // same filtering code as couple lines further below - if (( IS_NEWBIE || !itsme && FILTERED(source)) && - (!profile || profile[PPL_NOTIFY] <= PPL_NOTIFY_PENDING)) { + if (( +#ifndef _flag_enable_unauthenticated_message_private + IS_NEWBIE || +#endif + (!itsme && FILTERED(source)) && + (!profile || profile[PPL_NOTIFY] <= PPL_NOTIFY_PENDING))) { sendmsg(source, "_failure_filter_strangers", 0, ([ "_nick" : MYNICK ]) ); unless (boss(source)) diff --git a/world/net/place/archetype.gen b/world/net/place/archetype.gen index 44a5b24..d2be82b 100644 --- a/world/net/place/archetype.gen +++ b/world/net/place/archetype.gen @@ -1354,6 +1354,7 @@ msg(source, mc, data, mapping vars) { else unless (!source || qAllowExternal(&source, mc, vars) // mayExternal hook || MEMBER(source) + || source == "/" // allow root to send everywhere || isValidRelay(source) || isValidRelay(vars["_source_relay"]) || isValidRelay(vars["_context"])) { @@ -1789,6 +1790,7 @@ cmd(a, args, b, source, vars) { return 1; # endif case "hc": + case "histclean": // in case you don't remember exactly.. it's ok case "histclear": // wieso um alles in der welt soll das jeder dürfen? // liest denn keiner die cvs kommentare? da stands // drin.. ok wir sind realistisch und schreiben es diff --git a/world/net/psyc/edit.i b/world/net/psyc/edit.i index da11828..62c9bc5 100644 --- a/world/net/psyc/edit.i +++ b/world/net/psyc/edit.i @@ -177,8 +177,8 @@ static varargs string psyc_render(mixed source, string mc, mixed data, data = data? to_string(data): ""; #endif } - else if (data == S_GLYPH_PACKET_DELIMITER || - (data[0] == C_GLYPH_PACKET_DELIMITER && data[1] == '\n') + else if (data == S_GLYPH_PACKET_DELIMITER || (strlen(data) > 1 && + data[0] == C_GLYPH_PACKET_DELIMITER && data[1] == '\n') || strstr(data, "\n" S_GLYPH_PACKET_DELIMITER "\n") != -1) { // this check shouldn't be necessary here: we should check what // people are typing in usercmd diff --git a/world/net/psyc/parse.i b/world/net/psyc/parse.i index 631b0e8..9a9e1a5 100644 --- a/world/net/psyc/parse.i +++ b/world/net/psyc/parse.i @@ -414,7 +414,8 @@ vamixed parse(string a) { #ifndef __PIKE__ if (peerip && pongtime + 120 < time()) { if (same_host(SERVER_HOST, peerip)) { - P1(("why am i talking psyc to myself?\n")) + P1(("Another PSYC node on my IP? Or am I talking to myself? %O\n", ME)) + // not ponging to ping then... } else { #ifdef PSYC_TCP P2(("%O sending TCP PONG to %O=%O\n", diff --git a/world/net/twitter/listing.c b/world/net/twitter/listing.c new file mode 100644 index 0000000..af90246 --- /dev/null +++ b/world/net/twitter/listing.c @@ -0,0 +1,126 @@ +// vim:foldmethod=marker:syntax=lpc:noexpandtab +// +// http://localhost:33333/net/twitter/listing shows a list of friends + +#include +#include +#include + +volatile object fetcha; +volatile mixed wurst; + +parse(string body) { + if (!body || body == "") { + P1(("%O failed to get its listing.\n", ME)) + return; + } +//#if DEBUG > 0 + rm(DATA_PATH "twitter/friends.json"); + write_file(DATA_PATH "twitter/friends.json", body); + P4((body)) +//#endif + unless (pointerp(wurst = parse_json(body))) { + P1(("%O failed to parse its listing.\n", ME)) + return; + } +#ifdef DEVELOPMENT + write_file(DATA_PATH "twitter/friends.parsed", sprintf("%O\n", wurst)); +#endif + P1(("%O sorting %O subscription names ", ME, sizeof(wurst))) + wurst = sort_array(wurst, (: + unless (mappingp($1)) return 0; + unless (mappingp($2)) return 1; +// PT(("%O got %O vs %O\n", ME, $1, $2)) + P1((".")) + return lower_case($2["screen_name"] || "") > + lower_case($1["screen_name"] || ""); + :) ); + P1((" done!\n")) +} + +htget(prot, query, headers, qs, data, noprocess) { + string nick; + mapping d; //, s; + int i; + + //sTextPath(query["layout"] || "twitter", query["lang"], "html"); + localize(query["lang"], "html"); + + unless (pointerp(wurst)) { + hterror(R_TEMPOVERL, + "Haven't successfully retrieved data yet."); + return; + } + htok(prot); // outputs utf-8 header, but.. + w("_HTML_listing_head_twitter"); + for (i=sizeof(wurst)-1; i>=0; i--) { + d = wurst[i]; + unless (mappingp(d)) { + P1(("%O got a broken entry: %O.\n", ME, d)) + continue; + } +// +// user "foebud" has no updates ;) +// +// s = d["status"]; +// unless (mappingp(s)) { +// P1(("%O got a statusless entry: %O.\n", ME, d)) +// continue; +// } + unless (nick = d["screen_name"]) { + P1(("%O got a nickless tweeter.\n", ME)) + continue; + } + w("_HTML_listing_item_twitter", 0, ([ + // should i send text as _action? + "_nick": nick, + "_amount_updates": d["statuses_count"], + // _count_subscribers seems to be better for this + // or should it be _recipients? _targets? + "_amount_followers": d["followers_count"], + "_amount_sources": d["friends_count"], + // shows how old listing is.. hmm + //"_description_update": s["text"] || "", + "_color": "#"+ d["profile_sidebar_fill_color"], + "_description": d["description"] || "", + "_uniform_context": SERVER_UNIFORM +"@"+ nick, + "_page": d["url"] || "", + "_name": d["name"] || "", + // "_contact_twitter": d["id"], + "_reference_reply": d["in_reply_to_screen_name"], + // "_twit": d["id"], + "_uniform_photo": d["profile_image_url"] || "", + "_uniform_photo_background": + d["profile_background_image_url"] || "" + ])); + } + w("_HTML_listing_tail_twitter"); + return 1; +} + +fetch() { + fetcha -> content( #'parse, 0, 1 ); + fetcha -> fetch("http://twitter.com/statuses/friends.json?count=200"); +} + +create() { + mapping config; + object o = find_object(CONFIG_PATH "config"); + + if (o) config = o->qConfig(); + if (!config) { + P1(("\nNo configuration for twitter gateway found in %O.\n", o)) + //destruct(ME); + return 1; + } + + string body = read_file(DATA_PATH "twitter/friends.json"); + if (body) return parse(body); + + // we could even choose to inherit this instead... + fetcha = clone_object(NET_PATH "http/fetch"); + //fetcha -> sAgent(SERVER_VERSION " builtin Twitter to PSYC gateway"); + fetcha -> sAuth(config["nickname"], config["password"]); + call_out( #'fetch, 14 ); +} + diff --git a/world/net/twitter/polly.c b/world/net/twitter/polly.c new file mode 100644 index 0000000..4d4b6e6 --- /dev/null +++ b/world/net/twitter/polly.c @@ -0,0 +1,117 @@ +// vim:foldmethod=marker:syntax=lpc:noexpandtab +// +// yeah yeah twitter.. why twitter? +// http://about.psyc.eu/Twitter + +#include + +persistent int lastid; + +volatile object feed; + +parse(string body, mapping headers) { + mixed wurst; + string nick; + object o; + mapping d, p; + int i; + + if (!body || body == "") { + P1(("%O failed to get its timeline from %O.\n", ME, + previous_object())) + PT(("Got headers: %O\n", headers)) + return; + } +//#if DEBUG > 0 + rm(DATA_PATH "timeline.json"); + write_file(DATA_PATH "timeline.json", body); + P4((body)) +//#endif + unless (pointerp(wurst = parse_json(body))) { + P1(("%O failed to parse its timeline.\n", ME)) + return; + } + unless (sizeof(wurst)) { + P1(("%O received an empty structure.\n", ME)) + return; + } + if (wurst[0]["id"] <= lastid) { + P1(("%O received %d old updates.\n", ME, sizeof(wurst))) + return; + } + lastid = wurst[0]["id"]; + save_object(DATA_PATH "twitter"); + for (i=sizeof(wurst)-1; i>=0; i--) { + d = wurst[i]; + unless (mappingp(d)) { + P1(("%O got a broken tweet: %O.\n", ME, d)) + continue; + } + p = d["user"]; + unless (mappingp(p)) { + P1(("%O got a userless tweet.\n", ME)) + continue; + } + unless (nick = p["screen_name"]) { + P1(("%O got a nickless tweeter.\n", ME)) + continue; + } + P4((" %O", nick)) + o = find_place(nick); + + // _notice_update_twitter ? + sendmsg(o, "_message_twitter", d["text"], ([ + // should i send text as _action? + "_nick": nick, + // _count seems to be the better word for this + "_amount_updates": p["statuses_count"], + "_amount_followers": p["followers_count"], + "_amount_sources": p["friends_count"], + "_color": "#"+ p["profile_sidebar_fill_color"], + "_description": p["description"] || "", + "_page": p["url"] || "", + "_name": p["name"] || "", + // "_contact_twitter": p["id"], + "_description_agent_HTML": d["source"], + "_reference_reply": d["in_reply_to_screen_name"], + // "_twit": d["id"], + "_uniform_photo": p["profile_image_url"] || "", + "_uniform_photo_background": + p["profile_background_image_url"] || "" + ]), "/"); // send as root + + // der spiegel u.a. twittern übrigens in latin-1 + // während psyc utf-8 erwartet.. eine char guess engine + // muss her.. FIXME + } +} + +fetch() { + P1(("%O going to fetch from %O since %O\n", ME, feed, lastid)) + call_out( #'fetch, 4 * 59 ); // odd is better + feed -> content( #'parse, 1, 1 ); + // twitter ignores since_id if count is present. stupid. + feed -> fetch("http://twitter.com/statuses/friends_timeline.json?" + // +( lastid? ("since_id="+ lastid) : "count=23")); + "count="+( lastid? ("23&since_id="+ lastid) : "23")); +} + +create() { + mapping config; + object o = find_object(CONFIG_PATH "config"); + + if (o) config = o->qConfig(); + if (!config) { + P1(("\nNo configuration for twitter gateway found.\n")) + //destruct(ME); + return; + } + restore_object(DATA_PATH "twitter"); + + // we could even choose to inherit this instead... + feed = clone_object(NET_PATH "http/fetch"); + //feed -> sAgent(SERVER_VERSION " builtin Twitter to PSYC gateway"); + feed -> sAuth(config["nickname"], config["password"]); + call_out( #'fetch, 14 ); +} + diff --git a/world/net/user.c b/world/net/user.c index a2e132d..9f5a420 100644 --- a/world/net/user.c +++ b/world/net/user.c @@ -615,6 +615,9 @@ case "_jabber_iq_set": case "_jabber": P1(("%O got %O", ME, mc)) break; +case "_notice_composing_media": + if (v("scheme") != "psyc") return 1; + break; case "_message_video": case "_message_audio": // not being displayed to users other than psyc clients @@ -753,6 +756,7 @@ case "_message_announcement": case "_message_behaviour_warning": case "_message_behaviour_punishment": case "_message_behaviour": +case "_message_twitter": unless (stringp(data)) variant = "_default"; else variant = ""; #ifdef USE_THE_NICK diff --git a/world/static/twitter/listing.css b/world/static/twitter/listing.css new file mode 100644 index 0000000..6c2dc84 --- /dev/null +++ b/world/static/twitter/listing.css @@ -0,0 +1,31 @@ +* { + margin:0;padding:0;border-width:0;border-color:transparent; +} +a { + text-decoration:none; + color:#06a; +} +a:hover { + text-decoration:overline; +} +body{ + font-family: 'Lucida Grande',helvetica,sans-serif; + color:#333; + background-color:#cc6; + margin:44px; +} +.listing { + background-color:#ddd; + border-left: 2px solid black; + border-right: 2px solid black; +} + +.listing li:first-child{ border-top:1px dashed black; } +.listing li{position:relative;padding:7;border-bottom:1px dashed black;line-height:1.1em;} +.listing li:hover,ol.listing li.hover{background-color:white;} + +._photo{display:block;width:50px;height:50px;position:absolute;right:7;margin:0 0 0 0;} +._photo img{width:48px;height:48px;} +._description{font-size:70%;} +._uniform{font-size:80%;} +._uniform a{color:#c30}