mirror of
git://git.psyced.org/git/psyced
synced 2024-08-15 03:25:10 +00:00
oauth & twitter client
/twitter on & /twitter test in a user channel to test
This commit is contained in:
parent
791f671bf4
commit
a5164da136
10 changed files with 231 additions and 35 deletions
|
@ -19,6 +19,7 @@ inherit PRO_PATH "http/library2";
|
|||
inherit NET_PATH "library/sandbox";
|
||||
# endif
|
||||
inherit NET_PATH "library/base64";
|
||||
inherit NET_PATH "library/hmac";
|
||||
inherit NET_PATH "library/dns";
|
||||
inherit NET_PATH "library/htbasics";
|
||||
inherit NET_PATH "library/json";
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <net.h>
|
||||
#include <uniform.h>
|
||||
#include <services.h>
|
||||
#include <regexp.h>
|
||||
|
||||
virtual inherit NET_PATH "output"; // virtual: in case we get inherited..
|
||||
inherit NET_PATH "connect";
|
||||
|
@ -25,11 +26,10 @@ inherit NET_PATH "queue";
|
|||
#endif
|
||||
|
||||
volatile mapping headers, fheaders;
|
||||
volatile string modificationtime, etag, http_message;
|
||||
volatile string useragent = SERVER_VERSION;
|
||||
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;
|
||||
volatile string basicauth = "";
|
||||
volatile string buffer, thehost, url, fetched, host, resource, method;
|
||||
|
||||
int parse_status(string all);
|
||||
int parse_header(string all);
|
||||
|
@ -37,7 +37,9 @@ int buffer_content(string all);
|
|||
|
||||
string qHost() { return thehost; }
|
||||
|
||||
void fetch(string murl) {
|
||||
varargs void fetch(string murl, string meth, mapping hdrs) {
|
||||
method = meth || "GET";
|
||||
if (hdrs) rheaders += hdrs;
|
||||
if (url != murl) {
|
||||
// accept.c does this for us:
|
||||
//url = replace(murl, ":/", "://");
|
||||
|
@ -55,11 +57,10 @@ void fetch(string murl) {
|
|||
object load() { return ME; }
|
||||
|
||||
void sAuth(string user, string password) {
|
||||
basicauth = "Authorization: Basic "+
|
||||
encode_base64(user +":"+ password) +"\r\n";
|
||||
rheaders["Authorization"] = "Basic " + encode_base64(user +":"+ password);
|
||||
}
|
||||
|
||||
string sAgent(string a) { return useragent = a; }
|
||||
string sAgent(string a) { return rheaders["User-Agent"] = a; }
|
||||
|
||||
// net/place/news code follows.
|
||||
|
||||
|
@ -95,17 +96,15 @@ varargs int real_logon(int failure) {
|
|||
unless (url) return -3;
|
||||
unless (resource) sscanf(url, "%s://%s/%s", scheme, host, resource);
|
||||
|
||||
buffer = basicauth;
|
||||
if (modificationtime)
|
||||
buffer += "If-Modified-Since: "+ modificationtime + "\r\n";
|
||||
if (useragent) buffer += "User-Agent: "+ useragent +"\r\n";
|
||||
//if (etag)
|
||||
// emit("If-None-Match: " + etag + "\r\n");
|
||||
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("GET /"+ resource +" HTTP/1.0\r\n"
|
||||
emit(method + " /"+ resource +" HTTP/1.0\r\n"
|
||||
"Host: "+ host +"\r\n"
|
||||
+ buffer +
|
||||
"\r\n");
|
||||
|
@ -178,9 +177,9 @@ disconnected(remainder) {
|
|||
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"])
|
||||
etag = headers["etag"]; // heise does not work with etag
|
||||
rheaders["If-Modified-Since"] = headers["last-modified"];
|
||||
//if (headers["etag"])
|
||||
// rheaders["If-None-Match"] = headers["etag"]; // heise does not work with etag
|
||||
|
||||
fetched = buffer;
|
||||
if (remainder) fetched += remainder;
|
||||
|
@ -228,12 +227,25 @@ string qHeader(mixed key) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
string qReqHeader(string key) {
|
||||
return rheaders[key];
|
||||
}
|
||||
|
||||
void sReqHeader(string key, string value) {
|
||||
rheaders[key] = value;
|
||||
}
|
||||
|
||||
varargs void refetch(closure cb, int willbehave) {
|
||||
enqueue(ME, ({ cb, willbehave }));
|
||||
unless (fetching) connect();
|
||||
}
|
||||
|
||||
protected create() {
|
||||
void reset() {
|
||||
fetched = 0;
|
||||
qCreate();
|
||||
qInit(ME, 150, 5);
|
||||
}
|
||||
|
||||
protected create() {
|
||||
reset();
|
||||
}
|
||||
|
|
|
@ -134,6 +134,20 @@ string urldecode(string txt) {
|
|||
return txt;
|
||||
}
|
||||
|
||||
static string xx2c(string xx) {
|
||||
string c = " ";
|
||||
c[0] = hex2int(xx);
|
||||
return c;
|
||||
}
|
||||
|
||||
static string c2xx(string c) {
|
||||
return "%" + upper_case(sprintf("%x", c[0]));
|
||||
}
|
||||
|
||||
string urlencode(string txt) {
|
||||
return regreplace(txt, "[^A-Za-z0-9._~-]", #'c2xx, 1);
|
||||
}
|
||||
|
||||
#ifndef DEFAULT_HT_TYPE
|
||||
# define DEFAULT_HT_TYPE "text/plain"
|
||||
#endif
|
||||
|
@ -177,3 +191,19 @@ default:
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
parse_query(query, qs) {
|
||||
foreach (string pair : explode(qs, "&")) {
|
||||
string key, val;
|
||||
|
||||
if (sscanf(pair, "%s=%s", key, val)) {
|
||||
P3(("query: pair: %s, %s\n", urldecode(key),
|
||||
urldecode(val)))
|
||||
query[urldecode(key)] = urldecode(val);
|
||||
} else {
|
||||
P3(("query: single: %s\n", urldecode(pair)))
|
||||
query[urldecode(pair)] = 1;
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
|
101
world/net/http/oauth.c
Normal file
101
world/net/http/oauth.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
current specification:
|
||||
http://tools.ietf.org/html/draft-hammer-oauth-10
|
||||
|
||||
older ones:
|
||||
http://oauth.net/core/1.0a/
|
||||
http://oauth.net/core/1.0/
|
||||
*/
|
||||
|
||||
#include <net.h>
|
||||
#include <tls.h>
|
||||
|
||||
string consumer_key;
|
||||
string consumer_secret;
|
||||
string request_token_url;
|
||||
string request_token;
|
||||
string request_secret;
|
||||
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) {
|
||||
P3((">> oauth:fetch(%O, %O, %O)\n", url, method, oauth))
|
||||
unless (method) method = "GET";
|
||||
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";
|
||||
|
||||
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);
|
||||
|
||||
params = ({});
|
||||
foreach (string key, string value : oauth)
|
||||
params += ({"oauth_" + key + "=\"" + urlencode(to_string(value)) + "\""});
|
||||
|
||||
ua->fetch(url, method, (["Authorization": "OAuth " + implode(params, ",")]));
|
||||
}
|
||||
|
||||
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;
|
||||
sendmsg(user, "_notice_oauth_authorize_url", "Open [_url] to perform authorization.",
|
||||
(["_url": authorize_url + "?oauth_token=" + request_token]));
|
||||
} else {
|
||||
sendmsg(user, "_error_oauth_token_request", "OAuth failed: could not get a request token.");
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
sendmsg(user, "_notice_oauth_success", "OAuth successful.");
|
||||
} else {
|
||||
sendmsg(user, "_error_oauth_token_access", "OAuth failed: could not get an access token.");
|
||||
}
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
object load(object usr, string key, string secret, string request, string access, string authorize) {
|
||||
if (usr) user = usr;
|
||||
if (key) consumer_key = key;
|
||||
if (secret) consumer_secret = secret;
|
||||
if (request) request_token_url = request;
|
||||
if (access) access_token_url = access;
|
||||
if (authorize) authorize_url = authorize;
|
||||
|
||||
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]));
|
||||
}
|
||||
return ME;
|
||||
}
|
|
@ -100,22 +100,6 @@ parse_header(input) {
|
|||
}
|
||||
}
|
||||
|
||||
parse_query(query, qs) {
|
||||
foreach (string pair : explode(qs, "&")) {
|
||||
string key, val;
|
||||
|
||||
if (sscanf(pair, "%s=%s", key, val)) {
|
||||
P3(("query: pair: %s, %s\n", urldecode(key),
|
||||
urldecode(val)))
|
||||
query[urldecode(key)] = urldecode(val);
|
||||
} else {
|
||||
P3(("query: single: %s\n", urldecode(pair)))
|
||||
query[urldecode(pair)] = 1;
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
process() {
|
||||
string t, ext;
|
||||
mapping query = ([]);
|
||||
|
@ -192,6 +176,15 @@ case "/static": // really don't like to do this, but the IE stores directories
|
|||
case "/static/":
|
||||
file = "/static/index.html";
|
||||
break;
|
||||
case "/oauth":
|
||||
object oauth;
|
||||
//PT((">>> shm: %O\n", shared_memory("oauth_request_tokens")))
|
||||
if (query["oauth_verifier"] && (oauth = shared_memory("oauth_request_tokens")[query["oauth_token"]])) {
|
||||
//PT((">>> oauth: %O\n", oauth))
|
||||
oauth->verified(query["oauth_verifier"]);
|
||||
m_delete(shared_memory("oauth_request_tokens"), query["oauth_token"]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
switch (file[1]) {
|
||||
case '~':
|
||||
|
|
13
world/net/library/hmac.c
Normal file
13
world/net/library/hmac.c
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include <net.h>
|
||||
|
||||
inherit NET_PATH "library/base64";
|
||||
|
||||
#include HTTP_PATH "library.i"
|
||||
|
||||
string hmac_bin(int method, string key, string arg) {
|
||||
return regreplace(hmac(method, key, arg), "..", #'xx2c, 1); //'
|
||||
}
|
||||
|
||||
string hmac_base64(int method, string key, string arg) {
|
||||
return encode_base64((int *)hmac_bin(method, key, arg));
|
||||
}
|
|
@ -171,6 +171,7 @@ volatile mapping share = ([
|
|||
"_understand_modules" : PSYC_ROUTING,
|
||||
"_using_modules" : PSYC_ROUTING,
|
||||
]),
|
||||
"oauth_request_tokens": ([ ]),
|
||||
]);
|
||||
|
||||
varargs mixed shared_memory(mixed datensatz, mixed value) {
|
||||
|
|
|
@ -78,6 +78,8 @@ private volatile mapping _sigs = ([
|
|||
"_request_remove": ({ "_request_remove", 0, "_person" }),
|
||||
"_request_priv": ({ "_request_privacy", 0, "_privacy" }),
|
||||
"_request_privacy": ({ "_request_privacy", 0, "_privacy" }),
|
||||
"_request_tw": ({ "_request_twitter", 0, "_switch" }),
|
||||
"_request_twitter": ({ "_request_twitter", 0, "_switch" }),
|
||||
#endif
|
||||
#ifdef EXPERIMENTAL
|
||||
// stuff to play around with
|
||||
|
|
|
@ -16,6 +16,7 @@ volatile mixed lastTry;
|
|||
|
||||
volatile string owner;
|
||||
volatile string channel;
|
||||
volatile object twitter;
|
||||
|
||||
load(name, keep) {
|
||||
P3((">> userthreads:load(%O, %O)\n", name, keep))
|
||||
|
@ -23,6 +24,7 @@ load(name, keep) {
|
|||
sscanf(name, "~%s#%s", owner, channel);
|
||||
vSet("owners", ([ owner: 0 ]));
|
||||
vSet("privacy", "private");
|
||||
vSet("twitter", 0);
|
||||
|
||||
vSet("_restrict_invitation", BLAME);
|
||||
vSet("_filter_conversation", BLAME);
|
||||
|
@ -52,6 +54,7 @@ enter(source, mc, data, vars) {
|
|||
|
||||
if (p == source) {
|
||||
p->sChannel(MYNICK);
|
||||
if (v("twitter") && !twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source);
|
||||
}
|
||||
|
||||
return ::enter(source, mc, data, vars);
|
||||
|
@ -125,6 +128,23 @@ _request_privacy(source, mc, data, vars, b) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
_request_twitter(source, mc, data, vars, b) {
|
||||
string sw = vars["_switch"];
|
||||
if (sw == "on" || sw == "enabled" || sw == "1") {
|
||||
unless (twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source);
|
||||
vSet("twitter", 1);
|
||||
save();
|
||||
} else if (sw == "off" || sw == "disabled" || sw == "0") {
|
||||
if (twitter) twitter = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
htMain(int last) {
|
||||
return htmlEntries(_thread, last, 1, channel);
|
||||
}
|
||||
|
|
23
world/net/twitter/client.c
Normal file
23
world/net/twitter/client.c
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include <net.h>
|
||||
|
||||
inherit NET_PATH "http/oauth";
|
||||
|
||||
object load(object usr, string key, string secret, string request, string access, string authorize) {
|
||||
consumer_key = TWITTER_KEY;
|
||||
consumer_secret = TWITTER_SECRET;
|
||||
request_token_url = "http://twitter.com/oauth/request_token";
|
||||
access_token_url = "http://twitter.com/oauth/access_token";
|
||||
authorize_url = "http://twitter.com/oauth/authorize";
|
||||
|
||||
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 home() {
|
||||
object ua = clone_object(NET_PATH "http/fetch");
|
||||
ua->content(#'parse_home, 1, 1); //');
|
||||
fetch(ua, "http://api.twitter.com/1/statuses/home_timeline.json");
|
||||
}
|
Loading…
Reference in a new issue