Mercurial > pidgin-twitter
comparison pidgin-twitter.c @ 115:7d0dd0e1dbd0
very preliminary twitter API get status feature.
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Wed, 16 Jul 2008 19:19:10 +0900 |
parents | 388a8ca50817 |
children | dd27aeead474 |
comparison
equal
deleted
inserted
replaced
114:48bfe86ff990 | 115:7d0dd0e1dbd0 |
---|---|
53 | 53 |
54 typedef struct _got_icon_data { | 54 typedef struct _got_icon_data { |
55 gchar *user_name; | 55 gchar *user_name; |
56 gint service; | 56 gint service; |
57 } got_icon_data; | 57 } got_icon_data; |
58 | |
59 #define TWITTER_STATUS_POST "POST /statuses/update.xml HTTP/1.0\r\n" \ | |
60 "Host: twitter.com\r\n" \ | |
61 "User-Agent: Pidgin-Twitter\r\n" \ | |
62 "Authorization: Basic %s\r\n" \ | |
63 "Content-Length: %d\r\n\r\n" | |
64 | |
65 #define TWITTER_STATUS_FORMAT "status=%s" | |
66 #define TWITTER_STATUS_TERMINATOR "\r\n\r\n" | |
67 | |
68 #define TWITTER_BASE_URL "http://twitter.com" | |
69 | |
70 #define TWITTER_STATUS_GET "GET /statuses/friends_timeline.xml HTTP/1.0\r\n" \ | |
71 "Host: twitter.com\r\n" \ | |
72 "User-Agent: Pidgin-Twitter\r\n" \ | |
73 "Authorization: Basic %s\r\n" | |
74 | |
75 | |
76 | |
77 | |
78 | |
79 | |
80 | |
81 /* xml parser*/ | |
82 #include <libxml/tree.h> | |
83 #include <libxml/parser.h> | |
84 #include <libxml/xmlreader.h> | |
85 #include <libxml/xpath.h> | |
86 #include <libxml/xpathInternals.h> | |
87 #include <libxml/uri.h> | |
88 | |
89 typedef struct _status { | |
90 time_t time; | |
91 guint id; | |
92 gchar *created_at; | |
93 gchar *text; | |
94 gchar *screen_name; | |
95 gchar *profile_image_url; | |
96 } status_t; | |
97 | |
98 GList *stlist = NULL; | |
99 | |
100 static void | |
101 parse_user(xmlNode *user, status_t *st) | |
102 { | |
103 xmlNode *nptr; | |
104 | |
105 for(nptr = user->children; nptr != NULL; nptr = nptr->next) { | |
106 if(nptr->type == XML_ELEMENT_NODE) { | |
107 if(!xmlStrcmp(nptr->name, (xmlChar *)"screen_name")) { | |
108 gchar *str = (gchar *)xmlNodeGetContent(nptr); | |
109 st->screen_name = g_strdup(str); | |
110 xmlFree(str); | |
111 } | |
112 else if(!xmlStrcmp(nptr->name, (xmlChar *)"profile_image_url")) { | |
113 gchar *str = (gchar *)xmlNodeGetContent(nptr); | |
114 st->profile_image_url = g_strdup(str); | |
115 xmlFree(str); | |
116 } | |
117 } | |
118 } | |
119 } | |
120 | |
121 | |
122 static void | |
123 parse_status(xmlNode *status) | |
124 { | |
125 xmlNode *nptr; | |
126 | |
127 status_t *st = g_new0(status_t, 1); | |
128 | |
129 stlist = g_list_prepend(stlist, st); | |
130 | |
131 for(nptr = status->children; nptr != NULL; nptr = nptr->next) { | |
132 if(nptr->type == XML_ELEMENT_NODE) { | |
133 if(!xmlStrcmp(nptr->name, (xmlChar *)"created_at")) { | |
134 gchar *str = (gchar *)xmlNodeGetContent(nptr); | |
135 st->created_at = g_strdup(str); | |
136 xmlFree(str); | |
137 } | |
138 else if(!xmlStrcmp(nptr->name, (xmlChar *)"id")) { | |
139 gchar *str = (gchar *)xmlNodeGetContent(nptr); | |
140 st->id = atoi(str); | |
141 xmlFree(str); | |
142 } | |
143 else if(!xmlStrcmp(nptr->name, (xmlChar *)"text")) { | |
144 gchar *str = (gchar *)xmlNodeGetContent(nptr); | |
145 st->text = g_strdup(str); | |
146 xmlFree(str); | |
147 } | |
148 else if(!xmlStrcmp(nptr->name, (xmlChar *)"user")) { | |
149 parse_user(nptr, st); | |
150 } | |
151 } | |
152 } | |
153 } | |
154 | |
155 | |
156 static void | |
157 get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, | |
158 const gchar *url_text, size_t len, | |
159 const gchar *error_message) | |
160 | |
161 { | |
162 xmlDocPtr doc; | |
163 xmlNode *nptr, *nptr2; | |
164 static guint lastid = 0; | |
165 g_return_if_fail(url_text != NULL); | |
166 | |
167 PurpleConversation *conv = (PurpleConversation *)user_data; | |
168 | |
169 if(!conv) | |
170 return; | |
171 | |
172 const gchar *start = strstr(url_text, "<?xml"); | |
173 | |
174 doc = xmlRecoverMemory(start, len); | |
175 if(doc == NULL) | |
176 return; | |
177 | |
178 for(nptr = doc->children; nptr != NULL; nptr = nptr->next) { | |
179 if(nptr->type == XML_ELEMENT_NODE && | |
180 !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) { | |
181 | |
182 for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) { | |
183 if(nptr2->type == XML_ELEMENT_NODE && | |
184 !xmlStrcmp(nptr2->name, (xmlChar *)"status")) { | |
185 parse_status(nptr2); | |
186 } | |
187 } | |
188 } | |
189 } /* for */ | |
190 | |
191 /* process stlist */ | |
192 GList *stp; | |
193 | |
194 for(stp = stlist; stp; stp=stp->next) { | |
195 status_t *st = (status_t *)stp->data; | |
196 if(st->id > lastid) { | |
197 gchar *msg = NULL; | |
198 | |
199 msg = g_strdup_printf("%s: %s\n", st->screen_name, st->text); | |
200 purple_conv_im_write(conv->u.im, | |
201 "twitter@twitter.com", | |
202 msg, | |
203 PURPLE_MESSAGE_RECV, | |
204 st->time); | |
205 lastid = st->id; | |
206 | |
207 g_free(msg); | |
208 } | |
209 | |
210 g_free(st->created_at); | |
211 g_free(st->text); | |
212 g_free(st->screen_name); | |
213 g_free(st->profile_image_url); | |
214 g_free(stp->data); | |
215 stp->data = NULL; | |
216 } | |
217 | |
218 stlist = g_list_remove_all(stlist, NULL); | |
219 } | |
220 | |
221 | |
222 /* api based get */ | |
223 static gboolean | |
224 get_status_with_api(gpointer data) | |
225 { | |
226 /* fetch friends time line */ | |
227 char *request, *header; | |
228 char *basic_auth, *basic_auth_encoded; | |
229 | |
230 PurpleConversation *conv = (PurpleConversation *)data; | |
231 if(!conv) | |
232 return TRUE; //cease fetch | |
233 | |
234 const char *screen_name = | |
235 purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); | |
236 const char *password = | |
237 purple_prefs_get_string(OPT_PASSWORD_TWITTER); | |
238 | |
239 if (!screen_name || !password || !screen_name[0] || !password[0]) { | |
240 twitter_debug("screen_name or password is empty\n"); | |
241 return FALSE; | |
242 } | |
243 | |
244 /* auth */ | |
245 basic_auth = g_strdup_printf("%s:%s", screen_name, password); | |
246 basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, | |
247 strlen(basic_auth)); | |
248 g_free(basic_auth); | |
249 | |
250 /* header */ | |
251 header = g_strdup_printf(TWITTER_STATUS_GET, basic_auth_encoded); | |
252 request = g_strconcat(header, TWITTER_STATUS_TERMINATOR, NULL); | |
253 | |
254 /* invoke fetch */ | |
255 purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, | |
256 NULL, TRUE, request, TRUE, | |
257 get_status_with_api_cb, data); | |
258 | |
259 g_free(header); | |
260 g_free(basic_auth_encoded); | |
261 g_free(request); | |
262 | |
263 return FALSE; | |
264 } | |
265 | |
266 | |
267 | |
268 | |
269 | |
270 | |
271 | |
272 | |
58 | 273 |
59 /* this function is a modified clone of purple_markup_strip_html() */ | 274 /* this function is a modified clone of purple_markup_strip_html() */ |
60 static char * | 275 static char * |
61 strip_html_markup(const char *str) | 276 strip_html_markup(const char *str) |
62 { | 277 { |
281 plain = strip_html_markup(*str); | 496 plain = strip_html_markup(*str); |
282 g_free(*str); | 497 g_free(*str); |
283 *str = plain; | 498 *str = plain; |
284 } | 499 } |
285 | 500 |
286 #define TWITTER_STATUS_POST "POST /statuses/update.xml HTTP/1.0\r\n" \ | |
287 "Host: twitter.com\r\n" \ | |
288 "User-Agent: Pidgin-Twitter\r\n" \ | |
289 "Authorization: Basic %s\r\n" \ | |
290 "Content-Length: %d\r\n\r\n" | |
291 | |
292 #define TWITTER_STATUS_FORMAT "status=%s" | |
293 #define TWITTER_STATUS_TERMINATOR "\r\n\r\n" | |
294 | |
295 #define TWITTER_BASE_URL "http://twitter.com" | |
296 | 501 |
297 typedef struct twitter_message { | 502 typedef struct twitter_message { |
298 PurpleAccount *account; | 503 PurpleAccount *account; |
299 char *status; | 504 char *status; |
300 time_t time; | 505 time_t time; |
840 PurpleConversation *conv = | 1045 PurpleConversation *conv = |
841 pidgin_conv_window_get_active_conversation(win); | 1046 pidgin_conv_window_get_active_conversation(win); |
842 gint service = get_service_type(conv); | 1047 gint service = get_service_type(conv); |
843 switch(service) { | 1048 switch(service) { |
844 case twitter_service: | 1049 case twitter_service: |
1050 g_source_remove_by_user_data((gpointer)conv); | |
1051 detach_from_conv(conv, NULL); | |
1052 break; | |
845 case wassr_service: | 1053 case wassr_service: |
846 case identica_service: | 1054 case identica_service: |
847 detach_from_conv(conv, NULL); | 1055 detach_from_conv(conv, NULL); |
848 break; | 1056 break; |
849 default: | 1057 default: |
949 pidgin_conv_window_get_active_conversation(win); | 1157 pidgin_conv_window_get_active_conversation(win); |
950 gint service = get_service_type(conv); | 1158 gint service = get_service_type(conv); |
951 /* only attach to twitter conversation window */ | 1159 /* only attach to twitter conversation window */ |
952 switch(service) { | 1160 switch(service) { |
953 case twitter_service: | 1161 case twitter_service: |
1162 /* api based retrieve */ //xxx should configurable | |
1163 get_status_with_api((gpointer)conv); | |
1164 g_timeout_add_seconds(60, get_status_with_api, (gpointer)conv); | |
1165 attach_to_conv(conv, NULL); | |
1166 break; | |
954 case wassr_service: | 1167 case wassr_service: |
955 case identica_service: | 1168 case identica_service: |
956 attach_to_conv(conv, NULL); | 1169 attach_to_conv(conv, NULL); |
957 break; | 1170 break; |
958 default: | 1171 default: |
1119 | 1332 |
1120 gint service = get_service_type(conv); | 1333 gint service = get_service_type(conv); |
1121 /* only attach to twitter conversation window */ | 1334 /* only attach to twitter conversation window */ |
1122 switch(service) { | 1335 switch(service) { |
1123 case twitter_service: | 1336 case twitter_service: |
1337 /* api based retrieve */ //xxx should configurable | |
1338 get_status_with_api((gpointer)conv); | |
1339 g_timeout_add_seconds(60, get_status_with_api, (gpointer)conv); | |
1340 attach_to_conv(conv, NULL); | |
1341 break; | |
1124 case wassr_service: | 1342 case wassr_service: |
1125 case identica_service: | 1343 case identica_service: |
1126 attach_to_conv(conv, NULL); | 1344 attach_to_conv(conv, NULL); |
1127 break; | 1345 break; |
1128 default: | 1346 default: |
1190 } | 1408 } |
1191 } | 1409 } |
1192 | 1410 |
1193 if(!is_twitter_conv(conv)) { | 1411 if(!is_twitter_conv(conv)) { |
1194 return FALSE; | 1412 return FALSE; |
1413 } | |
1414 | |
1415 /* if we use api, discard incoming IM message. XXX need fix */ | |
1416 if(purple_prefs_get_bool(OPT_API_BASE_POST)) { | |
1417 g_free(*sender); *sender = NULL; | |
1418 g_free(*buffer); *buffer = NULL; | |
1195 } | 1419 } |
1196 | 1420 |
1197 if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS)) | 1421 if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS)) |
1198 return FALSE; | 1422 return FALSE; |
1199 | 1423 |
2259 purple_prefs_add_string(OPT_PASSWORD_TWITTER, EMPTY); | 2483 purple_prefs_add_string(OPT_PASSWORD_TWITTER, EMPTY); |
2260 | 2484 |
2261 purple_prefs_add_int(OPT_ICON_SIZE, 48); | 2485 purple_prefs_add_int(OPT_ICON_SIZE, 48); |
2262 purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY); | 2486 purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY); |
2263 purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY); | 2487 purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY); |
2488 | |
2264 } | 2489 } |
2265 | 2490 |
2266 PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info) | 2491 PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info) |