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)