diff --git a/world/net/http/fetch.c b/world/net/http/fetch.c index e24be74..e72338e 100644 --- a/world/net/http/fetch.c +++ b/world/net/http/fetch.c @@ -30,6 +30,7 @@ volatile mapping rheaders = (["User-Agent": SERVER_VERSION]); volatile string http_message; volatile int http_status, port, fetching, ssl; volatile string buffer, thehost, url, fetched, host, resource, method; +volatile mixed rbody; int parse_status(string all); int parse_header(string all); @@ -37,8 +38,9 @@ int buffer_content(string all); string qHost() { return thehost; } -varargs void fetch(string murl, string meth, mapping hdrs) { +varargs void fetch(string murl, string meth, mixed body, mapping hdrs) { method = meth || "GET"; + rbody = body; if (hdrs) rheaders += hdrs; if (url != murl) { // accept.c does this for us: @@ -96,18 +98,28 @@ varargs int real_logon(int failure) { unless (url) return -3; unless (resource) sscanf(url, "%s://%s/%s", scheme, host, resource); + string body = ""; + if (stringp(rbody)) { + body = rbody; + } else if (mappingp(rbody) && sizeof(rbody)) { + body = make_query_string(rbody); + unless (rheaders["Content-Type"]) + rheaders["Content-Type"] = "application/x-www-form-urlencoded"; + } + if (strlen(body)) rheaders["Content-Length"] = strlen(body); + buffer = ""; foreach (string key, string value : rheaders) { buffer += key + ": " + value + "\r\n"; } + // we won't need connection: close w/ http/1.0 //emit("Connection: close\r\n\r\n"); P2(("%O fetching /%s from %O\n", ME, resource, host)) P4(("%O using %O\n", ME, buffer)) emit(method + " /"+ resource +" HTTP/1.0\r\n" "Host: "+ host +"\r\n" - + buffer + - "\r\n"); + + buffer + "\r\n" + body); buffer = ""; next_input_to(#'parse_status); diff --git a/world/net/http/library.i b/world/net/http/library.i index 24344cf..fc173b4 100644 --- a/world/net/http/library.i +++ b/world/net/http/library.i @@ -192,7 +192,7 @@ default: return 0; } -parse_query(query, qs) { +mapping parse_query(mapping query, string qs) { foreach (string pair : explode(qs, "&")) { string key, val; @@ -207,3 +207,14 @@ parse_query(query, qs) { } return query; } + +varargs string make_query_string(mapping params, int sort) { + string q = ""; + array(mixed) keys = m_indices(params); + if (sort) keys = sort_array(keys, #'>); + + foreach(string key : keys) { + q += (strlen(q) ? "&" : "") + urlencode(to_string(key)) + "=" + urlencode(to_string(params[key])); + } + return q; +} diff --git a/world/net/http/oauth.c b/world/net/http/oauth.c index b5992c3..5aaca48 100644 --- a/world/net/http/oauth.c +++ b/world/net/http/oauth.c @@ -13,52 +13,49 @@ string consumer_key; string consumer_secret; string request_token_url; -string request_token; -string request_secret; +mapping request_params = ([ ]); +mapping access_params = ([ ]); string access_token_url; -string access_token; -string access_secret; string authorize_url; string callback_url = "http://" + my_lower_case_host() + ":" + HTTP_PORT + "/oauth"; //TODO: https? -mapping oauth = ([]); object user; -varargs void fetch(object ua, string url, string method, mapping oauth) { +varargs void fetch(object ua, string url, string method, mapping get, mapping post, mapping oauth) { P3((">> oauth:fetch(%O, %O, %O)\n", url, method, oauth)) unless (method) method = "GET"; + unless (get) get = ([]); + unless (post) post = ([]); unless (oauth) oauth = ([]); - oauth["consumer_key"] = consumer_key; - if (access_token || request_token) oauth["token"] = access_token || request_token; - string token_secret = access_token ? access_secret : request_token ? request_secret : ""; - oauth["timestamp"] = time(); - oauth["nonce"] = sprintf("%x", random(oauth["timestamp"] ^ 98987)); - oauth["signature_method"] = "HMAC-SHA1"; - oauth["version"] = "1.0"; + oauth["oauth_consumer_key"] = consumer_key; + string token; + if (token = access_params["oauth_token"] || request_params["oauth_token"]) + oauth["oauth_token"] = token; + string token_secret = access_params["oauth_token_secret"] || request_params["oauth_token_secret"] || ""; + oauth["oauth_timestamp"] = time(); + oauth["oauth_nonce"] = sprintf("%x", random(oauth["oauth_timestamp"] ^ 98987)); + oauth["oauth_signature_method"] = "HMAC-SHA1"; + oauth["oauth_version"] = "1.0"; - array(string) params = ({}); - foreach (string key : sort_array(m_indices(oauth), #'>)) //')) - params += ({"oauth_" + key + "=" + urlencode(to_string(oauth[key]))}); - string base_str = method + "&" + urlencode(url) + "&" + urlencode(implode(params, "&")); - oauth["signature"] = hmac_base64(TLS_HASH_SHA1, urlencode(consumer_secret) + "&" + urlencode(token_secret), base_str); + P3(("token: %O, token_secret: %O, access: %O, request: %O\n", token, token_secret, access_params, request_params)) + string base_str = method + "&" + urlencode(url) + "&" + urlencode(make_query_string(get + post + oauth, 1)); + oauth["oauth_signature"] = hmac_base64(TLS_HASH_SHA1, urlencode(consumer_secret) + "&" + urlencode(token_secret), base_str); - params = ({}); + string p = ""; foreach (string key, string value : oauth) - params += ({"oauth_" + key + "=\"" + urlencode(to_string(value)) + "\""}); + p += (strlen(p) ? "," : "") + key + "=\"" + urlencode(to_string(value)) + "\""; - ua->fetch(url, method, (["Authorization": "OAuth " + implode(params, ",")])); + ua->fetch(url, method, post, (["Authorization": "OAuth " + p])); } void parse_request_token(string body, mapping headers) { P3((">> oauth:parse_request_token(%O, %O)\n", body, headers)) - mapping params = ([]); - parse_query(params, body); - request_token = params["oauth_token"]; - request_secret = params["oauth_token_secret"]; - if (strlen(request_token) && strlen(request_secret)) { - shared_memory("oauth_request_tokens")[request_token] = ME; + request_params = ([]); + parse_query(request_params, body); + if (strlen(request_params["oauth_token"]) && strlen(request_params["oauth_token_secret"])) { + shared_memory("oauth_request_tokens")[request_params["oauth_token"]] = ME; sendmsg(user, "_notice_oauth_authorize_url", "Open [_url] to perform authorization.", - (["_url": authorize_url + "?oauth_token=" + request_token])); + (["_url": authorize_url + "?oauth_token=" + request_params["oauth_token"]])); } else { sendmsg(user, "_error_oauth_token_request", "OAuth failed: could not get a request token."); } @@ -66,11 +63,9 @@ void parse_request_token(string body, mapping headers) { void parse_access_token(string body, mapping headers) { P3((">> oauth:parse_access_token(%O, %O)\n", body, headers)) - mapping params = ([]); - parse_query(params, body); - access_token = params["oauth_token"]; - access_secret = params["oauth_token_secret"]; - if (strlen(access_token) && strlen(access_secret)) { + access_params = ([]); + parse_query(access_params, body); + if (strlen(access_params["oauth_token"]) && strlen(access_params["oauth_token_secret"])) { sendmsg(user, "_notice_oauth_success", "OAuth successful."); } else { sendmsg(user, "_error_oauth_token_access", "OAuth failed: could not get an access token."); @@ -81,7 +76,7 @@ void verified(string verifier) { P3((">> oauth:verified(%O)\n", verifier)) object ua = clone_object(NET_PATH "http/fetch"); ua->content(#'parse_access_token, 1, 1); //'); - fetch(ua, access_token_url, "POST", (["verifier": verifier])); + fetch(ua, access_token_url, "POST", 0, 0, (["oauth_verifier": verifier])); } object load(object usr, string key, string secret, string request, string access, string authorize) { @@ -95,7 +90,7 @@ object load(object usr, string key, string secret, string request, string access if (request_token_url && user) { object ua = clone_object(NET_PATH "http/fetch"); ua->content(#'parse_request_token, 1, 1); //'); - fetch(ua, request_token_url, "POST", (["callback": callback_url])); + fetch(ua, request_token_url, "POST", 0, 0, (["oauth_callback": callback_url])); } return ME; } diff --git a/world/net/http/server.c b/world/net/http/server.c index 06d55dd..847ceed 100644 --- a/world/net/http/server.c +++ b/world/net/http/server.c @@ -184,7 +184,7 @@ case "/oauth": //PT((">>> oauth: %O\n", oauth)) oauth->verified(query["oauth_verifier"]); m_delete(shared_memory("oauth_request_tokens"), query["oauth_token"]); - write("OAuth succeeded"); + write("OAuth succeeded, you can now return to your client."); } else { write("OAuth failed: token not found"); } diff --git a/world/net/library/signature.c b/world/net/library/signature.c index 4660024..40593f7 100644 --- a/world/net/library/signature.c +++ b/world/net/library/signature.c @@ -73,6 +73,17 @@ private volatile mapping _sigs = ([ "_request_ni": ({ "_request_nick_local", 0, "_nick_local", "_INTERNAL_stuss" }), "_request_public": ({ "_request_public", 0, "_flag_public" }), "_request_pub": ({ "_request_public", 0, "_flag_public" }), + + "_request_entries": ({ "_request_entries", 0, "_num" }), + "_request_entry": ({ "_request_entry", 0, "_id" }), + "_request_comment": ({ "_request_comment", 0, "_id", "_text" }), + "_request_thread": ({ "_request_thread", 0, "_id", "_title" }), + "_request_addentry": ({ "_request_addentry", 0, "_text" }), + "_request_submit": ({ "_request_addentry", 0, "_text" }), + "_request_blog": ({ "_request_addentry", 0, "_text" }), + "_request_delentry": ({ "_request_delentry", 0, "_id" }), + "_request_unsubmit": ({ "_request_delentry", 0, "_id" }), + "_request_unblog": ({ "_request_delentry", 0, "_id" }), #ifdef _flag_enable_module_microblogging "_request_add": ({ "_request_add", 0, "_person" }), "_request_remove": ({ "_request_remove", 0, "_person" }), diff --git a/world/net/place/threads.c b/world/net/place/threads.c index 21ce44f..c161668 100644 --- a/world/net/place/threads.c +++ b/world/net/place/threads.c @@ -40,146 +40,152 @@ create() { unless (pointerp(_thread)) _thread = ({ }); } -cmd(a, args, b, source, vars) { - P3((">> threads:cmd(%O, %O, %O, %O, %O)", a, args, b, source, vars)) -// TODO: multiline-sachen irgendwie - mapping entry; - array(mapping) entries; - int i = 0; - int id; - int num_entries; - //unless (source) source = previous_object(); - switch (a) { - case "entries": - num_entries = sizeof(args) >= 2 ? to_int(args[1]) : DEFAULT_BACKLOG; - // _thread[<5..] - //foreach( entry : _thread[= 0; i--) { - unless (entry = _thread[i]) continue; - entries = - ({ ([ - "_sep" : strlen(entry["thread"]) ? " - " : "", - "_thread" : entry["thread"], - "_text" : entry["text"], - "_author" : entry["author"], - "_date" : entry["date"], - "_comments": sizeof(entry["comments"]), - "_id" : i, - "_nick_place" : MYNICK, - ]) }) + entries; - if (sizeof(entries) == num_entries) break; - } - foreach(entry : entries) - sendmsg(source, "_list_thread_entry", - "#[_id] - [_author][_sep][_thread]: [_text] ([_comments])", - entry); - return 1; - case "entry": - unless (sizeof(args) > 1){ - sendmsg(source, "_warning_usage_entry", - "Usage: /entry ", ([ ])); - return 1; - } - id = to_int(args[1]); - if (id >= 0 && id < sizeof(_thread)) - entry = _thread[id]; +_request_entries(source, mc, data, vars, b) { + int num = to_int(vars["_num"]) || DEFAULT_BACKLOG; + array(mapping) entries = ({ }); + mapping entry; - unless (entry) { - sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); - return 1; - } - - sendmsg(source, "_list_thread_entry", - "#[_id] [_author][_sep][_thread]: [_text] ([_comments])", - ([ - "_sep" : strlen(entry["thread"]) ? " - " : "", - "_thread" : entry["thread"], - "_text" : entry["text"], - "_author" : entry["author"], - "_date" : entry["date"], - "_comments": sizeof(entry["comments"]), - "_id" : id, - "_nick_place" : MYNICK ]) ); - - if (entry["comments"]) { - foreach(mapping item : entry["comments"]) { - sendmsg(source, "_list_thread_comment", - "> [_nick]: [_text]", - ([ - "_nick" : item["nick"], - "_text" : item["text"], - "_date": item["date"], - "_nick_place" : MYNICK ]) ); - } - } - return 1; - case "thread": - unless (sizeof(args) > 2){ - sendmsg(source, "_warning_usage_thread", - "Usage: /thread ", ([ ])); - return 1; - } - id = to_int(args[1]); - unless (setSubject(id, ARGS(2))) - sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); - - return 1; - case "comment": - unless (sizeof(args) >= 2) { - sendmsg(source, "_warning_usage_reply", - "Usage: /comment <threadid> <text>", ([ ])); - return 1; - } - id = to_int(args[1]); - unless (addComment(ARGS(2), SNICKER, id)) - sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); - return 1; - case "blog": - case "submit": - case "addentry": - unless (canPost(SNICKER)) return 0; - unless (sizeof(args) >= 1) { - sendmsg(source, "_warning_usage_submit", - "Usage: /submit <text>", ([ ])); - return 1; - } - addEntry(ARGS(1), SNICKER); - return 1; - // TODO: append fuer multiline-sachen -#if 0 - case "iterator": - unless (canPost(SNICKER)) return 0; - sendmsg(source, "_notice_thread_iterator", - "[_iterator] blog entries have been requested " - "since creation.", ([ - // i suppose this wasn't intentionally using - // MMP _count so i rename it to _iterator - "_iterator" : v("iterator") - ]) ); - return 1; -#endif - case "unblog": - case "deblog": - case "delentry": - unless (canPost(SNICKER)) return 0; - id = to_int(args[1]); - if (delEntry(id, source, vars)) { - sendmsg(source, "_notice_thread_entry_removed", - "Entry #[_id] has been removed.", - ([ "_id" : id ]) ); - } else { - sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); - } - return 1; - } - return ::cmd(a, args, b, source, vars); + for (int i = sizeof(_thread) - 1; i >= 0; i--) { + unless (entry = _thread[i]) continue; + entries = + ({ ([ + "_sep" : strlen(entry["thread"]) ? " - " : "", + "_thread" : entry["thread"], + "_text" : entry["text"], + "_author" : entry["author"], + "_date" : entry["date"], + "_comments": sizeof(entry["comments"]), + "_id" : i, + "_nick_place" : MYNICK, + ]) }) + entries; + if (sizeof(entries) == num) break; + } + foreach(entry : entries) + sendmsg(source, "_list_thread_entry", + "#[_id] - [_author][_sep][_thread]: [_text] ([_comments])", + entry); + return 1; } +_request_entry(source, mc, data, vars, b) { + unless (vars["_id"] && strlen(vars["_id"])) { + sendmsg(source, "_warning_usage_entry", + "Usage: /entry <id>", ([ ])); + return 1; + } + + mapping entry; + int id = to_int(vars["_id"]); + + if (id >= 0 && id < sizeof(_thread)) + entry = _thread[id]; + + unless (entry) { + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + return 1; + } + + sendmsg(source, "_list_thread_entry", + "#[_id] [_author][_sep][_thread]: [_text] ([_comments])", + ([ + "_sep" : strlen(entry["thread"]) ? " - " : "", + "_thread" : entry["thread"], + "_text" : entry["text"], + "_author" : entry["author"], + "_date" : entry["date"], + "_comments": sizeof(entry["comments"]), + "_id" : id, + "_nick_place" : MYNICK ]) ); + + if (entry["comments"]) { + foreach(mapping item : entry["comments"]) { + sendmsg(source, "_list_thread_comment", + "> [_nick]: [_text]", + ([ + "_nick" : item["nick"], + "_text" : item["text"], + "_date": item["date"], + "_nick_place" : MYNICK ]) ); + } + } + return 1; +} + +_request_thread(source, mc, data, vars, b) { + unless (vars["_id"] && strlen(vars["_id"])) { + sendmsg(source, "_warning_usage_thread", + "Usage: /thread <id> <title>", ([ ])); + return 1; + } + + int id = to_int(vars["_id"]); + unless (setSubject(id, vars["_title"])) + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + + return 1; +} + +_request_comment(source, mc, data, vars, b) { + unless (vars["_id"] && strlen(vars["_id"]) && + vars["_text"] && strlen(vars["_text"])) { + sendmsg(source, "_warning_usage_reply", + "Usage: /comment <id> <text>", ([ ])); + return 1; + } + + int id = to_int(vars["_id"]); + unless (addComment(vars["_text"], SNICKER, id)) + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + + return 1; +} + +_request_addentry(source, mc, data, vars, b) { + unless (canPost(SNICKER)) return 0; + unless (vars["_text"] && strlen(vars["_text"])) { + sendmsg(source, "_warning_usage_addentry", + "Usage: /addentry <text>", ([ ])); + return 1; + } + addEntry(vars["_text"], SNICKER); + return 1; +} + +_request_delentry(source, mc, data, vars, b) { + unless (canPost(SNICKER)) return 0; + unless (vars["_id"] && strlen(vars["_id"])) { + sendmsg(source, "_warning_usage_delentry", + "Usage: /delentry <id>", ([ ])); + return 1; + } + int id = to_int(vars["_id"]); + if (delEntry(id, source, vars)) { + sendmsg(source, "_notice_thread_entry_removed", + "Entry #[_id] has been removed.", + ([ "_id" : id ]) ); + } else { + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + } + return 1; +} + +#if 0 +_request_iterator(source, mc, data, vars, b) { + unless (canPost(SNICKER)) return 0; + sendmsg(source, "_notice_thread_iterator", + "[_iterator] blog entries have been requested " + "since creation.", ([ + // i suppose this wasn't intentionally using + // MMP _count so i rename it to _iterator + "_iterator" : v("iterator") + ]) ); + return 1; +#endif + msg(source, mc, data, vars){ P3(("thread:msg(%O, %O, %O, %O)", source, mc, data, vars)) // TODO: die source muss hierbei uebereinstimmen mit dem autor diff --git a/world/net/place/userthreads.c b/world/net/place/userthreads.c index 4e997d5..e62e3d3 100644 --- a/world/net/place/userthreads.c +++ b/world/net/place/userthreads.c @@ -139,12 +139,16 @@ _request_twitter(source, mc, data, vars, b) { vSet("twitter", 0); save(); } - DT(else if (sw == "test") twitter->home();) sendmsg(source, "_status_twitter", "Twitter submission is [_status].", (["_status": v("twitter") ? "enabled" : "disabled"])); return 1; } +addEntry(text, unick, thread) { + if (::addEntry(text, unick, thread) && v("twitter") && twitter) + twitter->status_update(text); +} + htMain(int last) { return htmlEntries(_thread, last, 1, channel); } diff --git a/world/net/twitter/client.c b/world/net/twitter/client.c index 58e723d..5d43138 100644 --- a/world/net/twitter/client.c +++ b/world/net/twitter/client.c @@ -2,9 +2,10 @@ * * - register @ http://twitter.com/apps * - settings: - * - name: e.g. psyc://your.host/ + * - app name: e.g. psyc://your.host/ * - app type: browser * - callback url: http://your.host/oauth + * (actually the url psyced sends will be used but you have to type in something) * - access type: read/write * - then in local.h #define TWITTER_KEY & TWITTER_SECRET */ @@ -22,12 +23,26 @@ object load(object usr, string key, string secret, string request, string access return ::load(usr, key, secret, request, access, authorize); } -void parse_home(string body, string headers) { - P3(("twitter/client:parse_home(%O, %O)\n", body, headers)) +void parse_status_update(string body, string headers) { + P3(("twitter/client:parse_status_update(%O, %O)\n", body, headers)) } -void home() { +void status_update(string text) { + P3(("twitter/client:status_update()\n")) + if (strlen(text) > 140) text = text[0..136] + "..."; + object ua = clone_object(NET_PATH "http/fetch"); - ua->content(#'parse_home, 1, 1); //'); + ua->content(#'parse_status_update, 1, 1); //'); + fetch(ua, "http://api.twitter.com/1/statuses/update.json", "POST", 0, (["status": text])); +} + +void parse_home_timeline(string body, string headers) { + P3(("twitter/client:parse_home_timeline(%O, %O)\n", body, headers)) +} + +void home_timeline() { + P3(("twitter/client:home_timeline()\n")) + object ua = clone_object(NET_PATH "http/fetch"); + ua->content(#'parse_home_timeline, 1, 1); //'); fetch(ua, "http://api.twitter.com/1/statuses/home_timeline.json"); }