Changes in / [92d3044:cc6fdf8]
- Files:
-
- 4 added
- 18 edited
Legend:
- Unmodified
- Added
- Removed
-
debian/copyright
r92d3044 rcc6fdf8 8 8 9 9 Mainly Copyright 2002-2012 Wilmer van der Gaast. 10 Some parts are borrowed from Gaim (version 0.58) <http://gaim.sf.net/>. 11 For the copyrights on those parts, please read the Gaim source code. 10 11 12 Bits of third party code, also GPLed: 13 * Some parts (mostly protocols/oscar/) are borrowed from Gaim (version 14 0.58), now known as Pidgin <http://www.pidgin.im/>. 15 * protocols/yahoo/ is libyahoo2 <http://libyahoo2.sf.net/>. 16 17 Other license (but GPL-compatible): 18 * lib/json.[ch] is from <https://github.com/udp/json-parser> and licensed 19 under the modified BSD license. 20 12 21 13 22 BitlBee License: … … 33 42 34 43 The SGML-formatted documentation is written by Jelmer Vernooij 35 <jelmer@ nl.linux.org> under the GNU Free Documentation License:44 <jelmer@samba.org> under the GNU Free Documentation License: 36 45 37 46 ============================================================================ 38 GNU Free Documentation License 39 Version 1.1, March 2000 47 Copyright (c) 2002-2012 Jelmer Vernooij, Wilmer van der Gaast. 40 48 41 Copyright (C) 2000 Free Software Foundation, Inc. 42 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 43 Everyone is permitted to copy and distribute verbatim copies 44 of this license document, but changing it is not allowed. 45 46 47 0. PREAMBLE 48 49 The purpose of this License is to make a manual, textbook, or other 50 written document "free" in the sense of freedom: to assure everyone 51 the effective freedom to copy and redistribute it, with or without 52 modifying it, either commercially or noncommercially. Secondarily, 53 this License preserves for the author and publisher a way to get 54 credit for their work, while not being considered responsible for 55 modifications made by others. 56 57 This License is a kind of "copyleft", which means that derivative 58 works of the document must themselves be free in the same sense. It 59 complements the GNU General Public License, which is a copyleft 60 license designed for free software. 61 62 We have designed this License in order to use it for manuals for free 63 software, because free software needs free documentation: a free 64 program should come with manuals providing the same freedoms that the 65 software does. But this License is not limited to software manuals; 66 it can be used for any textual work, regardless of subject matter or 67 whether it is published as a printed book. We recommend this License 68 principally for works whose purpose is instruction or reference. 69 70 71 1. APPLICABILITY AND DEFINITIONS 72 73 This License applies to any manual or other work that contains a 74 notice placed by the copyright holder saying it can be distributed 75 under the terms of this License. The "Document", below, refers to any 76 such manual or work. Any member of the public is a licensee, and is 77 addressed as "you". 78 79 A "Modified Version" of the Document means any work containing the 80 Document or a portion of it, either copied verbatim, or with 81 modifications and/or translated into another language. 82 83 A "Secondary Section" is a named appendix or a front-matter section of 84 the Document that deals exclusively with the relationship of the 85 publishers or authors of the Document to the Document's overall subject 86 (or to related matters) and contains nothing that could fall directly 87 within that overall subject. (For example, if the Document is in part a 88 textbook of mathematics, a Secondary Section may not explain any 89 mathematics.) The relationship could be a matter of historical 90 connection with the subject or with related matters, or of legal, 91 commercial, philosophical, ethical or political position regarding 92 them. 93 94 The "Invariant Sections" are certain Secondary Sections whose titles 95 are designated, as being those of Invariant Sections, in the notice 96 that says that the Document is released under this License. 97 98 The "Cover Texts" are certain short passages of text that are listed, 99 as Front-Cover Texts or Back-Cover Texts, in the notice that says that 100 the Document is released under this License. 101 102 A "Transparent" copy of the Document means a machine-readable copy, 103 represented in a format whose specification is available to the 104 general public, whose contents can be viewed and edited directly and 105 straightforwardly with generic text editors or (for images composed of 106 pixels) generic paint programs or (for drawings) some widely available 107 drawing editor, and that is suitable for input to text formatters or 108 for automatic translation to a variety of formats suitable for input 109 to text formatters. A copy made in an otherwise Transparent file 110 format whose markup has been designed to thwart or discourage 111 subsequent modification by readers is not Transparent. A copy that is 112 not "Transparent" is called "Opaque". 113 114 Examples of suitable formats for Transparent copies include plain 115 ASCII without markup, Texinfo input format, LaTeX input format, SGML 116 or XML using a publicly available DTD, and standard-conforming simple 117 HTML designed for human modification. Opaque formats include 118 PostScript, PDF, proprietary formats that can be read and edited only 119 by proprietary word processors, SGML or XML for which the DTD and/or 120 processing tools are not generally available, and the 121 machine-generated HTML produced by some word processors for output 122 purposes only. 123 124 The "Title Page" means, for a printed book, the title page itself, 125 plus such following pages as are needed to hold, legibly, the material 126 this License requires to appear in the title page. For works in 127 formats which do not have any title page as such, "Title Page" means 128 the text near the most prominent appearance of the work's title, 129 preceding the beginning of the body of the text. 130 131 132 2. VERBATIM COPYING 133 134 You may copy and distribute the Document in any medium, either 135 commercially or noncommercially, provided that this License, the 136 copyright notices, and the license notice saying this License applies 137 to the Document are reproduced in all copies, and that you add no other 138 conditions whatsoever to those of this License. You may not use 139 technical measures to obstruct or control the reading or further 140 copying of the copies you make or distribute. However, you may accept 141 compensation in exchange for copies. If you distribute a large enough 142 number of copies you must also follow the conditions in section 3. 143 144 You may also lend copies, under the same conditions stated above, and 145 you may publicly display copies. 146 147 148 3. COPYING IN QUANTITY 149 150 If you publish printed copies of the Document numbering more than 100, 151 and the Document's license notice requires Cover Texts, you must enclose 152 the copies in covers that carry, clearly and legibly, all these Cover 153 Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on 154 the back cover. Both covers must also clearly and legibly identify 155 you as the publisher of these copies. The front cover must present 156 the full title with all words of the title equally prominent and 157 visible. You may add other material on the covers in addition. 158 Copying with changes limited to the covers, as long as they preserve 159 the title of the Document and satisfy these conditions, can be treated 160 as verbatim copying in other respects. 161 162 If the required texts for either cover are too voluminous to fit 163 legibly, you should put the first ones listed (as many as fit 164 reasonably) on the actual cover, and continue the rest onto adjacent 165 pages. 166 167 If you publish or distribute Opaque copies of the Document numbering 168 more than 100, you must either include a machine-readable Transparent 169 copy along with each Opaque copy, or state in or with each Opaque copy 170 a publicly-accessible computer-network location containing a complete 171 Transparent copy of the Document, free of added material, which the 172 general network-using public has access to download anonymously at no 173 charge using public-standard network protocols. If you use the latter 174 option, you must take reasonably prudent steps, when you begin 175 distribution of Opaque copies in quantity, to ensure that this 176 Transparent copy will remain thus accessible at the stated location 177 until at least one year after the last time you distribute an Opaque 178 copy (directly or through your agents or retailers) of that edition to 179 the public. 180 181 It is requested, but not required, that you contact the authors of the 182 Document well before redistributing any large number of copies, to give 183 them a chance to provide you with an updated version of the Document. 184 185 186 4. MODIFICATIONS 187 188 You may copy and distribute a Modified Version of the Document under 189 the conditions of sections 2 and 3 above, provided that you release 190 the Modified Version under precisely this License, with the Modified 191 Version filling the role of the Document, thus licensing distribution 192 and modification of the Modified Version to whoever possesses a copy 193 of it. In addition, you must do these things in the Modified Version: 194 195 A. Use in the Title Page (and on the covers, if any) a title distinct 196 from that of the Document, and from those of previous versions 197 (which should, if there were any, be listed in the History section 198 of the Document). You may use the same title as a previous version 199 if the original publisher of that version gives permission. 200 B. List on the Title Page, as authors, one or more persons or entities 201 responsible for authorship of the modifications in the Modified 202 Version, together with at least five of the principal authors of the 203 Document (all of its principal authors, if it has less than five). 204 C. State on the Title page the name of the publisher of the 205 Modified Version, as the publisher. 206 D. Preserve all the copyright notices of the Document. 207 E. Add an appropriate copyright notice for your modifications 208 adjacent to the other copyright notices. 209 F. Include, immediately after the copyright notices, a license notice 210 giving the public permission to use the Modified Version under the 211 terms of this License, in the form shown in the Addendum below. 212 G. Preserve in that license notice the full lists of Invariant Sections 213 and required Cover Texts given in the Document's license notice. 214 H. Include an unaltered copy of this License. 215 I. Preserve the section entitled "History", and its title, and add to 216 it an item stating at least the title, year, new authors, and 217 publisher of the Modified Version as given on the Title Page. If 218 there is no section entitled "History" in the Document, create one 219 stating the title, year, authors, and publisher of the Document as 220 given on its Title Page, then add an item describing the Modified 221 Version as stated in the previous sentence. 222 J. Preserve the network location, if any, given in the Document for 223 public access to a Transparent copy of the Document, and likewise 224 the network locations given in the Document for previous versions 225 it was based on. These may be placed in the "History" section. 226 You may omit a network location for a work that was published at 227 least four years before the Document itself, or if the original 228 publisher of the version it refers to gives permission. 229 K. In any section entitled "Acknowledgements" or "Dedications", 230 preserve the section's title, and preserve in the section all the 231 substance and tone of each of the contributor acknowledgements 232 and/or dedications given therein. 233 L. Preserve all the Invariant Sections of the Document, 234 unaltered in their text and in their titles. Section numbers 235 or the equivalent are not considered part of the section titles. 236 M. Delete any section entitled "Endorsements". Such a section 237 may not be included in the Modified Version. 238 N. Do not retitle any existing section as "Endorsements" 239 or to conflict in title with any Invariant Section. 240 241 If the Modified Version includes new front-matter sections or 242 appendices that qualify as Secondary Sections and contain no material 243 copied from the Document, you may at your option designate some or all 244 of these sections as invariant. To do this, add their titles to the 245 list of Invariant Sections in the Modified Version's license notice. 246 These titles must be distinct from any other section titles. 247 248 You may add a section entitled "Endorsements", provided it contains 249 nothing but endorsements of your Modified Version by various 250 parties--for example, statements of peer review or that the text has 251 been approved by an organization as the authoritative definition of a 252 standard. 253 254 You may add a passage of up to five words as a Front-Cover Text, and a 255 passage of up to 25 words as a Back-Cover Text, to the end of the list 256 of Cover Texts in the Modified Version. Only one passage of 257 Front-Cover Text and one of Back-Cover Text may be added by (or 258 through arrangements made by) any one entity. If the Document already 259 includes a cover text for the same cover, previously added by you or 260 by arrangement made by the same entity you are acting on behalf of, 261 you may not add another; but you may replace the old one, on explicit 262 permission from the previous publisher that added the old one. 263 264 The author(s) and publisher(s) of the Document do not by this License 265 give permission to use their names for publicity for or to assert or 266 imply endorsement of any Modified Version. 267 268 269 5. COMBINING DOCUMENTS 270 271 You may combine the Document with other documents released under this 272 License, under the terms defined in section 4 above for modified 273 versions, provided that you include in the combination all of the 274 Invariant Sections of all of the original documents, unmodified, and 275 list them all as Invariant Sections of your combined work in its 276 license notice. 277 278 The combined work need only contain one copy of this License, and 279 multiple identical Invariant Sections may be replaced with a single 280 copy. If there are multiple Invariant Sections with the same name but 281 different contents, make the title of each such section unique by 282 adding at the end of it, in parentheses, the name of the original 283 author or publisher of that section if known, or else a unique number. 284 Make the same adjustment to the section titles in the list of 285 Invariant Sections in the license notice of the combined work. 286 287 In the combination, you must combine any sections entitled "History" 288 in the various original documents, forming one section entitled 289 "History"; likewise combine any sections entitled "Acknowledgements", 290 and any sections entitled "Dedications". You must delete all sections 291 entitled "Endorsements." 292 293 294 6. COLLECTIONS OF DOCUMENTS 295 296 You may make a collection consisting of the Document and other documents 297 released under this License, and replace the individual copies of this 298 License in the various documents with a single copy that is included in 299 the collection, provided that you follow the rules of this License for 300 verbatim copying of each of the documents in all other respects. 301 302 You may extract a single document from such a collection, and distribute 303 it individually under this License, provided you insert a copy of this 304 License into the extracted document, and follow this License in all 305 other respects regarding verbatim copying of that document. 306 307 308 7. AGGREGATION WITH INDEPENDENT WORKS 309 310 A compilation of the Document or its derivatives with other separate 311 and independent documents or works, in or on a volume of a storage or 312 distribution medium, does not as a whole count as a Modified Version 313 of the Document, provided no compilation copyright is claimed for the 314 compilation. Such a compilation is called an "aggregate", and this 315 License does not apply to the other self-contained works thus compiled 316 with the Document, on account of their being thus compiled, if they 317 are not themselves derivative works of the Document. 318 319 If the Cover Text requirement of section 3 is applicable to these 320 copies of the Document, then if the Document is less than one quarter 321 of the entire aggregate, the Document's Cover Texts may be placed on 322 covers that surround only the Document within the aggregate. 323 Otherwise they must appear on covers around the whole aggregate. 324 325 326 8. TRANSLATION 327 328 Translation is considered a kind of modification, so you may 329 distribute translations of the Document under the terms of section 4. 330 Replacing Invariant Sections with translations requires special 331 permission from their copyright holders, but you may include 332 translations of some or all Invariant Sections in addition to the 333 original versions of these Invariant Sections. You may include a 334 translation of this License provided that you also include the 335 original English version of this License. In case of a disagreement 336 between the translation and the original English version of this 337 License, the original English version will prevail. 338 339 340 9. TERMINATION 341 342 You may not copy, modify, sublicense, or distribute the Document except 343 as expressly provided for under this License. Any other attempt to 344 copy, modify, sublicense or distribute the Document is void, and will 345 automatically terminate your rights under this License. However, 346 parties who have received copies, or rights, from you under this 347 License will not have their licenses terminated so long as such 348 parties remain in full compliance. 349 350 351 10. FUTURE REVISIONS OF THIS LICENSE 352 353 The Free Software Foundation may publish new, revised versions 354 of the GNU Free Documentation License from time to time. Such new 355 versions will be similar in spirit to the present version, but may 356 differ in detail to address new problems or concerns. See 357 http://www.gnu.org/copyleft/. 358 359 Each version of the License is given a distinguishing version number. 360 If the Document specifies that a particular numbered version of this 361 License "or any later version" applies to it, you have the option of 362 following the terms and conditions either of that specified version or 363 of any later version that has been published (not as a draft) by the 364 Free Software Foundation. If the Document does not specify a version 365 number of this License, you may choose any version ever published (not 366 as a draft) by the Free Software Foundation. 367 368 369 ADDENDUM: How to use this License for your documents 370 371 To use this License in a document you have written, include a copy of 372 the License in the document and put the following copyright and 373 license notices just after the title page: 374 375 Copyright (c) YEAR YOUR NAME. 376 Permission is granted to copy, distribute and/or modify this document 377 under the terms of the GNU Free Documentation License, Version 1.1 378 or any later version published by the Free Software Foundation; 379 with the Invariant Sections being LIST THEIR TITLES, with the 380 Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. 381 A copy of the license is included in the section entitled "GNU 382 Free Documentation License". 383 384 If you have no Invariant Sections, write "with no Invariant Sections" 385 instead of saying which ones are invariant. If you have no 386 Front-Cover Texts, write "no Front-Cover Texts" instead of 387 "Front-Cover Texts being LIST"; likewise for Back-Cover Texts. 388 389 If your document contains nontrivial examples of program code, we 390 recommend releasing these examples in parallel under your choice of 391 free software license, such as the GNU General Public License, 392 to permit their use in free software. 49 Permission is granted to copy, distribute and/or modify this document 50 under the terms of the GNU Free Documentation License, Version 1.1 or 51 any later version published by the Free Software Foundation; with no 52 Invariant Sections, with no Front-Cover Texts, and with no Back-Cover 53 Texts. A copy of the license is included in the section entitled `GNU 54 Free Documentation License''. 393 55 ============================================================================ -
doc/user-guide/commands.xml
r92d3044 rcc6fdf8 835 835 <bitlbee-setting name="commands" type="boolean" scope="account"> 836 836 <default>true</default> 837 <possible-values>true, false, strict</possible-values> 837 838 838 839 <description> … … 853 854 854 855 <para> 855 Anything that doesn't look like a command will be treated as a tweet. Watch out for typos ! :-)856 Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to <emphasis>strict</emphasis>, which causes the <emphasis>post</emphasis> command to become mandatory for posting a tweet. 856 857 </para> 857 858 </description> … … 1023 1024 1024 1025 </bitlbee-setting> 1026 1027 <bitlbee-setting name="stream" type="boolean" scope="account"> 1028 <default>true</default> 1029 1030 <description> 1031 <para> 1032 For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well. 1033 </para> 1034 1035 <para> 1036 For other Twitter-like services, this setting is not supported. 1037 </para> 1038 </description> 1039 1040 </bitlbee-setting> 1025 1041 1026 1042 <bitlbee-setting name="target_url_length" type="integer" scope="account"> … … 1338 1354 1339 1355 <bitlbee-setting name="show_ids" type="boolean" scope="account"> 1340 <default> false</default>1356 <default>true</default> 1341 1357 1342 1358 <description> -
lib/Makefile
r92d3044 rcc6fdf8 13 13 14 14 # [SH] Program variables 15 objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o15 objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o json.o json_util.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o 16 16 17 17 LFLAGS += -r -
lib/http_client.c
r92d3044 rcc6fdf8 193 193 } 194 194 195 static gboolean http_handle_headers( struct http_request *req ); 196 195 197 static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ) 196 198 { 197 199 struct http_request *req = data; 198 int evil_server = 0; 199 char buffer[2048]; 200 char *end1, *end2, *s; 200 char buffer[4096]; 201 char *s; 201 202 size_t content_length; 202 203 int st; … … 218 219 packets that abort connections! \o/ */ 219 220 220 goto got_reply;221 goto eof; 221 222 } 222 223 } 223 224 else if( st == 0 ) 224 225 { 225 goto got_reply;226 goto eof; 226 227 } 227 228 } … … 239 240 else if( st == 0 ) 240 241 { 241 goto got_reply;242 } 243 } 244 245 if( st > 0 )242 goto eof; 243 } 244 } 245 246 if( st > 0 && !req->sbuf ) 246 247 { 247 248 req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 ); 248 249 memcpy( req->reply_headers + req->bytes_read, buffer, st ); 249 250 req->bytes_read += st; 250 } 251 252 st = 0; 253 } 254 255 if( st >= 0 && ( req->flags & HTTPC_STREAMING ) ) 256 { 257 if( !req->reply_body && 258 ( strstr( req->reply_headers, "\r\n\r\n" ) || 259 strstr( req->reply_headers, "\n\n" ) ) ) 260 { 261 size_t hlen; 262 263 /* We've now received all headers, so process them once 264 before we start feeding back data. */ 265 if( !http_handle_headers( req ) ) 266 return FALSE; 267 268 hlen = req->reply_body - req->reply_headers; 269 270 req->sblen = req->bytes_read - hlen; 271 req->sbuf = g_memdup( req->reply_body, req->sblen + 1 ); 272 req->reply_headers = g_realloc( req->reply_headers, hlen + 1 ); 273 274 req->reply_body = req->sbuf; 275 } 276 277 if( st > 0 ) 278 { 279 int pos = req->reply_body - req->sbuf; 280 req->sbuf = g_realloc( req->sbuf, req->sblen + st + 1 ); 281 memcpy( req->sbuf + req->sblen, buffer, st ); 282 req->bytes_read += st; 283 req->sblen += st; 284 req->sbuf[req->sblen] = '\0'; 285 req->reply_body = req->sbuf + pos; 286 req->body_size = req->sblen - pos; 287 } 288 289 if( req->reply_body ) 290 req->func( req ); 291 } 292 293 if( ssl_pending( req->ssl ) ) 294 return http_incoming_data( data, source, cond ); 251 295 252 296 /* There will be more! */ … … 255 299 http_incoming_data, req ); 256 300 257 if( ssl_pending( req->ssl ) ) 258 return http_incoming_data( data, source, cond ); 259 else 260 return FALSE; 261 262 got_reply: 301 return FALSE; 302 303 eof: 304 req->flags |= HTTPC_EOF; 305 263 306 /* Maybe if the webserver is overloaded, or when there's bad SSL 264 307 support... */ … … 269 312 } 270 313 314 if( !( req->flags & HTTPC_STREAMING ) ) 315 { 316 /* Returns FALSE if we were redirected, in which case we should abort 317 and not run any callback yet. */ 318 if( !http_handle_headers( req ) ) 319 return FALSE; 320 } 321 322 cleanup: 323 if( req->ssl ) 324 ssl_disconnect( req->ssl ); 325 else 326 closesocket( req->fd ); 327 328 if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) && 329 sscanf( s, "%zd", &content_length ) == 1 ) 330 { 331 if( content_length < req->body_size ) 332 { 333 req->status_code = -1; 334 g_free( req->status_string ); 335 req->status_string = g_strdup( "Response truncated" ); 336 } 337 } 338 g_free( s ); 339 340 if( getenv( "BITLBEE_DEBUG" ) && req ) 341 printf( "Finishing HTTP request with status: %s\n", 342 req->status_string ? req->status_string : "NULL" ); 343 344 req->func( req ); 345 http_free( req ); 346 return FALSE; 347 } 348 349 /* Splits headers and body. Checks result code, in case of 300s it'll handle 350 redirects. If this returns FALSE, don't call any callbacks! */ 351 static gboolean http_handle_headers( struct http_request *req ) 352 { 353 char *end1, *end2; 354 int evil_server = 0; 355 271 356 /* Zero termination is very convenient. */ 272 req->reply_headers[req->bytes_read] = 0;357 req->reply_headers[req->bytes_read] = '\0'; 273 358 274 359 /* Find the separation between headers and body, and keep stupid … … 289 374 { 290 375 req->status_string = g_strdup( "Malformed HTTP reply" ); 291 goto cleanup;376 return TRUE; 292 377 } 293 378 … … 306 391 if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL ) 307 392 { 308 if( sscanf( end1 + 1, "% d", &req->status_code ) != 1 )393 if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 ) 309 394 { 310 395 req->status_string = g_strdup( "Can't parse status code" ); … … 349 434 { 350 435 req->status_string = g_strdup( "Can't locate Location: header" ); 351 goto cleanup;436 return TRUE; 352 437 } 353 438 … … 369 454 req->status_string = g_strdup( "Can't handle recursive redirects" ); 370 455 371 goto cleanup;456 return TRUE; 372 457 } 373 458 else … … 380 465 s = strstr( loc, "\r\n" ); 381 466 if( s == NULL ) 382 goto cleanup;467 return TRUE; 383 468 384 469 url = g_new0( url_t, 1 ); … … 389 474 req->status_string = g_strdup( "Malformed redirect URL" ); 390 475 g_free( url ); 391 goto cleanup;476 return TRUE; 392 477 } 393 478 … … 401 486 req->status_string = g_strdup( "Error while rebuilding request string" ); 402 487 g_free( url ); 403 goto cleanup;488 return TRUE; 404 489 } 405 490 … … 467 552 req->status_string = g_strdup( "Connection problem during redirect" ); 468 553 g_free( new_request ); 469 goto cleanup;554 return TRUE; 470 555 } 471 556 … … 480 565 } 481 566 482 /* Assume that a closed connection means we're finished, this indeed 483 breaks with keep-alive connections and faulty connections. */ 484 /* req->finished = 1; */ 485 486 cleanup: 567 return TRUE; 568 } 569 570 void http_flush_bytes( struct http_request *req, size_t len ) 571 { 572 if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) ) 573 return; 574 575 req->reply_body += len; 576 req->body_size -= len; 577 578 if( req->reply_body - req->sbuf >= 512 ) 579 { 580 printf( "Wasting %ld bytes, cleaning up stream buffer\n", req->reply_body - req->sbuf ); 581 char *new = g_memdup( req->reply_body, req->body_size + 1 ); 582 g_free( req->sbuf ); 583 req->reply_body = req->sbuf = new; 584 req->sblen = req->body_size; 585 } 586 } 587 588 void http_close( struct http_request *req ) 589 { 590 if( !req ) 591 return; 592 593 if( req->inpa > 0 ) 594 b_event_remove( req->inpa ); 595 487 596 if( req->ssl ) 488 597 ssl_disconnect( req->ssl ); … … 490 599 closesocket( req->fd ); 491 600 492 if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&493 sscanf( s, "%zd", &content_length ) == 1 )494 {495 if( content_length < req->body_size )496 {497 req->status_code = -1;498 g_free( req->status_string );499 req->status_string = g_strdup( "Response truncated" );500 }501 }502 g_free( s );503 504 if( getenv( "BITLBEE_DEBUG" ) && req )505 printf( "Finishing HTTP request with status: %s\n",506 req->status_string ? req->status_string : "NULL" );507 508 req->func( req );509 601 http_free( req ); 510 return FALSE;511 602 } 512 603 … … 516 607 g_free( req->reply_headers ); 517 608 g_free( req->status_string ); 609 g_free( req->sbuf ); 518 610 g_free( req ); 519 611 } 520 -
lib/http_client.h
r92d3044 rcc6fdf8 26 26 /* http_client allows you to talk (asynchronously, again) to HTTP servers. 27 27 In the "background" it will send the whole query and wait for a complete 28 response to come back. Right now it's only used by the MSN Passport 29 authentication code, but it might be useful for other things too (for 30 example the AIM usericon patch uses this so icons can be stored on 31 webservers instead of the local filesystem). 28 response to come back. Initially written for MS Passport authentication, 29 but used for many other things now like OAuth and Twitter. 32 30 33 Didn't test this too much, but it seems to work well. Just don't look 34 at the code that handles HTTP 30x redirects. ;-) The function is 35 probably not very useful for downloading lots of data since it keeps 36 everything in a memory buffer until the download is completed (and 37 can't pass any data or whatever before then). It's very useful for 38 doing quick requests without blocking the whole program, though. */ 31 It's very useful for doing quick requests without blocking the whole 32 program. Unfortunately it doesn't support fancy stuff like HTTP keep- 33 alives. */ 39 34 40 35 #include <glib.h> … … 42 37 43 38 struct http_request; 39 40 typedef enum http_client_flags 41 { 42 HTTPC_STREAMING = 1, 43 HTTPC_EOF = 2, 44 45 /* Let's reserve 0x1000000+ for lib users. */ 46 } http_client_flags_t; 44 47 45 48 /* Your callback function should look like this: */ … … 53 56 char *request; /* The request to send to the server. */ 54 57 int request_length; /* Its size. */ 55 int status_code;/* The numeric HTTP status code. (Or -158 short status_code; /* The numeric HTTP status code. (Or -1 56 59 if something really went wrong) */ 57 60 char *status_string; /* The error text. */ … … 59 62 char *reply_body; 60 63 int body_size; /* The number of bytes in reply_body. */ 61 /* int finished; Set to non-0 if the request was completed 62 successfully. */ 63 int redir_ttl; /* You can set it to 0 if you don't want 64 short redir_ttl; /* You can set it to 0 if you don't want 64 65 http_client to follow them. */ 66 67 http_client_flags_t flags; 65 68 66 69 http_input_function func; … … 68 71 69 72 /* Please don't touch the things down here, you shouldn't need them. */ 70 71 73 void *ssl; 72 74 int fd; … … 75 77 int bytes_written; 76 78 int bytes_read; 79 80 /* Used in streaming mode. Caller should read from reply_body. */ 81 char *sbuf; 82 size_t sblen; 77 83 }; 78 84 … … 83 89 struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ); 84 90 struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ); 91 92 /* For streaming connections only; flushes len bytes at the start of the buffer. */ 93 void http_flush_bytes( struct http_request *req, size_t len ); 94 void http_close( struct http_request *req ); -
lib/oauth2.c
r92d3044 rcc6fdf8 4 4 * Simple OAuth client (consumer) implementation. * 5 5 * * 6 * Copyright 2010-201 1Wilmer van der Gaast <wilmer@gaast.net> *6 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 7 7 * * 8 8 * This program is free software; you can redistribute it and/or modify * … … 26 26 #include "oauth2.h" 27 27 #include "oauth.h" 28 #include "json.h" 28 29 #include "url.h" 29 30 … … 44 45 }; 45 46 46 static char *oauth2_json_dumb_get( const char *json, const char *key );47 47 static void oauth2_access_token_done( struct http_request *req ); 48 48 … … 115 115 else if( content_type && strstr( content_type, "application/json" ) ) 116 116 { 117 atoken = oauth2_json_dumb_get( req->reply_body, "access_token" ); 118 rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" ); 117 json_value *js = json_parse( req->reply_body ); 118 if( js && js->type == json_object ) 119 { 120 int i; 121 122 for( i = 0; i < js->u.object.length; i ++ ) 123 { 124 if( js->u.object.values[i].value->type != json_string ) 125 continue; 126 if( strcmp( js->u.object.values[i].name, "access_token" ) == 0 ) 127 atoken = g_strdup( js->u.object.values[i].value->u.string.ptr ); 128 if( strcmp( js->u.object.values[i].name, "refresh_token" ) == 0 ) 129 rtoken = g_strdup( js->u.object.values[i].value->u.string.ptr ); 130 } 131 } 132 json_value_free( js ); 119 133 } 120 134 else … … 137 151 g_free( cb_data ); 138 152 } 139 140 /* Super dumb. I absolutely refuse to use/add a complete json parser library141 (adding a new dependency to BitlBee for the first time in.. 6 years?) just142 to parse 100 bytes of data. So I have to do my own parsing because OAuth2143 dropped support for XML. (GRRR!) This is very dumb and for example won't144 work for integer values, nor will it strip/handle backslashes. */145 static char *oauth2_json_dumb_get( const char *json, const char *key )146 {147 int is_key = 0; /* 1 == reading key, 0 == reading value */148 int found_key = 0;149 150 while( json && *json )151 {152 /* Grab strings and see if they're what we're looking for. */153 if( *json == '"' || *json == '\'' )154 {155 char q = *json;156 const char *str_start;157 json ++;158 str_start = json;159 160 while( *json )161 {162 /* \' and \" are not string terminators. */163 if( *json == '\\' && json[1] == q )164 json ++;165 /* But without a \ it is. */166 else if( *json == q )167 break;168 json ++;169 }170 if( *json == '\0' )171 return NULL;172 173 if( is_key && strncmp( str_start, key, strlen( key ) ) == 0 )174 {175 found_key = 1;176 }177 else if( !is_key && found_key )178 {179 char *ret = g_memdup( str_start, json - str_start + 1 );180 ret[json-str_start] = '\0';181 return ret;182 }183 184 }185 else if( *json == '{' || *json == ',' )186 {187 found_key = 0;188 is_key = 1;189 }190 else if( *json == ':' )191 is_key = 0;192 193 json ++;194 }195 196 return NULL;197 } -
lib/ssl_gnutls.c
r92d3044 rcc6fdf8 38 38 39 39 static gboolean initialized = FALSE; 40 gnutls_certificate_credentials xcred;40 gnutls_certificate_credentials_t xcred; 41 41 42 42 #include <limits.h> … … 60 60 gboolean verify; 61 61 62 gnutls_session session;62 gnutls_session_t session; 63 63 }; 64 64 … … 134 134 conn->data = data; 135 135 conn->inpa = -1; 136 conn->hostname = hostname;136 conn->hostname = g_strdup( hostname ); 137 137 138 138 /* For now, SSL verification is globally enabled by setting the cafile … … 171 171 int verifyret = 0; 172 172 gnutls_x509_crt_t cert; 173 const char *hostname;174 175 hostname= gnutls_session_get_ptr( session );173 struct scd *conn; 174 175 conn = gnutls_session_get_ptr( session ); 176 176 177 177 gnutlsret = gnutls_certificate_verify_peers2( session, &status ); … … 211 211 return VERIFY_CERT_ERROR; 212 212 213 if( !gnutls_x509_crt_check_hostname( cert, hostname ) )213 if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) ) 214 214 { 215 215 verifyret |= VERIFY_CERT_INVALID; … … 267 267 268 268 gnutls_init( &conn->session, GNUTLS_CLIENT ); 269 if( conn->verify ) 270 gnutls_session_set_ptr( conn->session, (void *) conn->hostname ); 269 gnutls_session_set_ptr( conn->session, (void *) conn ); 271 270 #if GNUTLS_VERSION_NUMBER < 0x020c00 272 271 gnutls_transport_set_lowat( conn->session, 0 ); … … 276 275 277 276 sock_make_nonblocking( conn->fd ); 278 gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr ) GNUTLS_STUPID_CAST conn->fd );277 gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd ); 279 278 280 279 return ssl_handshake( data, source, cond ); … … 402 401 if( conn->session ) 403 402 gnutls_deinit( conn->session ); 403 g_free( conn->hostname ); 404 404 g_free( conn ); 405 405 } -
nick.c
r92d3044 rcc6fdf8 134 134 chop = fmt[1]; 135 135 if( chop == '\0' ) 136 { 137 g_string_free( ret, TRUE ); 136 138 return NULL; 139 } 137 140 fmt += 2; 138 141 } … … 187 190 else 188 191 { 192 g_string_free( ret, TRUE ); 189 193 return NULL; 190 194 } -
protocols/msn/msn.c
r92d3044 rcc6fdf8 53 53 54 54 ic->proto_data = md; 55 ic->flags |= OPT_PONGS | OPT_PONGED; 55 56 56 57 if( strchr( acc->user, '@' ) == NULL ) -
protocols/msn/ns.c
r92d3044 rcc6fdf8 576 576 if( num_parts >= 7 ) 577 577 handler->msglen = atoi( cmd[6] ); 578 } 579 else if( strcmp( cmd[0], "QNG" ) == 0 ) 580 { 581 ic->flags |= OPT_PONGED; 578 582 } 579 583 else if( isdigit( cmd[0][0] ) ) -
protocols/nogaim.c
r92d3044 rcc6fdf8 261 261 struct im_connection *ic = d; 262 262 263 if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) ) 264 { 265 /* This protocol is expected to ack keepalives and hasn't 266 since the last time we were here. */ 267 imcb_error( ic, "Connection timeout" ); 268 imc_logout( ic, TRUE ); 269 return FALSE; 270 } 271 ic->flags &= ~OPT_PONGED; 272 263 273 if( ic->acc->prpl->keepalive ) 264 274 ic->acc->prpl->keepalive( ic ); 265 275 266 276 return TRUE; 277 } 278 279 void start_keepalives( struct im_connection *ic, int interval ) 280 { 281 b_event_remove( ic->keepalive ); 282 ic->keepalive = b_timeout_add( interval, send_keepalive, ic ); 283 284 /* Connecting successfully counts as a first successful pong. */ 285 if( ic->flags & OPT_PONGS ) 286 ic->flags |= OPT_PONGED; 267 287 } 268 288 … … 277 297 imcb_log( ic, "Logged in" ); 278 298 279 b_event_remove( ic->keepalive );280 ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );281 299 ic->flags |= OPT_LOGGED_IN; 300 start_keepalives( ic, 60000 ); 282 301 283 302 /* Necessary to send initial presence status, even if we're not away. */ -
protocols/nogaim.h
r92d3044 rcc6fdf8 68 68 #define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ 69 69 #define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */ 70 #define OPT_PONGS 0x00010000 /* Service sends us keep-alives */ 71 #define OPT_PONGED 0x00020000 /* Received a keep-alive during last interval */ 70 72 71 73 /* ok. now the fun begins. first we create a connection structure */ -
protocols/twitter/twitter.c
r92d3044 rcc6fdf8 4 4 * Simple module to facilitate twitter functionality. * 5 5 * * 6 * Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * 6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> * 7 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 7 8 * * 8 9 * This library is free software; you can redistribute it and/or * … … 29 30 #include "url.h" 30 31 31 #define twitter_msg( ic, fmt... ) \32 do { \33 struct twitter_data *td = ic->proto_data; \34 if( td->timeline_gc ) \35 imcb_chat_log( td->timeline_gc, fmt ); \36 else \37 imcb_log( ic, fmt ); \38 } while( 0 );39 40 32 GSList *twitter_connections = NULL; 41 42 33 /** 43 34 * Main loop function … … 62 53 struct twitter_data *td = ic->proto_data; 63 54 55 /* Create the room now that we "logged in". */ 56 if (td->flags & TWITTER_MODE_CHAT) 57 twitter_groupchat_init(ic); 58 64 59 imcb_log(ic, "Getting initial statuses"); 65 60 66 // Run this once. After this queue the main loop function. 61 // Run this once. After this queue the main loop function (or open the 62 // stream if available). 67 63 twitter_main_loop(ic, -1, 0); 68 69 // Queue the main_loop 70 // Save the return value, so we can remove the timeout on logout. 71 td->main_loop_id = 72 b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); 64 65 if (set_getbool(&ic->acc->set, "stream")) { 66 /* That fetch was just to get backlog, the stream will give 67 us the rest. \o/ */ 68 twitter_open_stream(ic); 69 70 /* Stream sends keepalives (empty lines) or actual data at 71 least twice a minute. Disconnect if this stops. */ 72 ic->flags |= OPT_PONGS; 73 } else { 74 /* Not using the streaming API, so keep polling the old- 75 fashioned way. :-( */ 76 td->main_loop_id = 77 b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, 78 twitter_main_loop, ic); 79 } 80 } 81 82 struct groupchat *twitter_groupchat_init(struct im_connection *ic) 83 { 84 char *name_hint; 85 struct groupchat *gc; 86 struct twitter_data *td = ic->proto_data; 87 GSList *l; 88 89 if (td->timeline_gc) 90 return td->timeline_gc; 91 92 td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); 93 94 name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); 95 imcb_chat_name_hint(gc, name_hint); 96 g_free(name_hint); 97 98 for (l = ic->bee->users; l; l = l->next) { 99 bee_user_t *bu = l->data; 100 if (bu->ic == ic) 101 imcb_chat_add_buddy(gc, bu->handle); 102 } 103 imcb_chat_add_buddy(gc, ic->acc->user); 104 105 return gc; 73 106 } 74 107 … … 83 116 if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) 84 117 twitter_oauth_start(ic); 85 else if ( g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0&&86 118 else if (!(td->flags & TWITTER_MODE_ONE) && 119 !(td->flags & TWITTER_HAVE_FRIENDS)) { 87 120 imcb_log(ic, "Getting contact list"); 88 121 twitter_get_friends_ids(ic, -1); 89 //twitter_get_statuses_friends(ic, -1);90 122 } else 91 123 twitter_main_loop_start(ic); … … 187 219 } 188 220 189 190 static char *set_eval_mode(set_t * set, char *value)191 {192 if (g_strcasecmp(value, "one") == 0 ||193 g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)194 return value;195 else196 return NULL;197 }198 199 221 int twitter_url_len_diff(gchar *msg, unsigned int target_len) 200 222 { … … 233 255 return TRUE; 234 256 235 imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max);257 twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max); 236 258 237 259 return FALSE; 260 } 261 262 static char *set_eval_commands(set_t * set, char *value) 263 { 264 if (g_strcasecmp(value, "strict") == 0 ) 265 return value; 266 else 267 return set_eval_bool(set, value); 268 } 269 270 static char *set_eval_mode(set_t * set, char *value) 271 { 272 if (g_strcasecmp(value, "one") == 0 || 273 g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) 274 return value; 275 else 276 return NULL; 238 277 } 239 278 … … 257 296 s->flags |= ACC_SET_OFFLINE_ONLY; 258 297 259 s = set_add(&acc->set, "commands", "true", set_eval_ bool, acc);298 s = set_add(&acc->set, "commands", "true", set_eval_commands, acc); 260 299 261 300 s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); … … 274 313 275 314 s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); 276 s->flags |= ACC_SET_OFFLINE_ONLY;277 315 278 316 s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc); 279 317 280 318 s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); 319 320 if (strcmp(acc->prpl->name, "twitter") == 0) { 321 s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); 322 s->flags |= ACC_SET_OFFLINE_ONLY; 323 } 281 324 } 282 325 283 326 /** 284 * Login method. Since the twitter API works with sep erate HTTP request we327 * Login method. Since the twitter API works with separate HTTP request we 285 328 * only save the user and pass to the twitter_data object. 286 329 */ … … 298 341 imc_logout(ic, FALSE); 299 342 return; 343 } 344 345 if (!strstr(url.host, "twitter.com") && 346 set_getbool(&ic->acc->set, "stream")) { 347 imcb_error(ic, "Warning: The streaming API is only supported by Twitter, " 348 "and you seem to be connecting to a different service."); 300 349 } 301 350 … … 340 389 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); 341 390 342 if (set_getbool(&acc->set, "show_ids")) 343 td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); 391 td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); 392 td->log_id = -1; 393 394 s = set_getstr(&ic->acc->set, "mode"); 395 if (g_strcasecmp(s, "one") == 0) 396 td->flags |= TWITTER_MODE_ONE; 397 else if (g_strcasecmp(s, "many") == 0) 398 td->flags |= TWITTER_MODE_MANY; 399 else 400 td->flags |= TWITTER_MODE_CHAT; 344 401 345 402 twitter_login_finish(ic); … … 363 420 364 421 if (td) { 422 http_close(td->stream); 365 423 oauth_info_free(td->oauth_info); 366 424 g_free(td->user); … … 495 553 * Returns 0 if the user provides garbage. 496 554 */ 497 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) { 555 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { 556 struct twitter_data *td = ic->proto_data; 498 557 struct twitter_user_data *tud; 499 bee_user_t *bu ;558 bee_user_t *bu = NULL; 500 559 guint64 id = 0; 501 if (g_str_has_prefix(arg, "#") && 502 sscanf(arg + 1, "%" G_GUINT64_FORMAT, &id) == 1) { 503 if (id < TWITTER_LOG_LENGTH && td->log) 560 561 if (bu_) 562 *bu_ = NULL; 563 if (!arg || !arg[0]) 564 return 0; 565 566 if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { 567 if ((tud = bu->data)) 568 id = tud->last_id; 569 } else { 570 if (arg[0] == '#') 571 arg++; 572 if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 && 573 id < TWITTER_LOG_LENGTH) { 574 bu = td->log[id].bu; 504 575 id = td->log[id].id; 505 } else if ((bu = bee_user_by_handle(ic->bee, ic, arg)) && 506 (tud = bu->data) && tud->last_id) 507 id = tud->last_id; 508 else if (sscanf(arg, "%" G_GUINT64_FORMAT, &id) == 1){ 509 if (id < TWITTER_LOG_LENGTH && td->log) 510 id = td->log[id].id; 511 } 576 /* Beware of dangling pointers! */ 577 if (!g_slist_find(ic->bee->users, bu)) 578 bu = NULL; 579 } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) { 580 /* Allow normal tweet IDs as well; not a very useful 581 feature but it's always been there. Just ignore 582 very low IDs to avoid accidents. */ 583 if (id < 1000000) 584 id = 0; 585 } 586 } 587 if (bu_) 588 *bu_ = bu; 512 589 return id; 513 590 } … … 517 594 struct twitter_data *td = ic->proto_data; 518 595 char *cmds, **cmd, *new = NULL; 519 guint64 in_reply_to = 0; 596 guint64 in_reply_to = 0, id; 597 gboolean allow_post = 598 g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; 599 bee_user_t *bu = NULL; 520 600 521 601 cmds = g_strdup(message); … … 523 603 524 604 if (cmd[0] == NULL) { 525 g_free(cmds); 526 return; 527 } else if (!set_getbool(&ic->acc->set, "commands")) { 528 /* Not supporting commands. */ 605 goto eof; 606 } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { 607 /* Not supporting commands if "commands" is set to true/strict. */ 529 608 } else if (g_strcasecmp(cmd[0], "undo") == 0) { 530 guint64 id;531 532 609 if (cmd[1] == NULL) 533 610 twitter_status_destroy(ic, td->last_status_id); 534 else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) { 535 if (id < TWITTER_LOG_LENGTH && td->log) 536 id = td->log[id].id; 537 611 else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) 538 612 twitter_status_destroy(ic, id); 539 } else 540 twitter_msg(ic, "Could not undo last action"); 541 542 g_free(cmds); 543 return; 613 else 614 twitter_log(ic, "Could not undo last action"); 615 616 goto eof; 544 617 } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) { 545 guint64 id; 546 if ((id = twitter_message_id_from_command_arg(ic, td, cmd[1]))) { 618 if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { 547 619 twitter_favourite_tweet(ic, id); 548 620 } else { 549 twitter_ msg(ic, "Please provide a message ID or username.");621 twitter_log(ic, "Please provide a message ID or username."); 550 622 } 551 g_free(cmds); 552 return; 623 goto eof; 553 624 } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) { 554 625 twitter_add_buddy(ic, cmd[1], NULL); 555 g_free(cmds); 556 return; 626 goto eof; 557 627 } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) { 558 628 twitter_remove_buddy(ic, cmd[1], NULL); 559 g_free(cmds); 560 return; 629 goto eof; 561 630 } else if ((g_strcasecmp(cmd[0], "report") == 0 || 562 631 g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { 563 char * screen_name; 564 guint64 id; 565 screen_name = cmd[1]; 632 char *screen_name; 633 566 634 /* Report nominally works on users but look up the user who 567 635 posted the given ID if the user wants to do it that way */ 568 if (g_str_has_prefix(cmd[1], "#") && 569 sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) { 570 if (id < TWITTER_LOG_LENGTH && td->log) { 571 if (g_slist_find(ic->bee->users, td->log[id].bu)) { 572 screen_name = td->log[id].bu->handle; 573 } 574 } 575 } 636 twitter_message_id_from_command_arg(ic, cmd[1], &bu); 637 if (bu) 638 screen_name = bu->handle; 639 else 640 screen_name = cmd[1]; 641 576 642 twitter_report_spam(ic, screen_name); 577 g_free(cmds); 578 return; 643 goto eof; 579 644 } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { 580 guint64 id = twitter_message_id_from_command_arg(ic, td, cmd[1]);645 id = twitter_message_id_from_command_arg(ic, cmd[1], NULL); 581 646 582 647 td->last_status_id = 0; … … 584 649 twitter_status_retweet(ic, id); 585 650 else 586 twitter_ msg(ic, "User `%s' does not exist or didn't "651 twitter_log(ic, "User `%s' does not exist or didn't " 587 652 "post any statuses recently", cmd[1]); 588 653 589 g_free(cmds); 590 return; 654 goto eof; 591 655 } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { 592 struct twitter_user_data *tud; 593 bee_user_t *bu = NULL; 594 guint64 id = 0; 595 596 if (g_str_has_prefix(cmd[1], "#") && 597 sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 && 598 (id < TWITTER_LOG_LENGTH) && td->log) { 599 bu = td->log[id].bu; 600 if (g_slist_find(ic->bee->users, bu)) 601 id = td->log[id].id; 602 else 603 bu = NULL; 604 } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) && 605 (tud = bu->data) && tud->last_id) { 606 id = tud->last_id; 607 } else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 && 608 (id < TWITTER_LOG_LENGTH) && td->log) { 609 bu = td->log[id].bu; 610 if (g_slist_find(ic->bee->users, bu)) 611 id = td->log[id].id; 612 else 613 bu = NULL; 614 } 615 656 id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); 616 657 if (!id || !bu) { 617 twitter_ msg(ic, "User `%s' does not exist or didn't "658 twitter_log(ic, "User `%s' does not exist or didn't " 618 659 "post any statuses recently", cmd[1]); 619 g_free(cmds); 620 return; 660 goto eof; 621 661 } 622 662 message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0])); 623 663 in_reply_to = id; 664 allow_post = TRUE; 624 665 } else if (g_strcasecmp(cmd[0], "post") == 0) { 625 666 message += 5; 626 } 627 628 { 667 allow_post = TRUE; 668 } 669 670 if (allow_post) { 629 671 char *s; 630 bee_user_t *bu; 631 632 if (!twitter_length_check(ic, message)) { 633 g_free(new); 634 g_free(cmds); 635 return; 636 } 672 673 if (!twitter_length_check(ic, message)) 674 goto eof; 637 675 638 676 s = cmd[0] + strlen(cmd[0]) - 1; … … 657 695 td->last_status_id = 0; 658 696 twitter_post_status(ic, message, in_reply_to); 659 g_free(new); 660 } 697 } else { 698 twitter_log(ic, "Unknown command: %s", cmd[0]); 699 } 700 eof: 701 g_free(new); 661 702 g_free(cmds); 662 703 } 704 705 void twitter_log(struct im_connection *ic, char *format, ... ) 706 { 707 struct twitter_data *td = ic->proto_data; 708 va_list params; 709 char *text; 710 711 va_start(params, format); 712 text = g_strdup_vprintf(format, params); 713 va_end(params); 714 715 if (td->timeline_gc) 716 imcb_chat_log(td->timeline_gc, "%s", text); 717 else 718 imcb_log(ic, "%s", text); 719 720 g_free(text); 721 } 722 663 723 664 724 void twitter_initmodule() -
protocols/twitter/twitter.h
r92d3044 rcc6fdf8 4 4 * Simple module to facilitate twitter functionality. * 5 5 * * 6 * Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * 6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> * 7 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 7 8 * * 8 9 * This library is free software; you can redistribute it and/or * … … 35 36 typedef enum 36 37 { 37 TWITTER_HAVE_FRIENDS = 1, 38 TWITTER_HAVE_FRIENDS = 0x00001, 39 TWITTER_MODE_ONE = 0x00002, 40 TWITTER_MODE_MANY = 0x00004, 41 TWITTER_MODE_CHAT = 0x00008, 38 42 TWITTER_DOING_TIMELINE = 0x10000, 39 TWITTER_GOT_TIMELINE = 0x20000,40 TWITTER_GOT_MENTIONS = 0x40000,43 TWITTER_GOT_TIMELINE = 0x20000, 44 TWITTER_GOT_MENTIONS = 0x40000, 41 45 } twitter_flags_t; 42 46 … … 57 61 guint64 last_status_id; /* For undo */ 58 62 gint main_loop_id; 63 struct http_request *stream; 59 64 struct groupchat *timeline_gc; 60 65 gint http_fails; … … 80 85 }; 81 86 82 #define TWITTER_LOG_LENGTH 10087 #define TWITTER_LOG_LENGTH 256 83 88 struct twitter_log_data 84 89 { … … 99 104 char *twitter_parse_error( struct http_request *req ); 100 105 106 void twitter_log(struct im_connection *ic, char *format, ... ); 107 struct groupchat *twitter_groupchat_init(struct im_connection *ic); 108 101 109 #endif //_TWITTER_H -
protocols/twitter/twitter_http.c
r92d3044 rcc6fdf8 47 47 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c 48 48 */ 49 void*twitter_http(struct im_connection *ic, char *url_string, http_input_function func,50 gpointer data, int is_post, char **arguments, int arguments_len)49 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 50 gpointer data, int is_post, char **arguments, int arguments_len) 51 51 { 52 52 struct twitter_data *td = ic->proto_data; … … 55 55 void *ret; 56 56 char *url_arguments; 57 url_t *base_url = NULL; 57 58 58 59 url_arguments = g_strdup(""); … … 67 68 } 68 69 } 70 71 if (strstr(url_string, "://")) { 72 base_url = g_new0(url_t, 1); 73 if (!url_set(base_url, url_string)) { 74 g_free(base_url); 75 return NULL; 76 } 77 } 78 69 79 // Make the request. 70 80 g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n" … … 72 82 "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n", 73 83 is_post ? "POST" : "GET", 74 td->url_path, url_string, 75 is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host); 84 base_url ? base_url->file : td->url_path, 85 base_url ? "" : url_string, 86 is_post ? "" : "?", is_post ? "" : url_arguments, 87 base_url ? base_url->host : td->url_host); 76 88 77 89 // If a pass and user are given we append them to the request. … … 80 92 char *full_url; 81 93 82 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); 94 if (base_url) 95 full_url = g_strdup(url_string); 96 else 97 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); 83 98 full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", 84 99 full_url, url_arguments); … … 109 124 } 110 125 111 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); 126 if (base_url) 127 ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); 128 else 129 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); 112 130 113 131 g_free(url_arguments); 114 132 g_string_free(request, TRUE); 133 g_free(base_url); 134 return ret; 135 } 136 137 struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, 138 gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags) 139 { 140 struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len); 141 if (ret) 142 ret->flags |= flags; 115 143 return ret; 116 144 } -
protocols/twitter/twitter_http.h
r92d3044 rcc6fdf8 28 28 #include "http_client.h" 29 29 30 typedef enum { 31 /* With this set, twitter_http_post() will post a generic confirmation 32 message to the user. */ 33 TWITTER_HTTP_USER_ACK = 0x1000000, 34 } twitter_http_flags_t; 35 30 36 struct oauth_info; 31 37 32 void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 33 gpointer data, int is_post, char** arguments, int arguments_len); 38 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 39 gpointer data, int is_post, char** arguments, int arguments_len); 40 struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, 41 gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags); 34 42 35 43 #endif //_TWITTER_HTTP_H -
protocols/twitter/twitter_lib.c
r92d3044 rcc6fdf8 35 35 #include "misc.h" 36 36 #include "base64.h" 37 #include "xmltree.h"38 37 #include "twitter_lib.h" 38 #include "json_util.h" 39 39 #include <ctype.h> 40 40 #include <errno.h> … … 67 67 char *text; 68 68 struct twitter_xml_user *user; 69 guint64 id, reply_to; 69 guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ 70 guint64 reply_to; 70 71 }; 71 72 static void twitter_groupchat_init(struct im_connection *ic);73 72 74 73 /** … … 148 147 // Check if the buddy is already in the buddy list. 149 148 if (!bee_user_by_handle(ic->bee, ic, name)) { 150 char *mode = set_getstr(&ic->acc->set, "mode");151 152 149 // The buddy is not in the list, add the buddy and set the status to logged in. 153 150 imcb_add_buddy(ic, name, NULL); 154 151 imcb_rename_buddy(ic, name, fullname); 155 if ( g_strcasecmp(mode, "chat") == 0) {152 if (td->flags & TWITTER_MODE_CHAT) { 156 153 /* Necessary so that nicks always get translated to the 157 154 exact Twitter username. */ 158 155 imcb_buddy_nick_hint(ic, name, name); 159 imcb_chat_add_buddy(td->timeline_gc, name); 160 } else if (g_strcasecmp(mode, "many") == 0) 156 if (td->timeline_gc) 157 imcb_chat_add_buddy(td->timeline_gc, name); 158 } else if (td->flags & TWITTER_MODE_MANY) 161 159 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); 162 160 } … … 168 166 { 169 167 static char *ret = NULL; 170 struct xt_node *root, *node, *err;168 json_value *root, *err; 171 169 172 170 g_free(ret); … … 174 172 175 173 if (req->body_size > 0) { 176 root = xt_from_string(req->reply_body, req->body_size);177 178 for (node = root; node; node = node->next)179 if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) {180 ret = g_strdup_printf("%s (%s)", req->status_string, err->text);181 break;182 }183 184 xt_free_node(root);174 root = json_parse(req->reply_body); 175 err = json_o_get(root, "errors"); 176 if (err && err->type == json_array && (err = err->u.array.values[0]) && 177 err->type == json_object) { 178 const char *msg = json_o_str(err, "message"); 179 if (msg) 180 ret = g_strdup_printf("%s (%s)", req->status_string, msg); 181 } 182 json_value_free(root); 185 183 } 186 184 … … 188 186 } 189 187 190 static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req) 188 /* WATCH OUT: This function might or might not destroy your connection. 189 Sub-optimal indeed, but just be careful when this returns NULL! */ 190 static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) 191 191 { 192 192 gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); 193 193 gboolean periodic; 194 194 struct twitter_data *td = ic->proto_data; 195 struct xt_node *ret;195 json_value *ret; 196 196 char path[64] = "", *s; 197 197 … … 211 211 throwing 401s so I'll keep treating this one as fatal 212 212 only during login. */ 213 imcb_error(ic, "Authentication failure"); 213 imcb_error(ic, "Authentication failure (%s)", 214 twitter_parse_error(req)); 214 215 imc_logout(ic, FALSE); 215 216 return NULL; … … 217 218 // It didn't go well, output the error and return. 218 219 if (!periodic || logging_in || ++td->http_fails >= 5) 219 imcb_error(ic, "Could not retrieve %s: %s",220 path, twitter_parse_error(req));220 twitter_log(ic, "Error: Could not retrieve %s: %s", 221 path, twitter_parse_error(req)); 221 222 222 223 if (logging_in) … … 227 228 } 228 229 229 if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {230 if ((ret = json_parse(req->reply_body)) == NULL) { 230 231 imcb_error(ic, "Could not retrieve %s: %s", 231 232 path, "XML parse error"); … … 251 252 252 253 /** 253 * Function to help fill a list.254 */255 static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl)256 {257 char *end = NULL;258 259 if (node->text)260 txl->next_cursor = g_ascii_strtoll(node->text, &end, 10);261 if (end == NULL)262 txl->next_cursor = -1;263 264 return XT_HANDLED;265 }266 267 /**268 254 * Fill a list of ids. 269 255 */ 270 static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl) 271 { 272 struct xt_node *child; 256 static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) 257 { 258 json_value *c; 259 int i; 273 260 274 261 // Set the list type. 275 262 txl->type = TXL_ID; 276 263 277 // The root <statuses> node should hold the list of statuses <status> 278 // Walk over the nodes children. 279 for (child = node->children; child; child = child->next) { 280 if (g_strcasecmp("ids", child->name) == 0) { 281 struct xt_node *idc; 282 for (idc = child->children; idc; idc = idc->next) 283 if (g_strcasecmp(idc->name, "id") == 0) 284 txl->list = g_slist_prepend(txl->list, 285 g_memdup(idc->text, idc->text_len + 1)); 286 } else if (g_strcasecmp("next_cursor", child->name) == 0) { 287 twitter_xt_next_cursor(child, txl); 288 } 289 } 290 291 return XT_HANDLED; 264 c = json_o_get(node, "ids"); 265 if (!c || c->type != json_array) 266 return FALSE; 267 268 for (i = 0; i < c->u.array.length; i ++) { 269 if (c->u.array.values[i]->type != json_integer) 270 continue; 271 272 txl->list = g_slist_prepend(txl->list, 273 g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); 274 } 275 276 c = json_o_get(node, "next_cursor"); 277 if (c && c->type == json_integer) 278 txl->next_cursor = c->u.integer; 279 else 280 txl->next_cursor = -1; 281 282 return TRUE; 292 283 } 293 284 … … 300 291 { 301 292 struct im_connection *ic; 302 struct xt_node *parsed;293 json_value *parsed; 303 294 struct twitter_xml_list *txl; 304 295 struct twitter_data *td; … … 312 303 td = ic->proto_data; 313 304 314 /* Create the room now that we "logged in". */315 if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)316 twitter_groupchat_init(ic);317 318 305 txl = g_new0(struct twitter_xml_list, 1); 319 306 txl->list = td->follow_ids; … … 322 309 if (!(parsed = twitter_parse_response(ic, req))) 323 310 return; 311 324 312 twitter_xt_get_friends_id_list(parsed, txl); 325 xt_free_node(parsed);313 json_value_free(parsed); 326 314 327 315 td->follow_ids = txl->list; … … 338 326 } 339 327 340 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);328 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); 341 329 static void twitter_http_get_users_lookup(struct http_request *req); 342 330 … … 379 367 { 380 368 struct im_connection *ic = req->data; 381 struct xt_node *parsed;369 json_value *parsed; 382 370 struct twitter_xml_list *txl; 383 371 GSList *l = NULL; … … 395 383 return; 396 384 twitter_xt_get_users(parsed, txl); 397 xt_free_node(parsed);385 json_value_free(parsed); 398 386 399 387 // Add the users as buddies. … … 409 397 } 410 398 411 /** 412 * Function to fill a twitter_xml_user struct. 413 * It sets: 414 * - the name and 415 * - the screen_name. 416 */ 417 static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu) 418 { 419 struct xt_node *child; 420 421 // Walk over the nodes children. 422 for (child = node->children; child; child = child->next) { 423 if (g_strcasecmp("name", child->name) == 0) { 424 txu->name = g_memdup(child->text, child->text_len + 1); 425 } else if (g_strcasecmp("screen_name", child->name) == 0) { 426 txu->screen_name = g_memdup(child->text, child->text_len + 1); 427 } 428 } 429 return XT_HANDLED; 399 struct twitter_xml_user *twitter_xt_get_user(const json_value *node) 400 { 401 struct twitter_xml_user *txu; 402 403 txu = g_new0(struct twitter_xml_user, 1); 404 txu->name = g_strdup(json_o_str(node, "name")); 405 txu->screen_name = g_strdup(json_o_str(node, "screen_name")); 406 407 return txu; 430 408 } 431 409 … … 435 413 * - all <user>s from the <users> element. 436 414 */ 437 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)415 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) 438 416 { 439 417 struct twitter_xml_user *txu; 440 struct xt_node *child;418 int i; 441 419 442 420 // Set the type of the list. 443 421 txl->type = TXL_USER; 444 422 423 if (!node || node->type != json_array) 424 return FALSE; 425 445 426 // The root <users> node should hold the list of users <user> 446 427 // Walk over the nodes children. 447 for (child = node->children; child; child = child->next) { 448 if (g_strcasecmp("user", child->name) == 0) { 449 txu = g_new0(struct twitter_xml_user, 1); 450 twitter_xt_get_user(child, txu); 451 // Put the item in the front of the list. 428 for (i = 0; i < node->u.array.length; i ++) { 429 txu = twitter_xt_get_user(node->u.array.values[i]); 430 if (txu) 452 431 txl->list = g_slist_prepend(txl->list, txu); 453 } 454 } 455 456 return XT_HANDLED; 432 } 433 434 return TRUE; 457 435 } 458 436 … … 462 440 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" 463 441 #endif 442 443 static char* expand_entities(char* text, const json_value *entities); 464 444 465 445 /** … … 471 451 * - the user in a twitter_xml_user struct. 472 452 */ 473 static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs) 474 { 475 struct xt_node *child, *rt = NULL; 476 477 // Walk over the nodes children. 478 for (child = node->children; child; child = child->next) { 479 if (g_strcasecmp("text", child->name) == 0) { 480 txs->text = g_memdup(child->text, child->text_len + 1); 481 } else if (g_strcasecmp("retweeted_status", child->name) == 0) { 482 rt = child; 483 } else if (g_strcasecmp("created_at", child->name) == 0) { 453 static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) 454 { 455 struct twitter_xml_status *txs; 456 const json_value *rt = NULL, *entities = NULL; 457 458 if (node->type != json_object) 459 return FALSE; 460 txs = g_new0(struct twitter_xml_status, 1); 461 462 JSON_O_FOREACH (node, k, v) { 463 if (strcmp("text", k) == 0 && v->type == json_string) { 464 txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); 465 strip_html(txs->text); 466 } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { 467 rt = v; 468 } else if (strcmp("created_at", k) == 0 && v->type == json_string) { 484 469 struct tm parsed; 485 470 … … 487 472 this field. :-( Also assumes the timezone used 488 473 is UTC since C time handling functions suck. */ 489 if (strptime( child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)474 if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) 490 475 txs->created_at = mktime_utc(&parsed); 491 } else if (g_strcasecmp("user", child->name) == 0) { 492 txs->user = g_new0(struct twitter_xml_user, 1); 493 twitter_xt_get_user(child, txs->user); 494 } else if (g_strcasecmp("id", child->name) == 0) { 495 txs->id = g_ascii_strtoull(child->text, NULL, 10); 496 } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) { 497 txs->reply_to = g_ascii_strtoull(child->text, NULL, 10); 476 } else if (strcmp("user", k) == 0 && v->type == json_object) { 477 txs->user = twitter_xt_get_user(v); 478 } else if (strcmp("id", k) == 0 && v->type == json_integer) { 479 txs->rt_id = txs->id = v->u.integer; 480 } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { 481 txs->reply_to = v->u.integer; 482 } else if (strcmp("entities", k) == 0 && v->type == json_object) { 483 entities = v; 498 484 } 499 485 } … … 502 488 wasn't truncated because it may be lying. */ 503 489 if (rt) { 504 struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); 505 if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) { 490 struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); 491 if (rtxs) { 492 g_free(txs->text); 493 txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); 494 txs->id = rtxs->id; 506 495 txs_free(rtxs); 507 return XT_HANDLED; 508 } 509 510 g_free(txs->text); 511 txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); 512 txs_free(rtxs); 513 } else { 514 struct xt_node *urls, *url; 496 } 497 } else if (entities) { 498 txs->text = expand_entities(txs->text, entities); 499 } 500 501 if (txs->text && txs->user && txs->id) 502 return txs; 503 504 txs_free(txs); 505 return NULL; 506 } 507 508 /** 509 * Function to fill a twitter_xml_status struct (DM variant). 510 */ 511 static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) 512 { 513 struct twitter_xml_status *txs; 514 const json_value *entities = NULL; 515 516 if (node->type != json_object) 517 return FALSE; 518 txs = g_new0(struct twitter_xml_status, 1); 519 520 JSON_O_FOREACH (node, k, v) { 521 if (strcmp("text", k) == 0 && v->type == json_string) { 522 txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); 523 strip_html(txs->text); 524 } else if (strcmp("created_at", k) == 0 && v->type == json_string) { 525 struct tm parsed; 526 527 /* Very sensitive to changes to the formatting of 528 this field. :-( Also assumes the timezone used 529 is UTC since C time handling functions suck. */ 530 if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) 531 txs->created_at = mktime_utc(&parsed); 532 } else if (strcmp("sender", k) == 0 && v->type == json_object) { 533 txs->user = twitter_xt_get_user(v); 534 } else if (strcmp("id", k) == 0 && v->type == json_integer) { 535 txs->id = v->u.integer; 536 } 537 } 538 539 if (entities) { 540 txs->text = expand_entities(txs->text, entities); 541 } 542 543 if (txs->text && txs->user && txs->id) 544 return txs; 545 546 txs_free(txs); 547 return NULL; 548 } 549 550 static char* expand_entities(char* text, const json_value *entities) { 551 JSON_O_FOREACH (entities, k, v) { 552 int i; 515 553 516 urls = xt_find_path(node, "entities"); 517 if (urls != NULL) 518 urls = urls->children; 519 for (; urls; urls = urls->next) { 520 if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0) 554 if (v->type != json_array) 555 continue; 556 if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) 557 continue; 558 559 for (i = 0; i < v->u.array.length; i ++) { 560 if (v->u.array.values[i]->type != json_object) 521 561 continue; 522 562 523 for (url = urls ? urls->children : NULL; url; url = url->next) { 524 /* "short" is a reserved word. :-P */ 525 struct xt_node *kort = xt_find_node(url->children, "url"); 526 struct xt_node *disp = xt_find_node(url->children, "display_url"); 527 char *pos, *new; 528 529 if (!kort || !kort->text || !disp || !disp->text || 530 !(pos = strstr(txs->text, kort->text))) 531 continue; 532 533 *pos = '\0'; 534 new = g_strdup_printf("%s%s <%s>%s", txs->text, kort->text, 535 disp->text, pos + strlen(kort->text)); 536 537 g_free(txs->text); 538 txs->text = new; 539 } 540 } 541 } 542 543 return XT_HANDLED; 563 const char *kort = json_o_str(v->u.array.values[i], "url"); 564 const char *disp = json_o_str(v->u.array.values[i], "display_url"); 565 char *pos, *new; 566 567 if (!kort || !disp || !(pos = strstr(text, kort))) 568 continue; 569 570 *pos = '\0'; 571 new = g_strdup_printf("%s%s <%s>%s", text, kort, 572 disp, pos + strlen(kort)); 573 574 g_free(text); 575 text = new; 576 } 577 } 578 579 return text; 544 580 } 545 581 … … 550 586 * - the next_cursor. 551 587 */ 552 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,553 588 static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, 589 struct twitter_xml_list *txl) 554 590 { 555 591 struct twitter_xml_status *txs; 556 struct xt_node *child; 557 bee_user_t *bu; 592 int i; 558 593 559 594 // Set the type of the list. 560 595 txl->type = TXL_STATUS; 596 597 if (node->type != json_array) 598 return FALSE; 561 599 562 600 // The root <statuses> node should hold the list of statuses <status> 563 601 // Walk over the nodes children. 564 for (child = node->children; child; child = child->next) { 565 if (g_strcasecmp("status", child->name) == 0) { 566 txs = g_new0(struct twitter_xml_status, 1); 567 twitter_xt_get_status(child, txs); 568 // Put the item in the front of the list. 569 txl->list = g_slist_prepend(txl->list, txs); 570 571 if (txs->user && txs->user->screen_name && 572 (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { 573 struct twitter_user_data *tud = bu->data; 574 575 if (txs->id > tud->last_id) { 576 tud->last_id = txs->id; 577 tud->last_time = txs->created_at; 578 } 579 } 580 } else if (g_strcasecmp("next_cursor", child->name) == 0) { 581 twitter_xt_next_cursor(child, txl); 582 } 583 } 584 585 return XT_HANDLED; 586 } 587 602 for (i = 0; i < node->u.array.length; i ++) { 603 txs = twitter_xt_get_status(node->u.array.values[i]); 604 if (!txs) 605 continue; 606 607 txl->list = g_slist_prepend(txl->list, txs); 608 } 609 610 return TRUE; 611 } 612 613 /* Will log messages either way. Need to keep track of IDs for stream deduping. 614 Plus, show_ids is on by default and I don't see why anyone would disable it. */ 588 615 static char *twitter_msg_add_id(struct im_connection *ic, 589 616 struct twitter_xml_status *txs, const char *prefix) 590 617 { 591 618 struct twitter_data *td = ic->proto_data; 592 char *ret = NULL; 593 594 if (!set_getbool(&ic->acc->set, "show_ids")) { 619 int reply_to = -1; 620 bee_user_t *bu; 621 622 if (txs->reply_to) { 623 int i; 624 for (i = 0; i < TWITTER_LOG_LENGTH; i++) 625 if (td->log[i].id == txs->reply_to) { 626 reply_to = i; 627 break; 628 } 629 } 630 631 if (txs->user && txs->user->screen_name && 632 (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { 633 struct twitter_user_data *tud = bu->data; 634 635 if (txs->id > tud->last_id) { 636 tud->last_id = txs->id; 637 tud->last_time = txs->created_at; 638 } 639 } 640 641 td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; 642 td->log[td->log_id].id = txs->id; 643 td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); 644 645 /* This is all getting hairy. :-( If we RT'ed something ourselves, 646 remember OUR id instead so undo will work. In other cases, the 647 original tweet's id should be remembered for deduplicating. */ 648 if (strcmp(txs->user->screen_name, td->user) == 0) 649 td->log[td->log_id].id = txs->rt_id; 650 651 if (set_getbool(&ic->acc->set, "show_ids")) { 652 if (reply_to != -1) 653 return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s", 654 td->log_id, reply_to, prefix, txs->text); 655 else 656 return g_strdup_printf("\002[\002%02x\002]\002 %s%s", 657 td->log_id, prefix, txs->text); 658 } else { 595 659 if (*prefix) 596 660 return g_strconcat(prefix, txs->text, NULL); … … 598 662 return NULL; 599 663 } 600 601 td->log[td->log_id].id = txs->id; 602 td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); 603 if (txs->reply_to) { 604 int i; 605 for (i = 0; i < TWITTER_LOG_LENGTH; i++) 606 if (td->log[i].id == txs->reply_to) { 607 ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s", 608 td->log_id, i, prefix, txs->text); 609 break; 610 } 611 } 612 if (ret == NULL) 613 ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text); 614 td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; 615 616 return ret; 617 } 618 619 static void twitter_groupchat_init(struct im_connection *ic) 620 { 621 char *name_hint; 664 } 665 666 /** 667 * Function that is called to see the statuses in a groupchat window. 668 */ 669 static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status) 670 { 671 struct twitter_data *td = ic->proto_data; 622 672 struct groupchat *gc; 623 struct twitter_data *td = ic->proto_data; 624 GSList *l; 625 626 td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); 627 628 name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); 629 imcb_chat_name_hint(gc, name_hint); 630 g_free(name_hint); 631 632 for (l = ic->bee->users; l; l = l->next) { 633 bee_user_t *bu = l->data; 634 if (bu->ic == ic) 635 imcb_chat_add_buddy(td->timeline_gc, bu->handle); 636 } 637 } 638 639 /** 640 * Function that is called to see the statuses in a groupchat window. 641 */ 642 static void twitter_groupchat(struct im_connection *ic, GSList * list) 643 { 644 struct twitter_data *td = ic->proto_data; 645 GSList *l = NULL; 646 struct twitter_xml_status *status; 647 struct groupchat *gc; 648 guint64 last_id = 0; 673 gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; 674 char *msg; 649 675 650 676 // Create a new groupchat if it does not exsist. 651 if (!td->timeline_gc) 652 twitter_groupchat_init(ic); 653 654 gc = td->timeline_gc; 655 if (!gc->joined) 656 imcb_chat_add_buddy(gc, ic->acc->user); 657 658 for (l = list; l; l = g_slist_next(l)) { 659 char *msg; 660 661 status = l->data; 662 if (status->user == NULL || status->text == NULL || last_id == status->id) 663 continue; 664 665 last_id = status->id; 666 667 strip_html(status->text); 668 669 if (set_getbool(&ic->acc->set, "strip_newlines")) 670 strip_newlines(status->text); 671 672 msg = twitter_msg_add_id(ic, status, ""); 673 674 // Say it! 675 if (g_strcasecmp(td->user, status->user->screen_name) == 0) { 676 imcb_chat_log(gc, "You: %s", msg ? msg : status->text); 677 } else { 678 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 679 680 imcb_chat_msg(gc, status->user->screen_name, 681 msg ? msg : status->text, 0, status->created_at); 682 } 683 684 g_free(msg); 685 686 // Update the timeline_id to hold the highest id, so that by the next request 687 // we won't pick up the updates already in the list. 688 td->timeline_id = MAX(td->timeline_id, status->id); 689 } 677 gc = twitter_groupchat_init(ic); 678 679 if (!me) 680 /* MUST be done before twitter_msg_add_id() to avoid #872. */ 681 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 682 msg = twitter_msg_add_id(ic, status, ""); 683 684 // Say it! 685 if (me) { 686 imcb_chat_log(gc, "You: %s", msg ? msg : status->text); 687 } else { 688 imcb_chat_msg(gc, status->user->screen_name, 689 msg ? msg : status->text, 0, status->created_at); 690 } 691 692 g_free(msg); 690 693 } 691 694 … … 693 696 * Function that is called to see statuses as private messages. 694 697 */ 695 static void twitter_private_message_chat(struct im_connection *ic, GSList * list) 696 { 697 struct twitter_data *td = ic->proto_data; 698 GSList *l = NULL; 699 struct twitter_xml_status *status; 700 char from[MAX_STRING]; 701 gboolean mode_one; 702 guint64 last_id = 0; 703 704 mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0; 705 706 if (mode_one) { 698 static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status) 699 { 700 struct twitter_data *td = ic->proto_data; 701 char from[MAX_STRING] = ""; 702 char *prefix = NULL, *text = NULL; 703 gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; 704 705 if (td->flags & TWITTER_MODE_ONE) { 707 706 g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); 708 707 from[MAX_STRING - 1] = '\0'; 709 708 } 710 709 711 for (l = list; l; l = g_slist_next(l)) { 712 char *prefix = NULL, *text = NULL; 713 714 status = l->data; 715 if (status->user == NULL || status->text == NULL || last_id == status->id) 716 continue; 717 718 last_id = status->id; 719 720 strip_html(status->text); 721 if (mode_one) 722 prefix = g_strdup_printf("\002<\002%s\002>\002 ", 723 status->user->screen_name); 724 else 725 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 726 727 text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); 728 729 imcb_buddy_msg(ic, 730 mode_one ? from : status->user->screen_name, 731 text ? text : status->text, 0, status->created_at); 732 733 // Update the timeline_id to hold the highest id, so that by the next request 734 // we won't pick up the updates already in the list. 735 td->timeline_id = MAX(td->timeline_id, status->id); 736 737 g_free(text); 738 g_free(prefix); 739 } 740 } 741 742 static void twitter_http_get_home_timeline(struct http_request *req); 743 static void twitter_http_get_mentions(struct http_request *req); 710 if (td->flags & TWITTER_MODE_ONE) 711 prefix = g_strdup_printf("\002<\002%s\002>\002 ", 712 status->user->screen_name); 713 else if (!me) 714 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 715 else 716 prefix = g_strdup("You: "); 717 718 text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); 719 720 imcb_buddy_msg(ic, 721 *from ? from : status->user->screen_name, 722 text ? text : status->text, 0, status->created_at); 723 724 g_free(text); 725 g_free(prefix); 726 } 727 728 static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status) 729 { 730 struct twitter_data *td = ic->proto_data; 731 732 if (status->user == NULL || status->text == NULL) 733 return; 734 735 /* Grrrr. Would like to do this during parsing, but can't access 736 settings from there. */ 737 if (set_getbool(&ic->acc->set, "strip_newlines")) 738 strip_newlines(status->text); 739 740 if (td->flags & TWITTER_MODE_CHAT) 741 twitter_status_show_chat(ic, status); 742 else 743 twitter_status_show_msg(ic, status); 744 745 // Update the timeline_id to hold the highest id, so that by the next request 746 // we won't pick up the updates already in the list. 747 td->timeline_id = MAX(td->timeline_id, status->rt_id); 748 } 749 750 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); 751 752 static void twitter_http_stream(struct http_request *req) 753 { 754 struct im_connection *ic = req->data; 755 struct twitter_data *td; 756 json_value *parsed; 757 int len = 0; 758 char c, *nl; 759 760 if (!g_slist_find(twitter_connections, ic)) 761 return; 762 763 ic->flags |= OPT_PONGED; 764 td = ic->proto_data; 765 766 if ((req->flags & HTTPC_EOF) || !req->reply_body) { 767 td->stream = NULL; 768 imcb_error(ic, "Stream closed (%s)", req->status_string); 769 imc_logout(ic, TRUE); 770 return; 771 } 772 773 printf( "%d bytes in stream\n", req->body_size ); 774 775 /* MUST search for CRLF, not just LF: 776 https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ 777 nl = strstr(req->reply_body, "\r\n"); 778 779 if (!nl) { 780 printf("Incomplete data\n"); 781 return; 782 } 783 784 len = nl - req->reply_body; 785 if (len > 0) { 786 c = req->reply_body[len]; 787 req->reply_body[len] = '\0'; 788 789 printf("JSON: %s\n", req->reply_body); 790 printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); 791 if (parsed) { 792 twitter_stream_handle_object(ic, parsed); 793 } 794 json_value_free(parsed); 795 req->reply_body[len] = c; 796 } 797 798 http_flush_bytes(req, len + 2); 799 800 /* One notification might bring multiple events! */ 801 if (req->body_size > 0) 802 twitter_http_stream(req); 803 } 804 805 static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); 806 static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs); 807 808 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) 809 { 810 struct twitter_data *td = ic->proto_data; 811 struct twitter_xml_status *txs; 812 json_value *c; 813 814 if ((txs = twitter_xt_get_status(o))) { 815 gboolean ret = twitter_stream_handle_status(ic, txs); 816 txs_free(txs); 817 return ret; 818 } else if ((c = json_o_get(o, "direct_message")) && 819 (txs = twitter_xt_get_dm(c))) { 820 if (strcmp(txs->user->screen_name, td->user) != 0) 821 imcb_buddy_msg(ic, txs->user->screen_name, 822 txs->text, 0, txs->created_at); 823 txs_free(txs); 824 return TRUE; 825 } else if ((c = json_o_get(o, "event")) && c->type == json_string) { 826 twitter_stream_handle_event(ic, o); 827 return TRUE; 828 } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) { 829 /* HACK: Because we're inside an event handler, we can't just 830 disconnect here. Instead, just change the HTTP status string 831 into a Twitter status string. */ 832 char *reason = json_o_strdup(c, "reason"); 833 if (reason) { 834 g_free(td->stream->status_string); 835 td->stream->status_string = reason; 836 } 837 return TRUE; 838 } 839 return FALSE; 840 } 841 842 static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs) 843 { 844 struct twitter_data *td = ic->proto_data; 845 int i; 846 847 for (i = 0; i < TWITTER_LOG_LENGTH; i++) { 848 if (td->log[i].id == txs->id) { 849 /* Got a duplicate (RT, probably). Drop it. */ 850 return TRUE; 851 } 852 } 853 854 if (!(strcmp(txs->user->screen_name, td->user) == 0 || 855 set_getbool(&ic->acc->set, "fetch_mentions") || 856 bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { 857 /* Tweet is from an unknown person and the user does not want 858 to see @mentions, so drop it. twitter_stream_handle_event() 859 picks up new follows so this simple filter should be safe. */ 860 /* TODO: The streaming API seems to do poor @mention matching. 861 I.e. I'm getting mentions for @WilmerSomething, not just for 862 @Wilmer. But meh. You want spam, you get spam. */ 863 return TRUE; 864 } 865 866 twitter_status_show(ic, txs); 867 868 return TRUE; 869 } 870 871 static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) 872 { 873 struct twitter_data *td = ic->proto_data; 874 json_value *source = json_o_get(o, "source"); 875 json_value *target = json_o_get(o, "target"); 876 const char *type = json_o_str(o, "event"); 877 878 if (!type || !source || source->type != json_object 879 || !target || target->type != json_object) { 880 return FALSE; 881 } 882 883 if (strcmp(type, "follow") == 0) { 884 struct twitter_xml_user *us = twitter_xt_get_user(source); 885 struct twitter_xml_user *ut = twitter_xt_get_user(target); 886 if (strcmp(us->screen_name, td->user) == 0) { 887 twitter_add_buddy(ic, ut->screen_name, ut->name); 888 } 889 txu_free(us); 890 txu_free(ut); 891 } 892 893 return TRUE; 894 } 895 896 gboolean twitter_open_stream(struct im_connection *ic) 897 { 898 struct twitter_data *td = ic->proto_data; 899 char *args[2] = {"with", "followings"}; 900 901 if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, 902 twitter_http_stream, ic, 0, args, 2))) { 903 /* This flag must be enabled or we'll get no data until EOF 904 (which err, kind of, defeats the purpose of a streaming API). */ 905 td->stream->flags |= HTTPC_STREAMING; 906 return TRUE; 907 } 908 909 return FALSE; 910 } 911 912 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); 913 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); 744 914 745 915 /** … … 778 948 struct twitter_xml_list *home_timeline = td->home_timeline_obj; 779 949 struct twitter_xml_list *mentions = td->mentions_obj; 950 guint64 last_id = 0; 780 951 GSList *output = NULL; 781 952 GSList *l; 782 953 954 imcb_connected(ic); 955 783 956 if (!(td->flags & TWITTER_GOT_TIMELINE)) { 784 957 return; … … 804 977 } 805 978 } 806 807 if (!(ic->flags & OPT_LOGGED_IN))808 imcb_connected(ic);809 979 810 980 // See if the user wants to see the messages in a groupchat window or as private messages. 811 if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) 812 twitter_groupchat(ic, output); 813 else 814 twitter_private_message_chat(ic, output); 815 816 g_slist_free(output); 981 while (output) { 982 struct twitter_xml_status *txs = output->data; 983 if (txs->id != last_id) 984 twitter_status_show(ic, txs); 985 last_id = txs->id; 986 output = g_slist_remove(output, txs); 987 } 817 988 818 989 txl_free(home_timeline); … … 823 994 } 824 995 996 static void twitter_http_get_home_timeline(struct http_request *req); 997 static void twitter_http_get_mentions(struct http_request *req); 998 825 999 /** 826 1000 * Get the timeline. 827 1001 */ 828 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)1002 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) 829 1003 { 830 1004 struct twitter_data *td = ic->proto_data; … … 862 1036 * Get mentions. 863 1037 */ 864 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)1038 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) 865 1039 { 866 1040 struct twitter_data *td = ic->proto_data; … … 893 1067 894 1068 g_free(args[1]); 895 if (td->timeline_id) { 896 g_free(args[5]); 897 } 1069 g_free(args[5]); 898 1070 } 899 1071 … … 905 1077 struct im_connection *ic = req->data; 906 1078 struct twitter_data *td; 907 struct xt_node *parsed;1079 json_value *parsed; 908 1080 struct twitter_xml_list *txl; 909 1081 … … 921 1093 goto end; 922 1094 twitter_xt_get_status_list(ic, parsed, txl); 923 xt_free_node(parsed);1095 json_value_free(parsed); 924 1096 925 1097 td->home_timeline_obj = txl; 926 1098 927 1099 end: 1100 if (!g_slist_find(twitter_connections, ic)) 1101 return; 1102 928 1103 td->flags |= TWITTER_GOT_TIMELINE; 929 1104 … … 938 1113 struct im_connection *ic = req->data; 939 1114 struct twitter_data *td; 940 struct xt_node *parsed;1115 json_value *parsed; 941 1116 struct twitter_xml_list *txl; 942 1117 … … 954 1129 goto end; 955 1130 twitter_xt_get_status_list(ic, parsed, txl); 956 xt_free_node(parsed);1131 json_value_free(parsed); 957 1132 958 1133 td->mentions_obj = txl; 959 1134 960 1135 end: 1136 if (!g_slist_find(twitter_connections, ic)) 1137 return; 1138 961 1139 td->flags |= TWITTER_GOT_MENTIONS; 962 1140 … … 972 1150 struct im_connection *ic = req->data; 973 1151 struct twitter_data *td; 974 struct xt_node *parsed, *node;1152 json_value *parsed, *id; 975 1153 976 1154 // Check if the connection is still active. … … 984 1162 return; 985 1163 986 if ((node = xt_find_node(parsed, "status")) && 987 (node = xt_find_node(node->children, "id")) && node->text) 988 td->last_status_id = g_ascii_strtoull(node->text, NULL, 10); 1164 if ((id = json_o_get(parsed, "id")) && id->type == json_integer) { 1165 td->last_status_id = id->u.integer; 1166 } 1167 1168 json_value_free(parsed); 1169 1170 if (req->flags & TWITTER_HTTP_USER_ACK) 1171 twitter_log(ic, "Command processed successfully"); 989 1172 } 990 1173 … … 1032 1215 char *url; 1033 1216 url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, 1034 (unsigned long long) id, ".xml"); 1035 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); 1217 (unsigned long long) id, ".json"); 1218 twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, 1219 TWITTER_HTTP_USER_ACK); 1036 1220 g_free(url); 1037 1221 } … … 1041 1225 char *url; 1042 1226 url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, 1043 (unsigned long long) id, ".xml"); 1044 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); 1227 (unsigned long long) id, ".json"); 1228 twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, 1229 TWITTER_HTTP_USER_ACK); 1045 1230 g_free(url); 1046 1231 } … … 1056 1241 }; 1057 1242 args[1] = screen_name; 1058 twitter_http (ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,1059 ic, 1, args, 2);1243 twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, 1244 ic, 1, args, 2, TWITTER_HTTP_USER_ACK); 1060 1245 } 1061 1246 … … 1067 1252 char *url; 1068 1253 url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL, 1069 (unsigned long long) id, ".xml"); 1070 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); 1254 (unsigned long long) id, ".json"); 1255 twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, 1256 TWITTER_HTTP_USER_ACK); 1071 1257 g_free(url); 1072 1258 } -
protocols/twitter/twitter_lib.h
r92d3044 rcc6fdf8 29 29 #include "twitter_http.h" 30 30 31 #define TWITTER_API_URL "http://api.twitter.com/1 "31 #define TWITTER_API_URL "http://api.twitter.com/1.1" 32 32 #define IDENTICA_API_URL "https://identi.ca/api" 33 33 34 34 /* Status URLs */ 35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update. xml"35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.json" 36 36 #define TWITTER_STATUS_SHOW_URL "/statuses/show/" 37 37 #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/" … … 39 39 40 40 /* Timeline URLs */ 41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline. xml"42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured. xml"43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline. xml"44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline. xml"45 #define TWITTER_MENTIONS_URL "/statuses/mentions .xml"46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline. xml"41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json" 42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" 43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" 44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" 45 #define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" 46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json" 47 47 48 48 /* Users URLs */ 49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup. xml"49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.json" 50 50 51 51 /* Direct messages URLs */ 52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages. xml"53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new. xml"54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent. xml"52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" 53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" 54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json" 55 55 #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" 56 56 57 57 /* Friendships URLs */ 58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create. xml"59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy. xml"60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show. xml"58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" 59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" 60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json" 61 61 62 62 /* Social graphs URLs */ 63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids. xml"64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids. xml"63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" 64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json" 65 65 66 66 /* Account URLs */ 67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status. xml"67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json" 68 68 69 69 /* Favorites URLs */ 70 #define TWITTER_FAVORITES_GET_URL "/favorites. xml"70 #define TWITTER_FAVORITES_GET_URL "/favorites.json" 71 71 #define TWITTER_FAVORITE_CREATE_URL "/favorites/create/" 72 72 #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" … … 77 77 78 78 /* Report spam */ 79 #define TWITTER_REPORT_SPAM_URL "/report_spam. xml"79 #define TWITTER_REPORT_SPAM_URL "/report_spam.json" 80 80 81 #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" 82 83 gboolean twitter_open_stream(struct im_connection *ic); 81 84 void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); 82 85 void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); 83 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);84 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);85 86 void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); 86 87
Note: See TracChangeset
for help on using the changeset viewer.