Changeset cc6fdf8


Ignore:
Timestamp:
2012-12-22T00:14:26Z (8 years ago)
Author:
Wilmer van der Gaast <wilmer@…>
Branches:
master
Children:
7d5afa6
Parents:
92d3044 (diff), 573e274 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merging JSON branch. It's very stable by now, and I want more testers.

Files:
4 added
18 edited

Legend:

Unmodified
Added
Removed
  • debian/copyright

    r92d3044 rcc6fdf8  
    88
    99Mainly 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
     12Bits 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
     17Other 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
    1221
    1322BitlBee License:
     
    3342
    3443The 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:
    3645
    3746============================================================================
    38                 GNU Free Documentation License
    39                    Version 1.1, March 2000
     47Copyright (c)  2002-2012 Jelmer Vernooij, Wilmer van der Gaast.
    4048
    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.
     49Permission is granted to copy, distribute and/or modify this document
     50under the terms of the GNU Free Documentation License, Version 1.1 or
     51any later version published by the Free Software Foundation; with no
     52Invariant Sections, with no Front-Cover Texts, and with no Back-Cover
     53Texts.  A copy of the license is included in the section entitled `GNU
     54Free Documentation License''.
    39355============================================================================
  • doc/user-guide/commands.xml

    r92d3044 rcc6fdf8  
    835835        <bitlbee-setting name="commands" type="boolean" scope="account">
    836836                <default>true</default>
     837                <possible-values>true, false, strict</possible-values>
    837838
    838839                <description>
     
    853854
    854855                        <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.
    856857                        </para>
    857858                </description>
     
    10231024
    10241025        </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>
    10251041       
    10261042        <bitlbee-setting name="target_url_length" type="integer" scope="account">
     
    13381354
    13391355        <bitlbee-setting name="show_ids" type="boolean" scope="account">
    1340                 <default>false</default>
     1356                <default>true</default>
    13411357
    13421358                <description>
  • lib/Makefile

    r92d3044 rcc6fdf8  
    1313
    1414# [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.o
     15objects = 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
    1616
    1717LFLAGS += -r
  • lib/http_client.c

    r92d3044 rcc6fdf8  
    193193}
    194194
     195static gboolean http_handle_headers( struct http_request *req );
     196
    195197static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
    196198{
    197199        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;
    201202        size_t content_length;
    202203        int st;
     
    218219                                   packets that abort connections! \o/ */
    219220                               
    220                                 goto got_reply;
     221                                goto eof;
    221222                        }
    222223                }
    223224                else if( st == 0 )
    224225                {
    225                         goto got_reply;
     226                        goto eof;
    226227                }
    227228        }
     
    239240                else if( st == 0 )
    240241                {
    241                         goto got_reply;
    242                 }
    243         }
    244        
    245         if( st > 0 )
     242                        goto eof;
     243                }
     244        }
     245       
     246        if( st > 0 && !req->sbuf )
    246247        {
    247248                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
    248249                memcpy( req->reply_headers + req->bytes_read, buffer, st );
    249250                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 );
    251295       
    252296        /* There will be more! */
     
    255299                                 http_incoming_data, req );
    256300       
    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
     303eof:
     304        req->flags |= HTTPC_EOF;
     305       
    263306        /* Maybe if the webserver is overloaded, or when there's bad SSL
    264307           support... */
     
    269312        }
    270313       
     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
     322cleanup:
     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! */
     351static gboolean http_handle_headers( struct http_request *req )
     352{
     353        char *end1, *end2;
     354        int evil_server = 0;
     355       
    271356        /* Zero termination is very convenient. */
    272         req->reply_headers[req->bytes_read] = 0;
     357        req->reply_headers[req->bytes_read] = '\0';
    273358       
    274359        /* Find the separation between headers and body, and keep stupid
     
    289374        {
    290375                req->status_string = g_strdup( "Malformed HTTP reply" );
    291                 goto cleanup;
     376                return TRUE;
    292377        }
    293378       
     
    306391        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
    307392        {
    308                 if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
     393                if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 )
    309394                {
    310395                        req->status_string = g_strdup( "Can't parse status code" );
     
    349434                {
    350435                        req->status_string = g_strdup( "Can't locate Location: header" );
    351                         goto cleanup;
     436                        return TRUE;
    352437                }
    353438               
     
    369454                        req->status_string = g_strdup( "Can't handle recursive redirects" );
    370455                       
    371                         goto cleanup;
     456                        return TRUE;
    372457                }
    373458                else
     
    380465                        s = strstr( loc, "\r\n" );
    381466                        if( s == NULL )
    382                                 goto cleanup;
     467                                return TRUE;
    383468                       
    384469                        url = g_new0( url_t, 1 );
     
    389474                                req->status_string = g_strdup( "Malformed redirect URL" );
    390475                                g_free( url );
    391                                 goto cleanup;
     476                                return TRUE;
    392477                        }
    393478                       
     
    401486                                req->status_string = g_strdup( "Error while rebuilding request string" );
    402487                                g_free( url );
    403                                 goto cleanup;
     488                                return TRUE;
    404489                        }
    405490                       
     
    467552                        req->status_string = g_strdup( "Connection problem during redirect" );
    468553                        g_free( new_request );
    469                         goto cleanup;
     554                        return TRUE;
    470555                }
    471556               
     
    480565        }
    481566       
    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
     570void 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
     588void 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       
    487596        if( req->ssl )
    488597                ssl_disconnect( req->ssl );
     
    490599                closesocket( req->fd );
    491600       
    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 );
    509601        http_free( req );
    510         return FALSE;
    511602}
    512603
     
    516607        g_free( req->reply_headers );
    517608        g_free( req->status_string );
     609        g_free( req->sbuf );
    518610        g_free( req );
    519611}
    520 
  • lib/http_client.h

    r92d3044 rcc6fdf8  
    2626/* http_client allows you to talk (asynchronously, again) to HTTP servers.
    2727   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.
    3230   
    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. */
    3934
    4035#include <glib.h>
     
    4237
    4338struct http_request;
     39
     40typedef 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;
    4447
    4548/* Your callback function should look like this: */
     
    5356        char *request;          /* The request to send to the server. */
    5457        int request_length;     /* Its size. */
    55         int status_code;        /* The numeric HTTP status code. (Or -1
     58        short status_code;      /* The numeric HTTP status code. (Or -1
    5659                                   if something really went wrong) */
    5760        char *status_string;    /* The error text. */
     
    5962        char *reply_body;
    6063        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
    6465                                   http_client to follow them. */
     66       
     67        http_client_flags_t flags;
    6568       
    6669        http_input_function func;
     
    6871       
    6972        /* Please don't touch the things down here, you shouldn't need them. */
    70        
    7173        void *ssl;
    7274        int fd;
     
    7577        int bytes_written;
    7678        int bytes_read;
     79       
     80        /* Used in streaming mode. Caller should read from reply_body. */
     81        char *sbuf;
     82        size_t sblen;
    7783};
    7884
     
    8389struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
    8490struct 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. */
     93void http_flush_bytes( struct http_request *req, size_t len );
     94void http_close( struct http_request *req );
  • lib/oauth2.c

    r92d3044 rcc6fdf8  
    44*  Simple OAuth client (consumer) implementation.                           *
    55*                                                                           *
    6 *  Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net>              *
     6*  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
    77*                                                                           *
    88*  This program is free software; you can redistribute it and/or modify     *
     
    2626#include "oauth2.h"
    2727#include "oauth.h"
     28#include "json.h"
    2829#include "url.h"
    2930
     
    4445};
    4546
    46 static char *oauth2_json_dumb_get( const char *json, const char *key );
    4747static void oauth2_access_token_done( struct http_request *req );
    4848
     
    115115        else if( content_type && strstr( content_type, "application/json" ) )
    116116        {
    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 );
    119133        }
    120134        else
     
    137151        g_free( cb_data );
    138152}
    139 
    140 /* Super dumb. I absolutely refuse to use/add a complete json parser library
    141    (adding a new dependency to BitlBee for the first time in.. 6 years?) just
    142    to parse 100 bytes of data. So I have to do my own parsing because OAuth2
    143    dropped support for XML. (GRRR!) This is very dumb and for example won't
    144    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  
    3838
    3939static gboolean initialized = FALSE;
    40 gnutls_certificate_credentials xcred;
     40gnutls_certificate_credentials_t xcred;
    4141
    4242#include <limits.h>
     
    6060        gboolean verify;
    6161       
    62         gnutls_session session;
     62        gnutls_session_t session;
    6363};
    6464
     
    134134        conn->data = data;
    135135        conn->inpa = -1;
    136         conn->hostname = hostname;
     136        conn->hostname = g_strdup( hostname );
    137137       
    138138        /* For now, SSL verification is globally enabled by setting the cafile
     
    171171        int verifyret = 0;
    172172        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 );
    176176
    177177        gnutlsret = gnutls_certificate_verify_peers2( session, &status );
     
    211211                return VERIFY_CERT_ERROR;
    212212
    213         if( !gnutls_x509_crt_check_hostname( cert, hostname ) )
     213        if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )
    214214        {
    215215                verifyret |= VERIFY_CERT_INVALID;
     
    267267       
    268268        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 );
    271270#if GNUTLS_VERSION_NUMBER < 0x020c00
    272271        gnutls_transport_set_lowat( conn->session, 0 );
     
    276275       
    277276        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 );
    279278       
    280279        return ssl_handshake( data, source, cond );
     
    402401        if( conn->session )
    403402                gnutls_deinit( conn->session );
     403        g_free( conn->hostname );
    404404        g_free( conn );
    405405}
  • nick.c

    r92d3044 rcc6fdf8  
    134134                                chop = fmt[1];
    135135                                if( chop == '\0' )
     136                                {
     137                                        g_string_free( ret, TRUE );
    136138                                        return NULL;
     139                                }
    137140                                fmt += 2;
    138141                        }
     
    187190                        else
    188191                        {
     192                                g_string_free( ret, TRUE );
    189193                                return NULL;
    190194                        }
  • protocols/msn/msn.c

    r92d3044 rcc6fdf8  
    5353       
    5454        ic->proto_data = md;
     55        ic->flags |= OPT_PONGS | OPT_PONGED;
    5556       
    5657        if( strchr( acc->user, '@' ) == NULL )
  • protocols/msn/ns.c

    r92d3044 rcc6fdf8  
    576576                if( num_parts >= 7 )
    577577                        handler->msglen = atoi( cmd[6] );
     578        }
     579        else if( strcmp( cmd[0], "QNG" ) == 0 )
     580        {
     581                ic->flags |= OPT_PONGED;
    578582        }
    579583        else if( isdigit( cmd[0][0] ) )
  • protocols/nogaim.c

    r92d3044 rcc6fdf8  
    261261        struct im_connection *ic = d;
    262262       
     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       
    263273        if( ic->acc->prpl->keepalive )
    264274                ic->acc->prpl->keepalive( ic );
    265275       
    266276        return TRUE;
     277}
     278
     279void 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;
    267287}
    268288
     
    277297        imcb_log( ic, "Logged in" );
    278298       
    279         b_event_remove( ic->keepalive );
    280         ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );
    281299        ic->flags |= OPT_LOGGED_IN;
     300        start_keepalives( ic, 60000 );
    282301       
    283302        /* Necessary to send initial presence status, even if we're not away. */
  • protocols/nogaim.h

    r92d3044 rcc6fdf8  
    6868#define OPT_THINKING    0x00000200 /* about these values... Stupid me! */
    6969#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 */
    7072
    7173/* ok. now the fun begins. first we create a connection structure */
  • protocols/twitter/twitter.c

    r92d3044 rcc6fdf8  
    44*  Simple module to facilitate twitter functionality.                       *
    55*                                                                           *
    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>              *
    78*                                                                           *
    89*  This library is free software; you can redistribute it and/or            *
     
    2930#include "url.h"
    3031
    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 
    4032GSList *twitter_connections = NULL;
    41 
    4233/**
    4334 * Main loop function
     
    6253        struct twitter_data *td = ic->proto_data;
    6354
     55        /* Create the room now that we "logged in". */
     56        if (td->flags & TWITTER_MODE_CHAT)
     57                twitter_groupchat_init(ic);
     58
    6459        imcb_log(ic, "Getting initial statuses");
    6560
    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).
    6763        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
     82struct 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;
    73106}
    74107
     
    83116        if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
    84117                twitter_oauth_start(ic);
    85         else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 &&
    86                 !(td->flags & TWITTER_HAVE_FRIENDS)) {
     118        else if (!(td->flags & TWITTER_MODE_ONE) &&
     119                !(td->flags & TWITTER_HAVE_FRIENDS)) {
    87120                imcb_log(ic, "Getting contact list");
    88121                twitter_get_friends_ids(ic, -1);
    89                 //twitter_get_statuses_friends(ic, -1);
    90122        } else
    91123                twitter_main_loop_start(ic);
     
    187219}
    188220
    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         else
    196                 return NULL;
    197 }
    198 
    199221int twitter_url_len_diff(gchar *msg, unsigned int target_len)
    200222{
     
    233255                return TRUE;
    234256
    235         imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max);
     257        twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max);
    236258
    237259        return FALSE;
     260}
     261
     262static 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
     270static 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;
    238277}
    239278
     
    257296        s->flags |= ACC_SET_OFFLINE_ONLY;
    258297
    259         s = set_add(&acc->set, "commands", "true", set_eval_bool, acc);
     298        s = set_add(&acc->set, "commands", "true", set_eval_commands, acc);
    260299
    261300        s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc);
     
    274313
    275314        s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc);
    276         s->flags |= ACC_SET_OFFLINE_ONLY;
    277315
    278316        s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc);
    279317
    280318        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        }
    281324}
    282325
    283326/**
    284  * Login method. Since the twitter API works with seperate HTTP request we
     327 * Login method. Since the twitter API works with separate HTTP request we
    285328 * only save the user and pass to the twitter_data object.
    286329 */
     
    298341                imc_logout(ic, FALSE);
    299342                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.");
    300349        }
    301350
     
    340389        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    341390
    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;
    344401
    345402        twitter_login_finish(ic);
     
    363420
    364421        if (td) {
     422                http_close(td->stream);
    365423                oauth_info_free(td->oauth_info);
    366424                g_free(td->user);
     
    495553 *  Returns 0 if the user provides garbage.
    496554 */
    497 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) {
     555static 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;
    498557        struct twitter_user_data *tud;
    499         bee_user_t *bu;
     558        bee_user_t *bu = NULL;
    500559        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;
    504575                        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;
    512589        return id;
    513590}
     
    517594        struct twitter_data *td = ic->proto_data;
    518595        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;
    520600
    521601        cmds = g_strdup(message);
     
    523603
    524604        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. */
    529608        } else if (g_strcasecmp(cmd[0], "undo") == 0) {
    530                 guint64 id;
    531 
    532609                if (cmd[1] == NULL)
    533610                        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)))
    538612                        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;
    544617        } 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))) {
    547619                        twitter_favourite_tweet(ic, id);
    548620                } else {
    549                         twitter_msg(ic, "Please provide a message ID or username.");
     621                        twitter_log(ic, "Please provide a message ID or username.");
    550622                }
    551                 g_free(cmds);
    552                 return;
     623                goto eof;
    553624        } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {
    554625                twitter_add_buddy(ic, cmd[1], NULL);
    555                 g_free(cmds);
    556                 return;
     626                goto eof;
    557627        } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {
    558628                twitter_remove_buddy(ic, cmd[1], NULL);
    559                 g_free(cmds);
    560                 return;
     629                goto eof;
    561630        } else if ((g_strcasecmp(cmd[0], "report") == 0 ||
    562631                    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               
    566634                /* Report nominally works on users but look up the user who
    567635                   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               
    576642                twitter_report_spam(ic, screen_name);
    577                 g_free(cmds);
    578                 return;
     643                goto eof;
    579644        } 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);
    581646
    582647                td->last_status_id = 0;
     
    584649                        twitter_status_retweet(ic, id);
    585650                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 "
    587652                                    "post any statuses recently", cmd[1]);
    588653
    589                 g_free(cmds);
    590                 return;
     654                goto eof;
    591655        } 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);
    616657                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 "
    618659                                    "post any statuses recently", cmd[1]);
    619                         g_free(cmds);
    620                         return;
     660                        goto eof;
    621661                }
    622662                message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));
    623663                in_reply_to = id;
     664                allow_post = TRUE;
    624665        } else if (g_strcasecmp(cmd[0], "post") == 0) {
    625666                message += 5;
    626         }
    627 
    628         {
     667                allow_post = TRUE;
     668        }
     669
     670        if (allow_post) {
    629671                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;
    637675
    638676                s = cmd[0] + strlen(cmd[0]) - 1;
     
    657695                td->last_status_id = 0;
    658696                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        }
     700eof:
     701        g_free(new);
    661702        g_free(cmds);
    662703}
     704
     705void 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
    663723
    664724void twitter_initmodule()
  • protocols/twitter/twitter.h

    r92d3044 rcc6fdf8  
    44*  Simple module to facilitate twitter functionality.                       *
    55*                                                                           *
    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>              *
    78*                                                                           *
    89*  This library is free software; you can redistribute it and/or            *
     
    3536typedef enum
    3637{
    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,
    3842        TWITTER_DOING_TIMELINE = 0x10000,
    39         TWITTER_GOT_TIMELINE = 0x20000,
    40         TWITTER_GOT_MENTIONS = 0x40000,
     43        TWITTER_GOT_TIMELINE   = 0x20000,
     44        TWITTER_GOT_MENTIONS   = 0x40000,
    4145} twitter_flags_t;
    4246
     
    5761        guint64 last_status_id; /* For undo */
    5862        gint main_loop_id;
     63        struct http_request *stream;
    5964        struct groupchat *timeline_gc;
    6065        gint http_fails;
     
    8085};
    8186
    82 #define TWITTER_LOG_LENGTH 100
     87#define TWITTER_LOG_LENGTH 256
    8388struct twitter_log_data
    8489{
     
    99104char *twitter_parse_error( struct http_request *req );
    100105
     106void twitter_log(struct im_connection *ic, char *format, ... );
     107struct groupchat *twitter_groupchat_init(struct im_connection *ic);
     108
    101109#endif //_TWITTER_H
  • protocols/twitter/twitter_http.c

    r92d3044 rcc6fdf8  
    4747 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
    4848 */
    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)
     49struct 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)
    5151{
    5252        struct twitter_data *td = ic->proto_data;
     
    5555        void *ret;
    5656        char *url_arguments;
     57        url_t *base_url = NULL;
    5758
    5859        url_arguments = g_strdup("");
     
    6768                }
    6869        }
     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       
    6979        // Make the request.
    7080        g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
     
    7282                        "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
    7383                        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);
    7688
    7789        // If a pass and user are given we append them to the request.
     
    8092                char *full_url;
    8193
    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);
    8398                full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",
    8499                                                full_url, url_arguments);
     
    109124        }
    110125
    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);
    112130
    113131        g_free(url_arguments);
    114132        g_string_free(request, TRUE);
     133        g_free(base_url);
     134        return ret;
     135}
     136
     137struct 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;
    115143        return ret;
    116144}
  • protocols/twitter/twitter_http.h

    r92d3044 rcc6fdf8  
    2828#include "http_client.h"
    2929
     30typedef 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
    3036struct oauth_info;
    3137
    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);
     38struct 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);
     40struct 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);
    3442
    3543#endif //_TWITTER_HTTP_H
  • protocols/twitter/twitter_lib.c

    r92d3044 rcc6fdf8  
    3535#include "misc.h"
    3636#include "base64.h"
    37 #include "xmltree.h"
    3837#include "twitter_lib.h"
     38#include "json_util.h"
    3939#include <ctype.h>
    4040#include <errno.h>
     
    6767        char *text;
    6868        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;
    7071};
    71 
    72 static void twitter_groupchat_init(struct im_connection *ic);
    7372
    7473/**
     
    148147        // Check if the buddy is already in the buddy list.
    149148        if (!bee_user_by_handle(ic->bee, ic, name)) {
    150                 char *mode = set_getstr(&ic->acc->set, "mode");
    151 
    152149                // The buddy is not in the list, add the buddy and set the status to logged in.
    153150                imcb_add_buddy(ic, name, NULL);
    154151                imcb_rename_buddy(ic, name, fullname);
    155                 if (g_strcasecmp(mode, "chat") == 0) {
     152                if (td->flags & TWITTER_MODE_CHAT) {
    156153                        /* Necessary so that nicks always get translated to the
    157154                           exact Twitter username. */
    158155                        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)
    161159                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    162160        }
     
    168166{
    169167        static char *ret = NULL;
    170         struct xt_node *root, *node, *err;
     168        json_value *root, *err;
    171169
    172170        g_free(ret);
     
    174172
    175173        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);
    185183        }
    186184
     
    188186}
    189187
    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! */
     190static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
    191191{
    192192        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
    193193        gboolean periodic;
    194194        struct twitter_data *td = ic->proto_data;
    195         struct xt_node *ret;
     195        json_value *ret;
    196196        char path[64] = "", *s;
    197197       
     
    211211                   throwing 401s so I'll keep treating this one as fatal
    212212                   only during login. */
    213                 imcb_error(ic, "Authentication failure");
     213                imcb_error(ic, "Authentication failure (%s)",
     214                               twitter_parse_error(req));
    214215                imc_logout(ic, FALSE);
    215216                return NULL;
     
    217218                // It didn't go well, output the error and return.
    218219                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));
    221222               
    222223                if (logging_in)
     
    227228        }
    228229
    229         if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {
     230        if ((ret = json_parse(req->reply_body)) == NULL) {
    230231                imcb_error(ic, "Could not retrieve %s: %s",
    231232                           path, "XML parse error");
     
    251252
    252253/**
    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 /**
    268254 * Fill a list of ids.
    269255 */
    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;
     256static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
     257{
     258        json_value *c;
     259        int i;
    273260
    274261        // Set the list type.
    275262        txl->type = TXL_ID;
    276263
    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;
    292283}
    293284
     
    300291{
    301292        struct im_connection *ic;
    302         struct xt_node *parsed;
     293        json_value *parsed;
    303294        struct twitter_xml_list *txl;
    304295        struct twitter_data *td;
     
    312303        td = ic->proto_data;
    313304
    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 
    318305        txl = g_new0(struct twitter_xml_list, 1);
    319306        txl->list = td->follow_ids;
     
    322309        if (!(parsed = twitter_parse_response(ic, req)))
    323310                return;
     311       
    324312        twitter_xt_get_friends_id_list(parsed, txl);
    325         xt_free_node(parsed);
     313        json_value_free(parsed);
    326314
    327315        td->follow_ids = txl->list;
     
    338326}
    339327
    340 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
     328static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
    341329static void twitter_http_get_users_lookup(struct http_request *req);
    342330
     
    379367{
    380368        struct im_connection *ic = req->data;
    381         struct xt_node *parsed;
     369        json_value *parsed;
    382370        struct twitter_xml_list *txl;
    383371        GSList *l = NULL;
     
    395383                return;
    396384        twitter_xt_get_users(parsed, txl);
    397         xt_free_node(parsed);
     385        json_value_free(parsed);
    398386
    399387        // Add the users as buddies.
     
    409397}
    410398
    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;
     399struct 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;
    430408}
    431409
     
    435413 *  - all <user>s from the <users> element.
    436414 */
    437 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
     415static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
    438416{
    439417        struct twitter_xml_user *txu;
    440         struct xt_node *child;
     418        int i;
    441419
    442420        // Set the type of the list.
    443421        txl->type = TXL_USER;
    444422
     423        if (!node || node->type != json_array)
     424                return FALSE;
     425
    445426        // The root <users> node should hold the list of users <user>
    446427        // 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)
    452431                        txl->list = g_slist_prepend(txl->list, txu);
    453                 }
    454         }
    455 
    456         return XT_HANDLED;
     432        }
     433
     434        return TRUE;
    457435}
    458436
     
    462440#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
    463441#endif
     442
     443static char* expand_entities(char* text, const json_value *entities);
    464444
    465445/**
     
    471451 *  - the user in a twitter_xml_user struct.
    472452 */
    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) {
     453static 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) {
    484469                        struct tm parsed;
    485470
     
    487472                           this field. :-( Also assumes the timezone used
    488473                           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)
    490475                                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;
    498484                }
    499485        }
     
    502488           wasn't truncated because it may be lying. */
    503489        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;
    506495                        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 */
     511static 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
     550static char* expand_entities(char* text, const json_value *entities) {
     551        JSON_O_FOREACH (entities, k, v) {
     552                int i;
    515553               
    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)
    521561                                continue;
    522562                       
    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 &lt;%s&gt;%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;
    544580}
    545581
     
    550586 *  - the next_cursor.
    551587 */
    552 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
    553                                             struct twitter_xml_list *txl)
     588static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
     589                                           struct twitter_xml_list *txl)
    554590{
    555591        struct twitter_xml_status *txs;
    556         struct xt_node *child;
    557         bee_user_t *bu;
     592        int i;
    558593
    559594        // Set the type of the list.
    560595        txl->type = TXL_STATUS;
     596       
     597        if (node->type != json_array)
     598                return FALSE;
    561599
    562600        // The root <statuses> node should hold the list of statuses <status>
    563601        // 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. */
    588615static char *twitter_msg_add_id(struct im_connection *ic,
    589616                                struct twitter_xml_status *txs, const char *prefix)
    590617{
    591618        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 {
    595659                if (*prefix)
    596660                        return g_strconcat(prefix, txs->text, NULL);
     
    598662                        return NULL;
    599663        }
    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 */
     669static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
     670{
     671        struct twitter_data *td = ic->proto_data;
    622672        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;
    649675
    650676        // 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);
    690693}
    691694
     
    693696 * Function that is called to see statuses as private messages.
    694697 */
    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) {
     698static 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) {
    707706                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
    708707                from[MAX_STRING - 1] = '\0';
    709708        }
    710709
    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
     728static 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
     750static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
     751
     752static 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
     805static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
     806static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
     807
     808static 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
     842static 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
     871static 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
     896gboolean 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
     912static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
     913static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    744914
    745915/**
     
    778948        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
    779949        struct twitter_xml_list *mentions = td->mentions_obj;
     950        guint64 last_id = 0;
    780951        GSList *output = NULL;
    781952        GSList *l;
    782953
     954        imcb_connected(ic);
     955       
    783956        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
    784957                return;
     
    804977                }
    805978        }
    806        
    807         if (!(ic->flags & OPT_LOGGED_IN))
    808                 imcb_connected(ic);
    809979
    810980        // 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        }
    817988
    818989        txl_free(home_timeline);
     
    823994}
    824995
     996static void twitter_http_get_home_timeline(struct http_request *req);
     997static void twitter_http_get_mentions(struct http_request *req);
     998
    825999/**
    8261000 * Get the timeline.
    8271001 */
    828 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     1002static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    8291003{
    8301004        struct twitter_data *td = ic->proto_data;
     
    8621036 * Get mentions.
    8631037 */
    864 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     1038static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
    8651039{
    8661040        struct twitter_data *td = ic->proto_data;
     
    8931067
    8941068        g_free(args[1]);
    895         if (td->timeline_id) {
    896                 g_free(args[5]);
    897         }
     1069        g_free(args[5]);
    8981070}
    8991071
     
    9051077        struct im_connection *ic = req->data;
    9061078        struct twitter_data *td;
    907         struct xt_node *parsed;
     1079        json_value *parsed;
    9081080        struct twitter_xml_list *txl;
    9091081
     
    9211093                goto end;
    9221094        twitter_xt_get_status_list(ic, parsed, txl);
    923         xt_free_node(parsed);
     1095        json_value_free(parsed);
    9241096
    9251097        td->home_timeline_obj = txl;
    9261098
    9271099      end:
     1100        if (!g_slist_find(twitter_connections, ic))
     1101                return;
     1102
    9281103        td->flags |= TWITTER_GOT_TIMELINE;
    9291104
     
    9381113        struct im_connection *ic = req->data;
    9391114        struct twitter_data *td;
    940         struct xt_node *parsed;
     1115        json_value *parsed;
    9411116        struct twitter_xml_list *txl;
    9421117
     
    9541129                goto end;
    9551130        twitter_xt_get_status_list(ic, parsed, txl);
    956         xt_free_node(parsed);
     1131        json_value_free(parsed);
    9571132
    9581133        td->mentions_obj = txl;
    9591134
    9601135      end:
     1136        if (!g_slist_find(twitter_connections, ic))
     1137                return;
     1138
    9611139        td->flags |= TWITTER_GOT_MENTIONS;
    9621140
     
    9721150        struct im_connection *ic = req->data;
    9731151        struct twitter_data *td;
    974         struct xt_node *parsed, *node;
     1152        json_value *parsed, *id;
    9751153
    9761154        // Check if the connection is still active.
     
    9841162                return;
    9851163       
    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");
    9891172}
    9901173
     
    10321215        char *url;
    10331216        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);
    10361220        g_free(url);
    10371221}
     
    10411225        char *url;
    10421226        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);
    10451230        g_free(url);
    10461231}
     
    10561241        };
    10571242        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);
    10601245}
    10611246
     
    10671252        char *url;
    10681253        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);
    10711257        g_free(url);
    10721258}
  • protocols/twitter/twitter_lib.h

    r92d3044 rcc6fdf8  
    2929#include "twitter_http.h"
    3030
    31 #define TWITTER_API_URL "http://api.twitter.com/1"
     31#define TWITTER_API_URL "http://api.twitter.com/1.1"
    3232#define IDENTICA_API_URL "https://identi.ca/api"
    3333
    3434/* Status URLs */
    35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
     35#define TWITTER_STATUS_UPDATE_URL "/statuses/update.json"
    3636#define TWITTER_STATUS_SHOW_URL "/statuses/show/"
    3737#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"
     
    3939
    4040/* 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"
    4747
    4848/* Users URLs */
    49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml"
     49#define TWITTER_USERS_LOOKUP_URL "/users/lookup.json"
    5050
    5151/* 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"
    5555#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"
    5656
    5757/* 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"
    6161
    6262/* 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"
    6565
    6666/* 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"
    6868
    6969/* Favorites URLs */
    70 #define TWITTER_FAVORITES_GET_URL "/favorites.xml"
     70#define TWITTER_FAVORITES_GET_URL "/favorites.json"
    7171#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"
    7272#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/"
     
    7777
    7878/* Report spam */
    79 #define TWITTER_REPORT_SPAM_URL "/report_spam.xml"
     79#define TWITTER_REPORT_SPAM_URL "/report_spam.json"
    8080
     81#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"
     82
     83gboolean twitter_open_stream(struct im_connection *ic);
    8184void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
    8285void 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);
    8586void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
    8687
Note: See TracChangeset for help on using the changeset viewer.