├── .github └── workflows │ ├── cross.yml │ └── linux.yml ├── LICENSE ├── Makefile ├── README.md ├── convey.yml ├── glibcompat.h ├── libicyque.c ├── purple2compat ├── ciphers │ └── sha1hash.h ├── circularbuffer.h ├── glibcompat.h ├── http.c ├── http.h ├── image-store.h ├── image.h ├── internal.h ├── plugins.h ├── purple-socket.c └── purple-socket.h └── purplecompat.h /.github/workflows/cross.yml: -------------------------------------------------------------------------------- 1 | name: Cross compile for Windows 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | build: 7 | name: build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Set up MinGW 13 | run: | 14 | sudo apt update 15 | sudo apt install -y gcc-mingw-w64-i686-win32 16 | 17 | - name: Load Pidgin from cache 18 | id: pidgin-cache 19 | uses: actions/cache@v4 20 | with: 21 | path: pidgin 22 | key: pidgin-2.14.1 23 | 24 | - name: Download Pidgin 25 | if: steps.pidgin-cache.outputs.cache-hit != 'true' 26 | run: | 27 | curl --http1.1 -L https://sourceforge.net/projects/pidgin/files/Pidgin/2.14.1/pidgin-2.14.1.tar.bz2/download -o pidgin.tar.bz2 28 | tar -xf pidgin.tar.bz2 29 | mv pidgin-2.14.1 pidgin 30 | curl --http1.1 -L https://sourceforge.net/projects/pidgin/files/Pidgin/2.14.1/pidgin-2.14.1-win32-bin.zip/download -o pidgin-win32bin.zip 31 | unzip pidgin-win32bin.zip 32 | cp pidgin-2.14.1-win32bin/libpurple.dll pidgin/libpurple/ 33 | 34 | - name: Load Win32 deps from cache 35 | id: win32-cache 36 | uses: actions/cache@v4 37 | with: 38 | path: win32-dev 39 | key: win32-dev 40 | 41 | - name: Setup WinPidgin build 42 | if: steps.win32-cache.outputs.cache-hit != 'true' 43 | run: | 44 | mkdir win32-dev 45 | curl --http1.1 -L https://download.gnome.org/binaries/win32/glib/2.28/glib-dev_2.28.8-1_win32.zip -o glib-dev.zip 46 | unzip glib-dev.zip -d win32-dev/glib-2.28.8 47 | curl --http1.1 -L https://download.gnome.org/binaries/win32/dependencies/gettext-runtime-dev_0.18.1.1-2_win32.zip -o gettext-runtime.zip 48 | unzip gettext-runtime.zip -d win32-dev/glib-2.28.8 49 | curl --http1.1 -L https://download.gnome.org/binaries/win32/dependencies/zlib-dev_1.2.5-2_win32.zip -o zlib-dev.zip 50 | unzip zlib-dev.zip -d win32-dev/glib-2.28.8 51 | 52 | cd win32-dev 53 | curl --http1.1 -L https://github.com/jgeboski/purple-facebook/releases/download/downloads/json-glib-0.14.tar.gz -o json-glib.tar.gz 54 | tar -xf json-glib.tar.gz 55 | rm json-glib.tar.gz 56 | cd .. 57 | 58 | ls win32-dev/ 59 | 60 | - name: make 61 | run: | 62 | export WIN32_CC=i686-w64-mingw32-gcc 63 | export WIN32_DEV_TOP=win32-dev 64 | export PIDGIN_TREE_TOP=pidgin 65 | make libicyque.dll 66 | 67 | - name: archive 68 | if: ${{ !env.ACT }} 69 | uses: actions/upload-artifact@v4 70 | with: 71 | name: plugin 72 | path: lib*.dll 73 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | build: 7 | name: build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: install deps 13 | run: | 14 | sudo apt update 15 | sudo apt install -y libglib2.0-dev libjson-glib-dev gettext 16 | # We only need these to build, not to run. Skip the dependency check. 17 | sudo apt download libpurple0 libpurple-dev 18 | sudo dpkg --force-depends -i libpurple0*.deb libpurple-dev*.deb 19 | - name: make 20 | run: make 21 | 22 | - name: archive 23 | if: ${{ !env.ACT }} 24 | uses: actions/upload-artifact@v4 25 | with: 26 | name: plugin 27 | path: lib*.so 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PIDGIN_TREE_TOP ?= ../pidgin-2.10.11 3 | PIDGIN3_TREE_TOP ?= ../pidgin-main 4 | LIBPURPLE_DIR ?= $(PIDGIN_TREE_TOP)/libpurple 5 | WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev 6 | 7 | WIN32_CC ?= $(WIN32_DEV_TOP)/mingw-4.7.2/bin/gcc 8 | MAKENSIS ?= makensis 9 | 10 | PKG_CONFIG ?= pkg-config 11 | 12 | CFLAGS ?= -O2 -g -pipe 13 | LDFLAGS ?= 14 | 15 | # Do some nasty OS and purple version detection 16 | ifeq ($(OS),Windows_NT) 17 | #only defined on 64-bit windows 18 | PROGFILES32 = ${ProgramFiles(x86)} 19 | ifndef PROGFILES32 20 | PROGFILES32 = $(PROGRAMFILES) 21 | endif 22 | ICYQUE_TARGET = libicyque.dll 23 | ICYQUE_DEST = "$(PROGFILES32)/Pidgin/plugins" 24 | ICYQUE_ICONS_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/protocols" 25 | MAKENSIS = "$(PROGFILES32)/NSIS/makensis.exe" 26 | else 27 | 28 | UNAME_S := $(shell uname -s) 29 | 30 | #.. There are special flags we need for OSX 31 | ifeq ($(UNAME_S), Darwin) 32 | # 33 | #.. /opt/local/include and subdirs are included here to ensure this compiles 34 | # for folks using Macports. I believe Homebrew uses /usr/local/include 35 | # so things should "just work". You *must* make sure your packages are 36 | # all up to date or you will most likely get compilation errors. 37 | # 38 | INCLUDES = -I/opt/local/include -lz $(OS) 39 | 40 | CC = gcc 41 | else 42 | INCLUDES = 43 | CC ?= gcc 44 | endif 45 | 46 | ifeq ($(shell $(PKG_CONFIG) --exists purple-3 2>/dev/null && echo "true"),) 47 | ifeq ($(shell $(PKG_CONFIG) --exists purple 2>/dev/null && echo "true"),) 48 | ICYQUE_TARGET = FAILNOPURPLE 49 | ICYQUE_DEST = 50 | ICYQUE_ICONS_DEST = 51 | else 52 | ICYQUE_TARGET = libicyque.so 53 | ICYQUE_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple` 54 | ICYQUE_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/protocols 55 | endif 56 | else 57 | ICYQUE_TARGET = libicyque3.so 58 | ICYQUE_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple-3` 59 | ICYQUE_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/protocols 60 | endif 61 | endif 62 | 63 | WIN32_CFLAGS = -I$(WIN32_DEV_TOP)/glib-2.28.8/include -I$(WIN32_DEV_TOP)/glib-2.28.8/include/glib-2.0 -I$(WIN32_DEV_TOP)/glib-2.28.8/lib/glib-2.0/include -I$(WIN32_DEV_TOP)/json-glib-0.14/include/json-glib-1.0 -DENABLE_NLS -DPACKAGE_VERSION='"$(PLUGIN_VERSION)"' -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-unused-parameter -fno-strict-aliasing -Wformat 64 | WIN32_LDFLAGS = -L$(WIN32_DEV_TOP)/glib-2.28.8/lib -L$(WIN32_DEV_TOP)/json-glib-0.14/lib -lpurple -lintl -lglib-2.0 -lgobject-2.0 -ljson-glib-1.0 -g -ggdb -static-libgcc -lz 65 | WIN32_PIDGIN2_CFLAGS = -I$(PIDGIN_TREE_TOP)/libpurple -I$(PIDGIN_TREE_TOP) $(WIN32_CFLAGS) 66 | WIN32_PIDGIN3_CFLAGS = -I$(PIDGIN3_TREE_TOP)/libpurple -I$(PIDGIN3_TREE_TOP) -I$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_CFLAGS) 67 | WIN32_PIDGIN2_LDFLAGS = -L$(PIDGIN_TREE_TOP)/libpurple $(WIN32_LDFLAGS) 68 | WIN32_PIDGIN3_LDFLAGS = -L$(PIDGIN3_TREE_TOP)/libpurple -L$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_LDFLAGS) -lgplugin 69 | 70 | C_FILES := libicyque.c 71 | PURPLE_COMPAT_FILES := purple2compat/http.c purple2compat/purple-socket.c 72 | PURPLE_C_FILES := libicyque.c $(C_FILES) 73 | TEST_C_FILES := icyque_test.c $(C_FILES) 74 | 75 | 76 | 77 | .PHONY: all install FAILNOPURPLE clean 78 | 79 | all: $(ICYQUE_TARGET) 80 | 81 | libicyque.so: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES) 82 | $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) `$(PKG_CONFIG) purple glib-2.0 json-glib-1.0 zlib --libs --cflags` -ldl $(INCLUDES) -Ipurple2compat -g -ggdb 83 | 84 | libicyque3.so: $(PURPLE_C_FILES) 85 | $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) `$(PKG_CONFIG) purple-3 glib-2.0 json-glib-1.0 zlib --libs --cflags` -ldl $(INCLUDES) -g -ggdb 86 | 87 | libicyque.dll: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES) 88 | $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat 89 | 90 | libicyque3.dll: $(PURPLE_C_FILES) 91 | $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN3_CFLAGS) $(WIN32_PIDGIN3_LDFLAGS) 92 | 93 | icyque-test.exe: $(TEST_C_FILES) $(PURPLE_COMPAT_FILES) 94 | $(WIN32_CC) -o $@ -DDEBUG $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat 95 | 96 | install: $(ICYQUE_TARGET) 97 | mkdir -p $(ICYQUE_DEST) 98 | install -p $(ICYQUE_TARGET) $(ICYQUE_DEST) 99 | 100 | FAILNOPURPLE: 101 | echo "You need libpurple development headers installed to be able to compile this plugin" 102 | 103 | clean: 104 | rm -f $(ICYQUE_TARGET) 105 | 106 | 107 | installer: purple-icyque.nsi libicyque.dll 108 | $(MAKENSIS) purple-icyque.nsi 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # icyque 2 | ICQ WIM protocol for libpurple 3 | 4 | # Installation # 5 | ## Linux install ## 6 | Download latest builds from https://bamboo.pidgin.im/browse/EIONROBB-ICYQUE/latestSuccessful/artifact/shared/builds/ and copy into ~/.purple/plugins/ directory. 7 | 8 | ### Manual Compiling ### 9 | Requires devel headers/libs for libpurple and libjson-glib [libglib2.0-dev, libjson-glib-dev and libpurple-dev] 10 | ```bash 11 | git clone git://github.com/EionRobb/icyque.git 12 | cd icyque 13 | make 14 | sudo make install 15 | ``` 16 | 17 | ## Windows install ## 18 | Download nightly builds of [libicyque.dll](https://eion.robbmob.com/libicyque.dll) and copy into your C:\Program Files (x86)\Pidgin\plugins\ folder 19 | If you haven't used one of my other plugins before, you'll need [libjson-glib-1.0.dll](https://eion.robbmob.com/libjson-glib-1.0.dll) in your C:\Program Files (x86)\Pidgin\ (not plugins!) folder 20 | 21 | # Setup # 22 | If you're switching to IcyQue from the built-in ICQ plugin, you'll need to restart Pidgin and then edit your existing account (or create a new account) with the "ICQ (WIM)" protocol: 23 | 24 | ![image](https://user-images.githubusercontent.com/1063865/55356131-95b07b00-5526-11e9-9fb8-27e0fc18ce74.png) 25 | 26 | If you only have a phone number, then enter that with a + at the beginning and leave the password field blank 27 | 28 | ![image](https://user-images.githubusercontent.com/1063865/58231424-15baca80-7d8b-11e9-9f85-e6d502ee3dfd.png) 29 | -------------------------------------------------------------------------------- /convey.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tasks: 3 | clean: 4 | type: convey/clean 5 | files: 6 | - libicyque-*.so 7 | 8 | import: 9 | type: docker/import 10 | files: 11 | - .:. 12 | 13 | export: 14 | type: docker/export 15 | files: 16 | - libicyque-*.so 17 | 18 | purple2: 19 | type: docker/run 20 | image: pidgin/pidgin2-dev:${TAG} 21 | workdir: /workspace 22 | script: 23 | - set -ex 24 | - make clean 25 | - rm -f libicyque-${TAG}.so 26 | - make libicyque.so 27 | - mv libicyque.so libicyque-${TAG}.so 28 | 29 | purple2-debian-stretch-amd64: 30 | type: convey/extend 31 | task: purple2 32 | environment: 33 | - TAG=debian-stretch-amd64 34 | purple2-debian-stretch-i386: 35 | type: convey/extend 36 | task: purple2 37 | environment: 38 | - TAG=debian-stretch-i386 39 | purple2-fedora-28-amd64: 40 | type: convey/extend 41 | task: purple2 42 | environment: 43 | - TAG=fedora-28-amd64 44 | 45 | plans: 46 | default: 47 | stages: 48 | - name: setup 49 | tasks: 50 | - import 51 | - name: builds 52 | tasks: 53 | - purple2-debian-stretch-amd64 54 | - purple2-fedora-28-amd64 55 | - purple2-debian-stretch-i386 56 | - name: export 57 | tasks: 58 | - export 59 | clean: 60 | stages: 61 | - name: clean 62 | tasks: 63 | - clean 64 | 65 | -------------------------------------------------------------------------------- /glibcompat.h: -------------------------------------------------------------------------------- 1 | #ifndef _GLIBCOMPAT_H_ 2 | #define _GLIBCOMPAT_H_ 3 | 4 | #if !GLIB_CHECK_VERSION(2, 32, 0) 5 | #define g_hash_table_contains(hash_table, key) g_hash_table_lookup_extended(hash_table, key, NULL, NULL) 6 | #endif /* 2.32.0 */ 7 | 8 | 9 | #if !GLIB_CHECK_VERSION(2, 28, 0) 10 | gint64 11 | g_get_real_time() 12 | { 13 | GTimeVal val; 14 | 15 | g_get_current_time (&val); 16 | 17 | return (((gint64) val.tv_sec) * 1000000) + val.tv_usec; 18 | } 19 | #endif /* 2.28.0 */ 20 | 21 | #endif /*_GLIBCOMPAT_H_*/ -------------------------------------------------------------------------------- /libicyque.c: -------------------------------------------------------------------------------- 1 | /** 2 | IcyQue - an ICQ replacement plugin for Pidgin 3 | Copyright (C) 2018-2019 Eion Robb 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include "purplecompat.h" 26 | 27 | #ifndef PURPLE_PLUGINS 28 | # define PURPLE_PLUGINS 29 | #endif 30 | 31 | #define ICQ_EVENTS "myInfo,presence,buddylist,typing,hiddenChat,hist,mchat,sentIM,imState,dataIM,offlineIM,userAddedToBuddyList,service,lifestream,apps,permitDeny,diff" // ,webrtcMsg 32 | #define ICQ_PRESENCE_FIELDS "aimId,displayId,friendly,friendlyName,state,userType,statusMsg,statusTime,lastseen,ssl,mute,abContactName,abPhoneNumber,abPhones,official,quiet,autoAddition,largeIconId,nick,userState" 33 | #define ICQ_ASSERT_CAPS "094613584C7F11D18222444553540000,0946135C4C7F11D18222444553540000,0946135b4c7f11d18222444553540000,0946135E4C7F11D18222444553540000,AABC2A1AF270424598B36993C6231952,1f99494e76cbc880215d6aeab8e42268" 34 | #define ICQ_API_SERVER "https://api.icq.net" 35 | #define ICQ_API14_SERVER "https://u.icq.net/api/v14/wim" 36 | #define ICQ_RAPI_SERVER "https://u.icq.net/rapi" 37 | #define ICQ_DEVID "ao1mAegmj4_7xQOy" 38 | 39 | #ifndef _ 40 | # define _(a) (a) 41 | # define N_(a) (a) 42 | #endif 43 | 44 | 45 | 46 | #include 47 | 48 | // Suppress overzealous json-glib 'critical errors' 49 | #define json_object_has_member(JSON_OBJECT, MEMBER) \ 50 | (JSON_OBJECT ? json_object_has_member(JSON_OBJECT, MEMBER) : FALSE) 51 | #define json_object_get_int_member(JSON_OBJECT, MEMBER) \ 52 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_int_member(JSON_OBJECT, MEMBER) : 0) 53 | #define json_object_get_string_member(JSON_OBJECT, MEMBER) \ 54 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_string_member(JSON_OBJECT, MEMBER) : NULL) 55 | #define json_object_get_array_member(JSON_OBJECT, MEMBER) \ 56 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_array_member(JSON_OBJECT, MEMBER) : NULL) 57 | #define json_object_get_object_member(JSON_OBJECT, MEMBER) \ 58 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_object_member(JSON_OBJECT, MEMBER) : NULL) 59 | #define json_object_get_boolean_member(JSON_OBJECT, MEMBER) \ 60 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_boolean_member(JSON_OBJECT, MEMBER) : FALSE) 61 | 62 | #define json_array_get_length(JSON_ARRAY) \ 63 | (JSON_ARRAY ? json_array_get_length(JSON_ARRAY) : 0) 64 | 65 | static gchar * 66 | json_object_to_string(JsonObject *obj) 67 | { 68 | JsonNode *node; 69 | gchar *str; 70 | JsonGenerator *generator; 71 | 72 | node = json_node_new(JSON_NODE_OBJECT); 73 | json_node_set_object(node, obj); 74 | 75 | // a json string ... 76 | generator = json_generator_new(); 77 | json_generator_set_root(generator, node); 78 | str = json_generator_to_data(generator, NULL); 79 | g_object_unref(generator); 80 | json_node_free(node); 81 | 82 | return str; 83 | } 84 | 85 | 86 | typedef struct { 87 | PurpleAccount *account; 88 | PurpleConnection *pc; 89 | 90 | GHashTable *cookie_table; 91 | GHashTable *user_ids; 92 | gchar *session_key; 93 | gchar *token; 94 | gchar *aimsid; 95 | 96 | GSList *http_conns; /**< PurpleHttpConnection to be cancelled on logout */ 97 | gchar *device_id; 98 | gint64 last_message_timestamp; 99 | GHashTable *sent_messages_hash; 100 | 101 | gchar *sms_trans_id; 102 | 103 | // ICQ API 104 | guint heartbeat_timeout; 105 | PurpleHttpKeepalivePool *keepalive_pool; 106 | gchar *last_fetchBaseURL; 107 | 108 | // RAPI (Robusto API) 109 | gint64 server_time_offset; 110 | gchar *robusto_token; 111 | gint64 robusto_client_id; 112 | guint64 robusto_request_id; 113 | } IcyQueAccount; 114 | 115 | 116 | typedef void (*IcyQueProxyCallbackFunc)(IcyQueAccount *ia, JsonObject *obj, gpointer user_data); 117 | 118 | typedef struct { 119 | IcyQueAccount *ia; 120 | IcyQueProxyCallbackFunc callback; 121 | gpointer user_data; 122 | } IcyQueProxyConnection; 123 | 124 | static int 125 | gc_hmac_sha256(const void *key, size_t keylen, const void *in, size_t inlen, void *resbuf) 126 | { 127 | #if PURPLE_VERSION_CHECK(3, 0, 0) 128 | GHmac *hmac; 129 | gsize digest_len = 32; 130 | 131 | hmac = g_hmac_new(G_CHECKSUM_SHA256, key, keylen); 132 | g_hmac_update(hmac, in, inlen); 133 | g_hmac_get_digest(hmac, resbuf, &digest_len); 134 | g_hmac_unref(hmac); 135 | 136 | #else 137 | PurpleCipherContext *hmac; 138 | 139 | hmac = purple_cipher_context_new_by_name("hmac", NULL); 140 | 141 | purple_cipher_context_set_option(hmac, "hash", "sha256"); 142 | purple_cipher_context_set_key_with_len(hmac, (guchar *)key, keylen); 143 | purple_cipher_context_append(hmac, (guchar *)in, inlen); 144 | purple_cipher_context_digest(hmac, 32, resbuf, NULL); 145 | purple_cipher_context_destroy(hmac); 146 | 147 | #endif 148 | 149 | return 1; 150 | } 151 | 152 | 153 | gchar * 154 | icq_generate_signature(const gchar *data, const gchar *session) 155 | { 156 | purple_debug_info("icyque", "Signature: {%s}, Session: {%s}\n", data, session); 157 | static guchar sig[33]; 158 | 159 | gc_hmac_sha256(session, strlen(session), data, strlen(data), sig); 160 | sig[32] = '\0'; 161 | 162 | return g_base64_encode(sig, 32); 163 | } 164 | 165 | gchar * 166 | icq_get_url_sign(IcyQueAccount *ia, gboolean is_post, const gchar *url, const gchar *data) 167 | { 168 | GString *hash_data = g_string_new(NULL); 169 | gchar *ret; 170 | 171 | g_string_append(hash_data, is_post ? "POST" : "GET"); 172 | g_string_append_c(hash_data, '&'); 173 | g_string_append(hash_data, purple_url_encode(url)); 174 | g_string_append_c(hash_data, '&'); 175 | g_string_append(hash_data, purple_url_encode(data)); 176 | 177 | ret = icq_generate_signature(hash_data->str, ia->session_key); 178 | g_string_free(hash_data, TRUE); 179 | 180 | return ret; 181 | } 182 | 183 | static JsonObject* 184 | icq_generate_robusto_request(IcyQueAccount *ia, const gchar* method, JsonObject* params) 185 | { 186 | JsonObject *robustoRequest = json_object_new(); 187 | 188 | json_object_set_string_member(robustoRequest, "authToken", ia->robusto_token); 189 | json_object_set_string_member(robustoRequest, "method", method); 190 | 191 | GString *request_id = g_string_new(NULL); 192 | g_string_append_printf(request_id, "%" G_GUINT64_FORMAT "-%" G_GINT64_FORMAT, ia->robusto_request_id++, time(NULL) - ia->server_time_offset); 193 | json_object_set_string_member(robustoRequest, "reqId", request_id->str); 194 | g_string_free(request_id, TRUE); 195 | 196 | if(ia->robusto_client_id >= 0) { 197 | json_object_set_int_member(robustoRequest, "clientId", ia->robusto_client_id); 198 | } 199 | 200 | json_object_set_object_member(robustoRequest, "params", params); 201 | return robustoRequest; 202 | } 203 | 204 | /*static gint64 205 | to_int(const gchar *id) 206 | { 207 | return id ? g_ascii_strtoll(id, NULL, 10) : 0; 208 | } 209 | 210 | static gchar * 211 | from_int(gint64 id) 212 | { 213 | return g_strdup_printf("%" G_GINT64_FORMAT, id); 214 | }*/ 215 | 216 | 217 | 218 | static void 219 | icq_update_cookies(IcyQueAccount *ia, const GList *cookie_headers) 220 | { 221 | const gchar *cookie_start; 222 | const gchar *cookie_end; 223 | gchar *cookie_name; 224 | gchar *cookie_value; 225 | const GList *cur; 226 | 227 | for (cur = cookie_headers; cur != NULL; cur = g_list_next(cur)) { 228 | cookie_start = cur->data; 229 | 230 | cookie_end = strchr(cookie_start, '='); 231 | 232 | if (cookie_end != NULL) { 233 | cookie_name = g_strndup(cookie_start, cookie_end - cookie_start); 234 | cookie_start = cookie_end + 1; 235 | cookie_end = strchr(cookie_start, ';'); 236 | 237 | if (cookie_end != NULL) { 238 | cookie_value = g_strndup(cookie_start, cookie_end - cookie_start); 239 | cookie_start = cookie_end; 240 | 241 | g_hash_table_replace(ia->cookie_table, cookie_name, cookie_value); 242 | } 243 | } 244 | } 245 | } 246 | 247 | static void 248 | icq_cookie_foreach_cb(gchar *cookie_name, gchar *cookie_value, GString *str) 249 | { 250 | g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); 251 | } 252 | 253 | static gchar * 254 | icq_cookies_to_string(IcyQueAccount *ia) 255 | { 256 | GString *str; 257 | 258 | str = g_string_new(NULL); 259 | 260 | g_hash_table_foreach(ia->cookie_table, (GHFunc) icq_cookie_foreach_cb, str); 261 | 262 | return g_string_free(str, FALSE); 263 | } 264 | 265 | static void 266 | icq_response_callback(PurpleHttpConnection *http_conn, 267 | PurpleHttpResponse *response, gpointer user_data) 268 | { 269 | gsize len; 270 | const gchar *url_text = purple_http_response_get_data(response, &len); 271 | const gchar *error_message = purple_http_response_get_error(response); 272 | const gchar *body; 273 | gsize body_len; 274 | IcyQueProxyConnection *conn = user_data; 275 | JsonParser *parser = json_parser_new(); 276 | 277 | conn->ia->http_conns = g_slist_remove(conn->ia->http_conns, http_conn); 278 | 279 | icq_update_cookies(conn->ia, purple_http_response_get_headers_by_name(response, "Set-Cookie")); 280 | 281 | body = url_text; 282 | body_len = len; 283 | 284 | if (body == NULL && error_message != NULL) { 285 | /* connection error - unresolvable dns name, non existing server */ 286 | gchar *error_msg_formatted = g_strdup_printf(_("Connection error: %s."), error_message); 287 | purple_connection_error(conn->ia->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg_formatted); 288 | g_free(error_msg_formatted); 289 | g_free(conn); 290 | return; 291 | } 292 | 293 | if (body != NULL && !json_parser_load_from_data(parser, body, body_len, NULL)) { 294 | if (conn->callback) { 295 | JsonObject *dummy_object = json_object_new(); 296 | 297 | json_object_set_string_member(dummy_object, "body", body); 298 | json_object_set_int_member(dummy_object, "len", body_len); 299 | g_dataset_set_data(dummy_object, "raw_body", (gpointer) body); 300 | 301 | conn->callback(conn->ia, dummy_object, conn->user_data); 302 | 303 | g_dataset_destroy(dummy_object); 304 | json_object_unref(dummy_object); 305 | } 306 | } else { 307 | JsonNode *root = json_parser_get_root(parser); 308 | 309 | purple_debug_misc("icyque", "Got response: %s\n", body); 310 | 311 | if (conn->callback) { 312 | if (root != NULL) { 313 | conn->callback(conn->ia, json_node_get_object(root), conn->user_data); 314 | } else { 315 | conn->callback(conn->ia, NULL, conn->user_data); 316 | } 317 | } 318 | 319 | } 320 | 321 | g_object_unref(parser); 322 | g_free(conn); 323 | } 324 | 325 | static void 326 | icq_fetch_url_with_method(IcyQueAccount *ia, const gchar *method, const gchar *url, const gchar *postdata, IcyQueProxyCallbackFunc callback, gpointer user_data) 327 | { 328 | PurpleAccount *account; 329 | IcyQueProxyConnection *conn; 330 | gchar *cookies; 331 | PurpleHttpConnection *http_conn; 332 | 333 | account = ia->account; 334 | 335 | if (purple_account_is_disconnected(account)) { 336 | return; 337 | } 338 | 339 | conn = g_new0(IcyQueProxyConnection, 1); 340 | conn->ia = ia; 341 | conn->callback = callback; 342 | conn->user_data = user_data; 343 | 344 | cookies = icq_cookies_to_string(ia); 345 | 346 | if (method == NULL) { 347 | method = "GET"; 348 | } 349 | 350 | purple_debug_info("icyque", "Fetching url %s\n", url); 351 | 352 | 353 | PurpleHttpRequest *request = purple_http_request_new(url); 354 | purple_http_request_set_method(request, method); 355 | purple_http_request_header_set(request, "Accept", "*/*"); 356 | purple_http_request_header_set(request, "Cookie", cookies); 357 | purple_http_request_set_timeout(request, 59); 358 | 359 | if (postdata) { 360 | if (strstr(url, "/auth/clientLogin") && strstr(postdata, "pwd")) { 361 | purple_debug_info("icyque", "With postdata ###PASSWORD REMOVED###\n"); 362 | } else { 363 | purple_debug_info("icyque", "With postdata %s\n", postdata); 364 | } 365 | 366 | if (postdata[0] == '{') { 367 | purple_http_request_header_set(request, "Content-Type", "application/json"); 368 | } else { 369 | purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded"); 370 | } 371 | 372 | purple_http_request_set_contents(request, postdata, -1); 373 | } 374 | 375 | purple_http_request_set_keepalive_pool(request, ia->keepalive_pool); 376 | 377 | http_conn = purple_http_request(ia->pc, request, icq_response_callback, conn); 378 | purple_http_request_unref(request); 379 | 380 | if (http_conn != NULL) { 381 | ia->http_conns = g_slist_prepend(ia->http_conns, http_conn); 382 | } 383 | 384 | 385 | g_free(cookies); 386 | } 387 | 388 | static PurpleGroup * 389 | icq_get_or_create_default_group(const gchar *group_name) 390 | { 391 | if (group_name == NULL) { 392 | group_name = "ICQ"; 393 | } 394 | 395 | PurpleGroup *icq_group = purple_blist_find_group(group_name); 396 | 397 | if (!icq_group) { 398 | icq_group = purple_group_new(group_name); 399 | purple_blist_add_group(icq_group, NULL); 400 | } 401 | 402 | return icq_group; 403 | } 404 | 405 | static const char * 406 | icq_list_icon(PurpleAccount *account, PurpleBuddy *buddy) 407 | { 408 | return "icq"; 409 | } 410 | 411 | static GList * 412 | icq_status_types(PurpleAccount *account) 413 | { 414 | GList *types = NULL; 415 | PurpleStatusType *status; 416 | 417 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, "online", _("Online"), TRUE, TRUE, FALSE, "message", _("Status"), purple_value_new(PURPLE_TYPE_STRING), NULL); 418 | types = g_list_append(types, status); 419 | 420 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, "away", _("Away"), TRUE, FALSE, FALSE, "message", _("Status"), purple_value_new(PURPLE_TYPE_STRING), NULL); 421 | types = g_list_append(types, status); 422 | 423 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_MOBILE, "mobile", _("Mobile"), TRUE, FALSE, FALSE, "message", _("Status"), purple_value_new(PURPLE_TYPE_STRING), NULL); 424 | types = g_list_append(types, status); 425 | 426 | status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, "offline", _("Offline"), TRUE, TRUE, FALSE); 427 | types = g_list_append(types, status); 428 | 429 | return types; 430 | } 431 | 432 | static gchar * 433 | icq_status_text(PurpleBuddy *buddy) 434 | { 435 | const gchar *message = purple_status_get_attr_string(purple_presence_get_active_status(purple_buddy_get_presence(buddy)), "message"); 436 | 437 | if (message == NULL) { 438 | return NULL; 439 | } 440 | 441 | return g_markup_printf_escaped("%s", message); 442 | } 443 | 444 | static void 445 | icq_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full) 446 | { 447 | PurplePresence *presence; 448 | PurpleStatus *status; 449 | const gchar *message; 450 | 451 | g_return_if_fail(buddy != NULL); 452 | 453 | presence = purple_buddy_get_presence(buddy); 454 | status = purple_presence_get_active_status(presence); 455 | purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status)); 456 | 457 | message = purple_status_get_attr_string(status, "message"); 458 | if (message != NULL) { 459 | purple_notify_user_info_add_pair_html(user_info, _("Message"), message); 460 | } 461 | } 462 | 463 | static void 464 | icq_set_status(PurpleAccount *account, PurpleStatus *status) 465 | { 466 | PurpleConnection *pc = purple_account_get_connection(account); 467 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 468 | const gchar *status_id = purple_status_get_id(status); //online, mobile, away, offline 469 | 470 | gchar *uuid = purple_uuid_random(); 471 | GString *postdata = g_string_new(NULL); 472 | const gchar *url = ICQ_API_SERVER "/presence/setState"; 473 | 474 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 475 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 476 | g_string_append(postdata, "f=json&"); 477 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 478 | g_string_append_printf(postdata, "ts=%d&", (int)(time(NULL) - ia->server_time_offset)); 479 | g_string_append_printf(postdata, "view=%s", purple_url_encode(status_id)); 480 | 481 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 482 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 483 | g_free(sig_sha256); 484 | 485 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 486 | 487 | g_string_free(postdata, TRUE); 488 | g_free(uuid); 489 | 490 | 491 | const gchar *message = purple_status_get_attr_string(status, "message"); 492 | uuid = purple_uuid_random(); 493 | postdata = g_string_new(NULL); 494 | url = ICQ_API_SERVER "/presence/setStatus"; 495 | 496 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 497 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 498 | g_string_append(postdata, "f=json&"); 499 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 500 | g_string_append_printf(postdata, "statusMsg=%s&", purple_url_encode(message ? message : "")); 501 | g_string_append_printf(postdata, "ts=%d", (int)(time(NULL) - ia->server_time_offset)); 502 | 503 | sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 504 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 505 | g_free(sig_sha256); 506 | 507 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 508 | 509 | g_string_free(postdata, TRUE); 510 | g_free(uuid); 511 | } 512 | 513 | 514 | static void 515 | icq_block_user(PurpleConnection *pc, const char *who) 516 | { 517 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 518 | 519 | GString *postdata = g_string_new(NULL); 520 | const gchar *url = ICQ_API_SERVER "/preference/setPermitDeny"; 521 | 522 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 523 | g_string_append(postdata, "f=json&"); 524 | g_string_append_printf(postdata, "pdIgnore=%s&", purple_url_encode(who)); 525 | 526 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 527 | 528 | g_string_free(postdata, TRUE); 529 | } 530 | 531 | static void 532 | icq_unblock_user(PurpleConnection *pc, const char *who) 533 | { 534 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 535 | 536 | GString *postdata = g_string_new(NULL); 537 | const gchar *url = ICQ_API_SERVER "/preference/setPermitDeny"; 538 | 539 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 540 | g_string_append(postdata, "f=json&"); 541 | g_string_append_printf(postdata, "pdIgnoreRemove=%s&", purple_url_encode(who)); 542 | 543 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 544 | 545 | g_string_free(postdata, TRUE); 546 | } 547 | 548 | static void 549 | icq_got_user_info(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 550 | { 551 | if (obj == NULL) { 552 | return; 553 | } 554 | 555 | JsonObject *response = json_object_get_object_member(obj, "response"); 556 | JsonObject *data = json_object_get_object_member(response, "data"); 557 | JsonArray *infoArray = json_object_get_array_member(data, "infoArray"); 558 | guint len = json_array_get_length(infoArray); 559 | 560 | if (!infoArray || !len) { 561 | return; 562 | } 563 | 564 | JsonObject *info = json_array_get_object_element(infoArray, 0); 565 | JsonObject *profile = json_object_get_object_member(info, "profile"); 566 | 567 | if (profile == NULL) { 568 | return; 569 | } 570 | 571 | const gchar *aimId = json_object_get_string_member(profile, "aimId"); 572 | if (aimId == NULL) { 573 | return; 574 | } 575 | 576 | PurpleNotifyUserInfo *user_info = purple_notify_user_info_new(); 577 | 578 | purple_notify_user_info_add_pair_html(user_info, _("ID"), aimId); 579 | 580 | purple_notify_user_info_add_pair_html(user_info, _("First name"), 581 | json_object_get_string_member(profile, "firstName")); 582 | purple_notify_user_info_add_pair_html(user_info, _("Last name"), 583 | json_object_get_string_member(profile, "lastName")); 584 | purple_notify_user_info_add_pair_html(user_info, _("Gender"), 585 | json_object_get_string_member(profile, "gender")); 586 | purple_notify_user_info_add_pair_html(user_info, _("Alias"), 587 | json_object_get_string_member(profile, "friendlyName")); 588 | 589 | 590 | purple_notify_userinfo(ia->pc, aimId, user_info, NULL, NULL); 591 | } 592 | 593 | static void 594 | icq_get_info(PurpleConnection *pc, const gchar *who) 595 | { 596 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 597 | GString *postdata = g_string_new(NULL); 598 | const gchar *url = ICQ_API_SERVER "/memberDir/get"; 599 | gchar *uuid = purple_uuid_random(); 600 | 601 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 602 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 603 | g_string_append(postdata, "f=json&"); 604 | g_string_append(postdata, "infoLevel=full&"); 605 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 606 | g_string_append_printf(postdata, "t=%s&", purple_url_encode(who)); 607 | g_string_append_printf(postdata, "ts=%d", (int) time(NULL)); 608 | 609 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 610 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 611 | g_free(sig_sha256); 612 | 613 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, icq_got_user_info, NULL); 614 | 615 | g_string_free(postdata, TRUE); 616 | g_free(uuid); 617 | } 618 | 619 | static void 620 | icq_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message) 621 | { 622 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 623 | const gchar *who = purple_buddy_get_name(buddy); 624 | const gchar *alias = purple_buddy_get_alias(buddy); 625 | const gchar *group_name = purple_group_get_name(group); 626 | 627 | GString *postdata = g_string_new(NULL); 628 | const gchar *url = ICQ_API_SERVER "/buddylist/addBuddy"; 629 | gchar *uuid = purple_uuid_random(); 630 | 631 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 632 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 633 | g_string_append_printf(postdata, "authorizationMsg=%s&", purple_url_encode(message)); 634 | g_string_append_printf(postdata, "buddy=%s&", purple_url_encode(who)); 635 | g_string_append(postdata, "f=json&"); 636 | g_string_append_printf(postdata, "group=%s&", purple_url_encode(group_name)); 637 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 638 | g_string_append_printf(postdata, "nick=%s&", purple_url_encode(alias && *alias ? alias : who)); 639 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 640 | g_string_append(postdata, "preAuthorized=true&"); 641 | g_string_append_printf(postdata, "ts=%d", (int) time(NULL)); 642 | 643 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 644 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 645 | g_free(sig_sha256); 646 | 647 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 648 | 649 | g_string_free(postdata, TRUE); 650 | g_free(uuid); 651 | } 652 | 653 | #if !PURPLE_VERSION_CHECK(3, 0, 0) 654 | static void 655 | icq_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) 656 | { 657 | icq_add_buddy_with_invite(pc, buddy, group, NULL); 658 | } 659 | #endif 660 | 661 | static void 662 | icq_remove_buddy_by_name(IcyQueAccount *ia, const gchar *who) 663 | { 664 | GString *postdata = g_string_new(NULL); 665 | const gchar *url = ICQ_API_SERVER "/buddylist/removeBuddy"; 666 | gchar *uuid = purple_uuid_random(); 667 | 668 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 669 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 670 | g_string_append(postdata, "allGroups=true&"); 671 | g_string_append_printf(postdata, "buddy=%s&", purple_url_encode(who)); 672 | g_string_append(postdata, "f=json&"); 673 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 674 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 675 | g_string_append_printf(postdata, "ts=%d", (int) time(NULL)); 676 | 677 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 678 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 679 | g_free(sig_sha256); 680 | 681 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 682 | 683 | g_string_free(postdata, TRUE); 684 | g_free(uuid); 685 | } 686 | 687 | // static void 688 | // icq_remove_buddy(PurpleConnection *connection, PurpleBuddy *buddy, PurpleGroup *group) 689 | // { 690 | // IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 691 | // const gchar *buddy_name = purple_buddy_get_name(buddy); 692 | 693 | // icq_remove_buddy_by_name(ia, buddy_name); 694 | // } 695 | 696 | static void 697 | icq_chat_leave(PurpleConnection *pc, int id) 698 | { 699 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 700 | const gchar *sn; 701 | PurpleChatConversation *chatconv; 702 | 703 | chatconv = purple_conversations_find_chat(pc, id); 704 | sn = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "sn"); 705 | if (sn == NULL) { 706 | sn = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv)); 707 | g_return_if_fail(sn); 708 | } 709 | 710 | return icq_remove_buddy_by_name(ia, sn); 711 | } 712 | 713 | static void 714 | icq_chat_kick(PurpleConnection *pc, int id, const gchar *who) 715 | { 716 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 717 | const gchar *sn; 718 | PurpleChatConversation *chatconv; 719 | 720 | chatconv = purple_conversations_find_chat(pc, id); 721 | sn = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "sn"); 722 | if (sn == NULL) { 723 | sn = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv)); 724 | g_return_if_fail(sn); 725 | } 726 | 727 | GString *postdata = g_string_new(NULL); 728 | gchar *uuid = purple_uuid_random(); 729 | const gchar *url = ICQ_API_SERVER "/mchat/DelMembers"; 730 | 731 | // Needs to be alphabetical 732 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 733 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 734 | g_string_append_printf(postdata, "chat_id=%s&", purple_url_encode(sn)); 735 | g_string_append(postdata, "f=json&"); 736 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 737 | g_string_append_printf(postdata, "members=%s&", purple_url_encode(who)); 738 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 739 | g_string_append_printf(postdata, "ts=%d", (int) time(NULL)); 740 | 741 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 742 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 743 | g_free(sig_sha256); 744 | 745 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 746 | 747 | g_string_free(postdata, TRUE); 748 | g_free(uuid); 749 | } 750 | 751 | static GList * 752 | icq_chat_info(PurpleConnection *pc) 753 | { 754 | GList *m = NULL; 755 | PurpleProtocolChatEntry *pce; 756 | 757 | pce = g_new0(PurpleProtocolChatEntry, 1); 758 | pce->label = _("Group ID"); 759 | pce->identifier = "sn"; 760 | pce->required = TRUE; 761 | m = g_list_append(m, pce); 762 | 763 | return m; 764 | } 765 | 766 | static GHashTable * 767 | icq_chat_info_defaults(PurpleConnection *pc, const char *chatname) 768 | { 769 | GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); 770 | 771 | if (chatname != NULL) 772 | { 773 | g_hash_table_insert(defaults, "sn", g_strdup(chatname)); 774 | } 775 | 776 | return defaults; 777 | } 778 | 779 | static gchar * 780 | icq_get_chat_name(GHashTable *data) 781 | { 782 | gchar *temp; 783 | 784 | if (data == NULL) 785 | return NULL; 786 | 787 | temp = g_hash_table_lookup(data, "sn"); 788 | 789 | if (temp == NULL) 790 | return NULL; 791 | 792 | return g_strdup(temp); 793 | } 794 | 795 | static void 796 | icq_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who) 797 | { 798 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 799 | GString *postdata = g_string_new(NULL); 800 | gchar *uuid = purple_uuid_random(); 801 | const gchar *url = ICQ_API_SERVER "/mchat/AddChat"; 802 | PurpleChatConversation *chatconv = purple_conversations_find_chat(pc, id); 803 | const gchar *sn = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "sn"); 804 | 805 | if (!sn) { 806 | sn = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv)); 807 | g_return_if_fail(sn); 808 | } 809 | 810 | // Needs to be alphabetical 811 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 812 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 813 | g_string_append_printf(postdata, "chat_id=%s&", purple_url_encode(sn)); 814 | g_string_append(postdata, "f=json&"); 815 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 816 | g_string_append_printf(postdata, "members=%s&", purple_url_encode(who)); 817 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 818 | g_string_append_printf(postdata, "ts=%d", (int) time(NULL)); 819 | 820 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 821 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 822 | g_free(sig_sha256); 823 | 824 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 825 | 826 | g_string_free(postdata, TRUE); 827 | g_free(uuid); 828 | } 829 | 830 | 831 | static void 832 | icq_join_chat_got_chat_info_cb(IcyQueAccount *ia, JsonObject *data, gpointer user_data){ 833 | // Example response: 834 | /* 835 | { 836 | "ts": 1576097893, 837 | "status": { 838 | "code": 20000 839 | }, 840 | "method": "getIdInfo", 841 | "reqId": "censored", 842 | "results": { 843 | "chat": { 844 | "sn": "682293892@chat.agent", 845 | "about": "Just learning", 846 | "name": "Test", 847 | "stamp": "AoLFq-UEyLqpbUxAA0c", 848 | "memberCount": 2, 849 | "public": true 850 | } 851 | } 852 | } 853 | */ 854 | 855 | JsonObject *json_results = json_object_get_object_member(data, "results"); 856 | JsonObject *json_chat = json_object_get_object_member(json_results, "chat"); 857 | 858 | const gchar *chat_sn = json_object_get_string_member(json_chat, "sn"); 859 | const gchar *chat_name = json_object_get_string_member(json_chat, "name"); 860 | const gchar *chat_stamp = json_object_get_string_member(json_chat, "stamp"); 861 | const gchar *chat_about = json_object_get_string_member(json_chat, "about"); 862 | 863 | 864 | if (chat_sn) { 865 | PurpleChatConversation *chatconv = purple_serv_got_joined_chat(ia->pc, g_str_hash(chat_sn), chat_sn); 866 | purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "sn", g_strdup(chat_sn)); 867 | purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "stamp", g_strdup(chat_stamp)); 868 | purple_conversation_set_title(PURPLE_CONVERSATION(chatconv), g_strdup(chat_name)); 869 | purple_conv_chat_set_topic(chatconv, NULL, g_strdup(chat_about)); 870 | } 871 | 872 | //TODO download history and room list members 873 | } 874 | 875 | static void 876 | icq_joined_chat_cb(IcyQueAccount *ia, JsonObject *data, gpointer user_data){ 877 | gchar *chatId = user_data; 878 | 879 | // {"method":"getIdInfo","reqId":"censored","aimsid":"censored","params":{"id":"AoLFq-UEyLqpbUxAA0c"}} 880 | JsonObject* getIdInfoParams = json_object_new(); 881 | json_object_set_string_member(getIdInfoParams, "aimsid", ia->aimsid); 882 | json_object_set_string_member(getIdInfoParams, "id", chatId); 883 | 884 | JsonObject* getIdInfoRequest = icq_generate_robusto_request(ia, "getIdInfo", getIdInfoParams); 885 | 886 | gchar* getIdInfoRequestStr = json_object_to_string(getIdInfoRequest); 887 | json_object_unref(getIdInfoRequest); 888 | g_free(chatId); 889 | 890 | icq_fetch_url_with_method(ia, "POST", ICQ_RAPI_SERVER, getIdInfoRequestStr, icq_join_chat_got_chat_info_cb, NULL); 891 | g_free(getIdInfoRequestStr); 892 | } 893 | 894 | static void 895 | icq_join_chat_send_request(IcyQueAccount *ia, const gchar *stamp) 896 | { 897 | // {"method":"joinChat","reqId":"censored","aimsid":"censored","params":{"stamp":"AoLFq-UEyLqpbUxAA0c"}} 898 | JsonObject* joinChatParams = json_object_new(); 899 | json_object_set_string_member(joinChatParams, "aimsid", ia->aimsid); 900 | json_object_set_string_member(joinChatParams, "stamp", stamp); 901 | 902 | JsonObject *joinChatRequest = icq_generate_robusto_request(ia, "joinChat", joinChatParams); 903 | 904 | gchar* joinChatRequestStr = json_object_to_string(joinChatRequest); 905 | json_object_unref(joinChatRequest); 906 | 907 | icq_fetch_url_with_method(ia, "POST", ICQ_RAPI_SERVER, joinChatRequestStr, icq_joined_chat_cb, g_strdup(stamp)); 908 | g_free(joinChatRequestStr); 909 | } 910 | 911 | static void 912 | icq_join_chat_with_sn_convert_request_cb(IcyQueAccount *ia, JsonObject *data, gpointer user_data) 913 | { 914 | // Read the chat stamp from the ChatInfo and join the chat. 915 | 916 | /* 917 | { 918 | "ts": 1576185167, 919 | "status": {"code": 20000}, 920 | "method": "getChatInfo", 921 | "reqId": "29884-1576185166", 922 | "results": { 923 | "name": "MTL/TORONTO CARDING ™", 924 | "about": "Spam and Carding ", 925 | "stamp": "AoLEztZBCgVxGUuu4jM", 926 | "createTime": 1533396838, 927 | "avatarLastModified": 1533396936, 928 | "blockedCount": 57, 929 | "creator": "740484960", 930 | "live": true, 931 | "controlled": true, 932 | "infoVersion": "6593762738562316835", 933 | "membersVersion": "6769650523642062326", 934 | "membersCount": 2848, 935 | "adminsCount": 4, 936 | "defaultRole": "member", 937 | "regions": "CA", 938 | "sn": "680766273@chat.agent", 939 | "abuseReportsCurrentCount": 0, 940 | "you": {"role": "member"}, 941 | "members": [ 942 | { 943 | "sn": "746705346", 944 | "role": "member", 945 | "noAvatar": true, 946 | "lastseen": 0, 947 | "friendly": "anonymous anonymous", 948 | "anketa": { 949 | "sn": "746705346", 950 | "firstName": "anonymous", 951 | "lastName": "anonymous", 952 | "friendly": "anonymous anonymous" 953 | } 954 | }, 955 | ] 956 | } 957 | */ 958 | 959 | JsonObject *json_results = json_object_get_object_member(data, "results"); 960 | const gchar *chat_stamp = json_object_get_string_member(json_results, "stamp"); 961 | 962 | icq_join_chat_send_request(ia, chat_stamp); 963 | } 964 | 965 | static void 966 | icq_join_chat_with_sn_send_convert_request(IcyQueAccount *ia, const gchar *sn) 967 | { 968 | // Obtain the chat stamp and join the chat. 969 | 970 | /* 971 | { 972 | method: "getChatInfo", 973 | reqId: "29884-1576185166", 974 | aimsid: "003.4098791917.2548346359:746705346" 975 | params: { 976 | sn: "680766273@chat.agent", 977 | memberLimit: 50 978 | } 979 | } 980 | */ 981 | 982 | JsonObject* getChatInfoParams = json_object_new(); 983 | json_object_set_string_member(getChatInfoParams, "aimsid", ia->aimsid); 984 | json_object_set_string_member(getChatInfoParams, "sn", sn); 985 | json_object_set_int_member(getChatInfoParams, "memberLimit", 50); 986 | 987 | JsonObject *getChatInfoRequest = icq_generate_robusto_request(ia, "getChatInfo", getChatInfoParams); 988 | 989 | gchar* getChatInfoRequestStr = json_object_to_string(getChatInfoRequest); 990 | json_object_unref(getChatInfoRequest); 991 | 992 | icq_fetch_url_with_method(ia, "POST", ICQ_RAPI_SERVER, getChatInfoRequestStr, icq_join_chat_with_sn_convert_request_cb, NULL); 993 | g_free(getChatInfoRequestStr); 994 | } 995 | 996 | static void 997 | icq_join_chat(PurpleConnection *pc, GHashTable *data) 998 | { 999 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 1000 | const gchar *sn; 1001 | PurpleChatConversation *chatconv; 1002 | 1003 | sn = g_hash_table_lookup(data, "sn"); 1004 | if (sn == NULL) 1005 | { 1006 | return; 1007 | } 1008 | 1009 | chatconv = purple_conversations_find_chat_with_account(sn, ia->account); 1010 | if (chatconv != NULL && !purple_chat_conversation_has_left(chatconv)) { 1011 | purple_conversation_present(PURPLE_CONVERSATION(chatconv)); 1012 | return; 1013 | } 1014 | 1015 | // Is it actually an sn or a stamp? An sn ends with @chat.agent 1016 | if (g_str_has_suffix(sn, "@chat.agent")) { 1017 | icq_join_chat_with_sn_send_convert_request(ia, sn); 1018 | return; 1019 | } 1020 | 1021 | // The sn is actually a stamp 1022 | icq_join_chat_send_request(ia, sn); 1023 | 1024 | } 1025 | 1026 | static void 1027 | icq_sent_msg(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1028 | { 1029 | JsonObject *response = json_object_get_object_member(obj, "response"); 1030 | JsonObject *data = json_object_get_object_member(response, "data"); 1031 | const gchar *msgId = json_object_get_string_member(data, "msgId"); 1032 | 1033 | if (msgId != NULL) { 1034 | gchar *id = g_strdup(msgId); 1035 | g_hash_table_replace(ia->sent_messages_hash, id, id); 1036 | } 1037 | 1038 | } 1039 | 1040 | static int 1041 | icq_send_msg(IcyQueAccount *ia, const gchar *to, const gchar *message) 1042 | { 1043 | GString *postdata = g_string_new(NULL); 1044 | gchar *stripped = purple_markup_strip_html(message); 1045 | gchar *uuid = purple_uuid_random(); 1046 | const gchar *url = ICQ_API_SERVER "/im/sendIM"; 1047 | 1048 | // Needs to be alphabetical 1049 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 1050 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 1051 | g_string_append(postdata, "f=json&"); 1052 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 1053 | g_string_append(postdata, "mentions=&"); 1054 | g_string_append_printf(postdata, "message=%s&", purple_url_encode(stripped)); 1055 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 1056 | g_string_append(postdata, "offlineIM=true&"); 1057 | g_string_append_printf(postdata, "t=%s&", purple_url_encode(to)); 1058 | g_string_append_printf(postdata, "ts=%d", (int) time(NULL)); 1059 | 1060 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 1061 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 1062 | g_free(sig_sha256); 1063 | 1064 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, icq_sent_msg, NULL); 1065 | 1066 | g_string_free(postdata, TRUE); 1067 | g_free(stripped); 1068 | g_free(uuid); 1069 | 1070 | return 1; 1071 | } 1072 | 1073 | static int 1074 | icq_send_im(PurpleConnection *pc, 1075 | #if PURPLE_VERSION_CHECK(3, 0, 0) 1076 | PurpleMessage *msg) 1077 | { 1078 | const gchar *who = purple_message_get_recipient(msg); 1079 | const gchar *message = purple_message_get_contents(msg); 1080 | #else 1081 | const gchar *who, const gchar *message, PurpleMessageFlags flags) 1082 | { 1083 | #endif 1084 | 1085 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 1086 | 1087 | return icq_send_msg(ia, who, message); 1088 | } 1089 | 1090 | static gint 1091 | icq_chat_send(PurpleConnection *pc, gint id, 1092 | #if PURPLE_VERSION_CHECK(3, 0, 0) 1093 | PurpleMessage *msg) 1094 | { 1095 | const gchar *message = purple_message_get_contents(msg); 1096 | #else 1097 | const gchar *message, PurpleMessageFlags flags) 1098 | { 1099 | #endif 1100 | 1101 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 1102 | PurpleChatConversation *chatconv = purple_conversations_find_chat(pc, id); 1103 | const gchar *sn = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "sn"); 1104 | 1105 | if (!sn) { 1106 | sn = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv)); 1107 | g_return_val_if_fail(sn, -1); 1108 | } 1109 | 1110 | return icq_send_msg(ia, sn, message); 1111 | } 1112 | 1113 | static guint 1114 | icq_send_typing(PurpleConnection *pc, const gchar *who, PurpleIMTypingState state) 1115 | { 1116 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 1117 | GString *postdata = g_string_new(NULL); 1118 | gchar *uuid = purple_uuid_random(); 1119 | const gchar *url = ICQ_API_SERVER "/im/setTyping"; 1120 | const gchar *typingStatus = "typing"; 1121 | 1122 | if (state == PURPLE_IM_TYPED) { 1123 | typingStatus = "typed"; 1124 | } else if (state == PURPLE_IM_NOT_TYPING) { 1125 | typingStatus = "none"; 1126 | } 1127 | 1128 | // Needs to be alphabetical 1129 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 1130 | g_string_append_printf(postdata, "aimsid=%s&", purple_url_encode(ia->aimsid)); 1131 | g_string_append(postdata, "f=json&"); 1132 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 1133 | g_string_append_printf(postdata, "nonce=%s&", purple_url_encode(uuid)); 1134 | g_string_append_printf(postdata, "t=%s&", purple_url_encode(who)); 1135 | g_string_append_printf(postdata, "ts=%d&", (int) time(NULL)); 1136 | g_string_append_printf(postdata, "typingStatus=%s", typingStatus); 1137 | 1138 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 1139 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 1140 | g_free(sig_sha256); 1141 | 1142 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, NULL /*TODO*/, NULL); 1143 | 1144 | g_string_free(postdata, TRUE); 1145 | g_free(uuid); 1146 | 1147 | return 10; 1148 | } 1149 | 1150 | static void 1151 | icq_got_buddy_icon(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1152 | { 1153 | PurpleBuddy *buddy = user_data; 1154 | 1155 | if (obj != NULL) { 1156 | const gchar *response_str; 1157 | gsize response_len; 1158 | gpointer response_dup; 1159 | const gchar *buddyIcon = g_dataset_get_data(buddy, "buddyIcon"); 1160 | 1161 | response_str = g_dataset_get_data(obj, "raw_body"); 1162 | response_len = json_object_get_int_member(obj, "len"); 1163 | response_dup = g_memdup(response_str, response_len); 1164 | 1165 | const gchar *username = purple_buddy_get_name(buddy); 1166 | 1167 | purple_buddy_icons_set_for_user(ia->account, username, response_dup, response_len, buddyIcon); 1168 | } 1169 | 1170 | g_dataset_destroy(buddy); 1171 | } 1172 | 1173 | static void 1174 | icq_get_chat_history(IcyQueAccount *ia, const gchar* chatId, const gchar* fromMsg, gint64 count, IcyQueProxyCallbackFunc callback, gpointer user_data) 1175 | { 1176 | JsonObject* getHistoryParams = json_object_new(); 1177 | json_object_set_string_member(getHistoryParams, "aimsid", ia->aimsid); 1178 | json_object_set_string_member(getHistoryParams, "lang", "en-US"); 1179 | //FIXME: I have absolutely no idea where this comes from or what this is. 1180 | //TODO: the initial histDlgState event contains a patchVersion. Store this per conversation and use that..? 1181 | json_object_set_string_member(getHistoryParams, "patchVersion", "1"); 1182 | json_object_set_string_member(getHistoryParams, "sn", chatId); 1183 | json_object_set_string_member(getHistoryParams, "fromMsgId", fromMsg); 1184 | json_object_set_int_member(getHistoryParams, "count", count); 1185 | 1186 | JsonObject *getHistoryRequest = icq_generate_robusto_request(ia, "getHistory", getHistoryParams); 1187 | gchar* getHistoryRequestStr = json_object_to_string(getHistoryRequest); 1188 | json_object_unref(getHistoryRequest); 1189 | 1190 | icq_fetch_url_with_method(ia, "POST", ICQ_RAPI_SERVER, getHistoryRequestStr, callback, user_data); 1191 | g_free(getHistoryRequestStr); 1192 | } 1193 | 1194 | static GList *valid_icyque_accounts = NULL; 1195 | #define ICYQUE_ACCOUNT_IS_VALID(ia) (g_list_find(valid_icyque_accounts, (ia)) != NULL) 1196 | 1197 | static void 1198 | icq_mark_message_as_read(IcyQueAccount *ia, const gchar *sn, const gchar *messageId) 1199 | { 1200 | JsonObject *setDlgStateParams = json_object_new(); 1201 | JsonArray *exclude = json_array_new(); 1202 | json_object_set_string_member(setDlgStateParams, "aimSid", ia->aimsid); 1203 | json_object_set_array_member(setDlgStateParams, "exclude", exclude); 1204 | json_object_set_string_member(setDlgStateParams, "lastRead", messageId); 1205 | json_object_set_string_member(setDlgStateParams, "sn", sn); 1206 | 1207 | JsonObject *setDlgStateRequest = icq_generate_robusto_request(ia, "setDlgState", setDlgStateParams); 1208 | gchar* setDlgStateRequestStr = json_object_to_string(setDlgStateRequest); 1209 | json_object_unref(setDlgStateRequest); 1210 | 1211 | icq_fetch_url_with_method(ia, "POST", ICQ_RAPI_SERVER, setDlgStateRequestStr, NULL, NULL); 1212 | g_free(setDlgStateRequestStr); 1213 | } 1214 | 1215 | static void 1216 | icq_unread_message_load_cb(IcyQueAccount *ia, JsonObject *data, gpointer user_data) 1217 | { 1218 | JsonObject *status = json_object_get_object_member(data, "status"); 1219 | if(status && json_object_get_int_member(status, "code") == 20000) { 1220 | JsonObject *results = json_object_get_object_member(data, "results"); 1221 | // Acquire persons first 1222 | JsonArray *persons = json_object_get_array_member(results, "persons"); 1223 | const gchar* sn = NULL; // TODO: Support group chats here 1224 | gint i, len = json_array_get_length(persons); 1225 | if(len == 0) return; 1226 | if(len > 1) { 1227 | return; 1228 | } 1229 | JsonObject* firstPerson = json_array_get_object_element(persons, 0); 1230 | sn = json_object_get_string_member(firstPerson, "sn"); 1231 | 1232 | JsonArray *messages = json_object_get_array_member(results, "messages"); 1233 | len = json_array_get_length(messages); 1234 | 1235 | for (i = (len - 1); i >= 0; i--) { 1236 | JsonObject *message = json_array_get_object_element(messages, i); 1237 | gint64 time = json_object_get_int_member(message, "time"); 1238 | const gchar* text = json_object_get_string_member(message, "text"); 1239 | gchar *escaped_text = purple_markup_escape_text(text, -1); 1240 | purple_serv_got_im(ia->pc, sn, escaped_text, PURPLE_MESSAGE_RECV, (time_t) time); 1241 | g_free(escaped_text); 1242 | } 1243 | 1244 | // Take the last received message's id, and mark it as read. 1245 | if(len > 0) { 1246 | JsonObject* lastMessage = json_array_get_object_element(messages, 0); 1247 | const gchar* messageId = json_object_get_string_member(lastMessage, "msgId"); 1248 | gint64 time = json_object_get_int_member(lastMessage, "time"); 1249 | 1250 | ia->last_message_timestamp = MAX(ia->last_message_timestamp, time); 1251 | purple_account_set_int(ia->account, "last_message_timestamp_high", ia->last_message_timestamp >> 32); 1252 | purple_account_set_int(ia->account, "last_message_timestamp_low", ia->last_message_timestamp & 0xFFFFFFFF); 1253 | //TODO: Should this be done directly after retrieiving them here? 1254 | // should it rather be done, when the conversation window is activated? 1255 | // .. Should it be done at all? (Responding to a message automatically marks received messages as read) 1256 | //TODO: Whatever the decision: This should be applied to normal messages (online messages) as well. 1257 | icq_mark_message_as_read(ia, sn, messageId); 1258 | } 1259 | } else { 1260 | purple_debug_warning("icyque", "Failed to retrieve unread messages."); 1261 | } 1262 | } 1263 | 1264 | static void 1265 | icq_process_event(IcyQueAccount *ia, const gchar *event_type, JsonObject *data) 1266 | { 1267 | if (event_type == NULL) return; 1268 | if (!ICYQUE_ACCOUNT_IS_VALID(ia)) return; 1269 | 1270 | if (purple_strequal(event_type, "presence")) { 1271 | const gchar *aimId = json_object_get_string_member(data, "aimId"); 1272 | const gchar *state = json_object_get_string_member(data, "state"); 1273 | const gchar *statusMsg = json_object_get_string_member(data, "statusMsg"); 1274 | 1275 | if (statusMsg != NULL) { 1276 | purple_protocol_got_user_status(ia->account, aimId, state, "message", statusMsg, NULL); 1277 | } else { 1278 | purple_protocol_got_user_status(ia->account, aimId, state, NULL); 1279 | } 1280 | 1281 | PurpleBuddy *pbuddy = purple_blist_find_buddy(ia->account, aimId); 1282 | if (pbuddy != NULL) { 1283 | const gchar *buddyIcon = json_object_get_string_member(data, "buddyIcon"); 1284 | 1285 | if (!purple_strequal(purple_buddy_icons_get_checksum_for_user(pbuddy), buddyIcon)) { 1286 | g_dataset_set_data_full(pbuddy, "buddyIcon", g_strdup(buddyIcon), g_free); 1287 | 1288 | icq_fetch_url_with_method(ia, "GET", buddyIcon, NULL, icq_got_buddy_icon, pbuddy); 1289 | } 1290 | } 1291 | 1292 | } else if (purple_strequal(event_type, "typing")) { 1293 | const gchar *aimId = json_object_get_string_member(data, "aimId"); 1294 | const gchar *typingStatus = json_object_get_string_member(data, "typingStatus"); 1295 | PurpleIMTypingState typing_state; 1296 | 1297 | if (purple_strequal(typingStatus, "typing")) { 1298 | typing_state = PURPLE_IM_TYPING; 1299 | } else if (purple_strequal(typingStatus, "typed")) { 1300 | typing_state = PURPLE_IM_TYPED; 1301 | } else { 1302 | typing_state = PURPLE_IM_NOT_TYPING; 1303 | } 1304 | 1305 | purple_serv_got_typing(ia->pc, aimId, 10, typing_state); 1306 | 1307 | } else if (purple_strequal(event_type, "histDlgState")) { 1308 | const gchar *sn = json_object_get_string_member(data, "sn"); 1309 | // Use initial fetch-event (starting == true) to load all unread messages. 1310 | if(json_object_get_boolean_member(data, "starting")) { 1311 | guint64 unreadMsgCnt = json_object_get_int_member(data, "unreadCnt"); 1312 | if(unreadMsgCnt > 0) { 1313 | purple_debug_info("icyque", "Acquiring unread messages for conversation: %s\n", sn); 1314 | //TODO: "fromMsgId == -1" means last message. So the following loads all messages that are unread. 1315 | // Should we instead store the last message id that we saw, and sync all messages that have been sent 1316 | // in the meantime (with other clients e.g.) ? 1317 | //FIXME: Do NOT use "-1" here. This might lead to a race condition, when another client sends a new message now. 1318 | icq_get_chat_history(ia, sn, "-1", -unreadMsgCnt, icq_unread_message_load_cb, NULL); 1319 | } 1320 | } else { 1321 | JsonObject *tail = json_object_get_object_member(data, "tail"); 1322 | JsonArray *messages = json_object_get_array_member((tail != NULL ? tail : data), "messages"); 1323 | guint i, len = json_array_get_length(messages); 1324 | 1325 | for (i = 0; i < len; i++) { 1326 | JsonObject *message = json_array_get_object_element(messages, i); 1327 | gint64 time = json_object_get_int_member(message, "time"); 1328 | 1329 | if (ia->last_message_timestamp && time > ia->last_message_timestamp) { 1330 | const gchar *mediaType = json_object_get_string_member(message, "mediaType"); 1331 | const gchar *text = json_object_get_string_member(message, "text"); 1332 | PurpleMessageFlags msg_flags = PURPLE_MESSAGE_RECV; 1333 | 1334 | if (json_object_get_boolean_member(message, "outgoing")) { 1335 | msg_flags = PURPLE_MESSAGE_SEND; 1336 | 1337 | const gchar *wid = json_object_get_string_member(message, "wid"); 1338 | if (wid && g_hash_table_remove(ia->sent_messages_hash, wid)) { 1339 | // We sent this message from Pidgin 1340 | continue; 1341 | } 1342 | } 1343 | 1344 | gchar *escaped_text = purple_markup_escape_text(text, -1); 1345 | 1346 | if (g_str_has_suffix(sn, "@chat.agent")) { 1347 | // Group chat 1348 | JsonObject *chat = json_object_get_object_member(message, "chat"); 1349 | const gchar *sender = json_object_get_string_member(chat, "sender"); 1350 | const gchar *chatName = json_object_get_string_member(chat, "name"); 1351 | JsonObject *memberEvent = json_object_get_object_member(chat, "memberEvent"); 1352 | 1353 | if (memberEvent != NULL) { 1354 | PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(sn, ia->account); 1355 | const gchar *memberEventType = json_object_get_string_member(memberEvent, "type"); 1356 | if (purple_strequal(memberEventType, "invite") || purple_strequal(memberEventType, "addMembers")) { 1357 | JsonArray *members = json_object_get_array_member(memberEvent, "members"); 1358 | //add members to the group chat 1359 | const gchar *role = json_object_get_string_member(memberEvent, "role"); 1360 | PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE; 1361 | if (purple_strequal(role, "admin")) { 1362 | cbflags = PURPLE_CHAT_USER_OP; 1363 | } else if (purple_strequal(role, "moder")) { 1364 | cbflags = PURPLE_CHAT_USER_HALFOP; 1365 | } 1366 | 1367 | GList *users = NULL, *flags = NULL; 1368 | int j; 1369 | for (j = json_array_get_length(members) - 1; j >= 0; j--) { 1370 | const gchar *member = json_array_get_string_element(members, j); 1371 | 1372 | users = g_list_prepend(users, g_strdup(member)); 1373 | flags = g_list_prepend(flags, GINT_TO_POINTER(cbflags)); 1374 | } 1375 | 1376 | purple_chat_conversation_add_users(chatconv, users, NULL, flags, TRUE); 1377 | while (users != NULL) { 1378 | g_free(users->data); 1379 | users = g_list_delete_link(users, users); 1380 | } 1381 | g_list_free(flags); 1382 | 1383 | } else if (purple_strequal(memberEventType, "delMembers")) { 1384 | JsonArray *members = json_object_get_array_member(memberEvent, "members"); 1385 | //remove members from the group chat 1386 | GList *users = NULL; 1387 | int j; 1388 | for (j = json_array_get_length(members) - 1; j >= 0; j--) { 1389 | const gchar *member = json_array_get_string_element(members, j); 1390 | 1391 | users = g_list_prepend(users, g_strdup(member)); 1392 | } 1393 | 1394 | purple_chat_conversation_remove_users(chatconv, users, NULL); 1395 | while (users != NULL) { 1396 | g_free(users->data); 1397 | users = g_list_delete_link(users, users); 1398 | } 1399 | } 1400 | 1401 | 1402 | } else if (purple_strequal(mediaType, "text")) { 1403 | PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(sn, ia->account); 1404 | if (chatconv == NULL) { 1405 | chatconv = purple_serv_got_joined_chat(ia->pc, g_str_hash(sn), sn); 1406 | purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "sn", g_strdup(sn)); 1407 | purple_chat_conversation_set_topic(chatconv, NULL, chatName); 1408 | } 1409 | 1410 | purple_serv_got_chat_in(ia->pc, g_str_hash(sn), sender, msg_flags, escaped_text, time); 1411 | 1412 | } else { 1413 | purple_debug_warning("icyque", "Unknown chat message mediaType '%s'\n", mediaType); 1414 | } 1415 | 1416 | } else { 1417 | // One-to-one IM 1418 | if (purple_strequal(mediaType, "text")) { 1419 | if (msg_flags & PURPLE_MESSAGE_SEND) { 1420 | PurpleIMConversation *imconv = purple_conversations_find_im_with_account(sn, ia->account); 1421 | PurpleMessage *msgObj = purple_message_new_outgoing(sn, escaped_text, msg_flags); 1422 | if (imconv == NULL) 1423 | { 1424 | imconv = purple_im_conversation_new(ia->account, sn); 1425 | } 1426 | purple_message_set_time(msgObj, time); 1427 | purple_conversation_write_message(PURPLE_CONVERSATION(imconv), msgObj); 1428 | 1429 | } else { 1430 | purple_serv_got_im(ia->pc, sn, escaped_text, msg_flags, (time_t) time); 1431 | } 1432 | } else { 1433 | purple_debug_warning("icyque", "Unknown IM message mediaType '%s'\n", mediaType); 1434 | } 1435 | } 1436 | 1437 | g_free(escaped_text); 1438 | } 1439 | 1440 | ia->last_message_timestamp = MAX(ia->last_message_timestamp, time); 1441 | purple_account_set_int(ia->account, "last_message_timestamp_high", ia->last_message_timestamp >> 32); 1442 | purple_account_set_int(ia->account, "last_message_timestamp_low", ia->last_message_timestamp & 0xFFFFFFFF); 1443 | } 1444 | } 1445 | } else if (purple_strequal(event_type, "userAddedToBuddyList")) { 1446 | /*{ 1447 | "requester": "123456789", 1448 | "displayAIMid": "Person Name", 1449 | "authRequested": 0 1450 | }, */ 1451 | 1452 | } else if (purple_strequal(event_type, "buddylist")) { 1453 | JsonArray *groups = json_object_get_array_member(data, "groups"); 1454 | guint i, len = json_array_get_length(groups); 1455 | 1456 | for (i = 0; i < len; i++) { 1457 | JsonObject *group = json_array_get_object_element(groups, i); 1458 | const gchar *group_name = json_object_get_string_member(group, "name"); 1459 | PurpleGroup *pgroup = icq_get_or_create_default_group(group_name); 1460 | JsonArray *buddies = json_object_get_array_member(group, "buddies"); 1461 | guint j, buddies_len = json_array_get_length(buddies); 1462 | 1463 | for (j = 0; j < buddies_len; j++) { 1464 | JsonObject *buddy = json_array_get_object_element(buddies, j); 1465 | const gchar *aimId = json_object_get_string_member(buddy, "aimId"); 1466 | const gchar *state = json_object_get_string_member(buddy, "state"); 1467 | const gchar *statusMsg = json_object_get_string_member(buddy, "statusMsg"); 1468 | 1469 | if (g_str_has_suffix(aimId, "@chat.agent")) { 1470 | // Group chat 1471 | PurpleChat *chat = purple_blist_find_chat(ia->account, aimId); 1472 | 1473 | if (chat == NULL) { 1474 | const gchar *friendly = json_object_get_string_member(buddy, "friendly"); 1475 | chat = purple_chat_new(ia->account, friendly, icq_chat_info_defaults(ia->pc, aimId)); 1476 | 1477 | purple_blist_add_chat(chat, pgroup, NULL); 1478 | } 1479 | 1480 | } else { 1481 | // Buddy 1482 | PurpleBuddy *pbuddy = purple_blist_find_buddy(ia->account, aimId); 1483 | 1484 | if (pbuddy == NULL) { 1485 | const gchar *friendly = json_object_get_string_member(buddy, "friendly"); 1486 | pbuddy = purple_buddy_new(ia->account, aimId, friendly); 1487 | 1488 | purple_blist_add_buddy(pbuddy, NULL, pgroup, NULL); 1489 | } 1490 | 1491 | if (statusMsg != NULL) { 1492 | purple_protocol_got_user_status(ia->account, aimId, state, "message", statusMsg, NULL); 1493 | } else { 1494 | purple_protocol_got_user_status(ia->account, aimId, state, NULL); 1495 | } 1496 | } 1497 | } 1498 | } 1499 | 1500 | } 1501 | } 1502 | 1503 | static void 1504 | icq_fetch_events_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1505 | { 1506 | if (!ICYQUE_ACCOUNT_IS_VALID(ia)) return; 1507 | 1508 | if (obj == NULL) { 1509 | icq_fetch_url_with_method(ia, "GET", ia->last_fetchBaseURL, NULL, icq_fetch_events_cb, NULL); 1510 | return; 1511 | } 1512 | 1513 | JsonObject *response = json_object_get_object_member(obj, "response"); 1514 | JsonObject *data = json_object_get_object_member(response, "data"); 1515 | 1516 | const gchar *fetchBaseURL = json_object_get_string_member(data, "fetchBaseURL"); 1517 | JsonArray *events = json_object_get_array_member(data, "events"); 1518 | guint i, len = json_array_get_length(events); 1519 | for (i = 0; i < len; i++) { 1520 | JsonObject *event = json_array_get_object_element(events, i); 1521 | const gchar *type = json_object_get_string_member(event, "type"); 1522 | JsonObject *eventData = json_object_get_object_member(event, "eventData"); 1523 | 1524 | icq_process_event(ia, type, eventData); 1525 | } 1526 | 1527 | g_free(ia->last_fetchBaseURL); 1528 | ia->last_fetchBaseURL = g_strdup(fetchBaseURL); 1529 | 1530 | icq_fetch_url_with_method(ia, "GET", fetchBaseURL, NULL, icq_fetch_events_cb, NULL); 1531 | } 1532 | 1533 | static void 1534 | icq_robusto_add_client_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1535 | { 1536 | JsonObject *status = json_object_get_object_member(obj, "status"); 1537 | if(!status || json_object_get_int_member(status, "code") != 20000) { 1538 | purple_connection_error(ia->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Failed to register client at robusto API."); 1539 | } else { 1540 | JsonObject *results = json_object_get_object_member(obj, "results"); 1541 | guint64 clientId = json_object_get_int_member(results, "clientId"); 1542 | ia->robusto_client_id = clientId; 1543 | 1544 | purple_connection_set_state(ia->pc, PURPLE_CONNECTION_CONNECTED); 1545 | purple_debug_info("icyque", "Authentication succeeded. Starting fetch-loop.\n"); 1546 | icq_fetch_url_with_method(ia, "GET", ia->last_fetchBaseURL, NULL, icq_fetch_events_cb, NULL); 1547 | } 1548 | } 1549 | 1550 | static void icq_robusto_add_client(IcyQueAccount *ia) 1551 | { 1552 | JsonObject *addClientParams = json_object_new(); 1553 | JsonObject *addClientParamsUserAgent = json_object_new(); 1554 | json_object_set_object_member(addClientParams, "ua", addClientParamsUserAgent); 1555 | json_object_set_string_member(addClientParamsUserAgent, "app", "icq"); //TODO pretend to be official client or Pidgin? 1556 | json_object_set_string_member(addClientParamsUserAgent, "build", "1"); 1557 | json_object_set_string_member(addClientParamsUserAgent, "label", "webicq"); 1558 | json_object_set_string_member(addClientParamsUserAgent, "os", "win"); 1559 | json_object_set_string_member(addClientParamsUserAgent, "version", "0.1"); 1560 | 1561 | JsonObject *addClientRequest = icq_generate_robusto_request(ia, "addClient", addClientParams); 1562 | 1563 | const gchar* addClientRequestStr = json_object_to_string(addClientRequest); 1564 | json_object_unref(addClientRequest); 1565 | 1566 | purple_debug_info("icyque", "Registering client at ICQ RAPI.\n"); 1567 | icq_fetch_url_with_method(ia, "POST", ICQ_RAPI_SERVER, addClientRequestStr, icq_robusto_add_client_cb, NULL); 1568 | } 1569 | 1570 | static void 1571 | icq_robusto_gen_token_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1572 | { 1573 | JsonObject *results = json_object_get_object_member(obj, "results"); 1574 | const gchar *authToken = json_object_get_string_member(results, "authToken"); 1575 | 1576 | if(authToken == NULL) { 1577 | purple_connection_error(ia->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Failed to acquire authentication token (robusto)."); 1578 | } else { 1579 | ia->robusto_token = g_strdup(authToken); 1580 | icq_robusto_add_client(ia); 1581 | } 1582 | } 1583 | 1584 | static void icq_robusto_gen_token(IcyQueAccount *ia) 1585 | { 1586 | const gchar *url = ICQ_RAPI_SERVER "/genToken"; 1587 | GString *postdata = g_string_new(NULL); 1588 | 1589 | // Make sure these are added alphabetically for the signature to work 1590 | g_string_append_printf(postdata, "a=%s&", purple_url_encode(ia->token)); 1591 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 1592 | g_string_append_printf(postdata, "ts=%d", (int)(time(NULL) - ia->server_time_offset)); 1593 | 1594 | gchar *sig_sha256 = icq_get_url_sign(ia, TRUE, url, postdata->str); 1595 | g_string_append_printf(postdata, "&sig_sha256=%s", purple_url_encode(sig_sha256)); 1596 | g_free(sig_sha256); 1597 | 1598 | icq_fetch_url_with_method(ia, "POST", url, postdata->str, icq_robusto_gen_token_cb, NULL); 1599 | g_string_free(postdata, TRUE); 1600 | } 1601 | 1602 | static void 1603 | icq_session_start_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1604 | { 1605 | if (!ICYQUE_ACCOUNT_IS_VALID(ia)) return; 1606 | 1607 | JsonObject *response = json_object_get_object_member(obj, "response"); 1608 | JsonObject *data = json_object_get_object_member(response, "data"); 1609 | 1610 | const gchar *aimsid = json_object_get_string_member(data, "aimsid"); 1611 | const gchar *fetchBaseURL = json_object_get_string_member(data, "fetchBaseURL"); 1612 | 1613 | ia->aimsid = g_strdup(aimsid); 1614 | ia->last_fetchBaseURL = g_strdup(fetchBaseURL); 1615 | 1616 | icq_robusto_gen_token(ia); 1617 | } 1618 | 1619 | static void 1620 | icq_session_start(IcyQueAccount *ia) 1621 | { 1622 | if (!ICYQUE_ACCOUNT_IS_VALID(ia)) return; 1623 | 1624 | GString *url = g_string_new(ICQ_API14_SERVER "/aim/startSession?"); 1625 | 1626 | g_string_append_printf(url, "a=%s&", purple_url_encode(ia->token)); 1627 | g_string_append_printf(url, "ts=%d&", (int)(time(NULL) - ia->server_time_offset)); 1628 | g_string_append_printf(url, "k=%s&", purple_url_encode(ICQ_DEVID)); 1629 | g_string_append(url, "view=online&"); 1630 | g_string_append(url, "clientName=webicq&"); 1631 | g_string_append(url, "language=en-US&"); 1632 | g_string_append_printf(url, "deviceId=%s&", purple_url_encode(ia->device_id)); 1633 | g_string_append(url, "sessionTimeout=31536000&"); 1634 | g_string_append_printf(url, "assertCaps=%s&", purple_url_encode(ICQ_ASSERT_CAPS)); 1635 | g_string_append(url, "interestCaps=&"); 1636 | g_string_append_printf(url, "events=%s&", purple_url_encode(ICQ_EVENTS)); 1637 | g_string_append_printf(url, "includePresenceFields=%s", purple_url_encode(ICQ_PRESENCE_FIELDS)); 1638 | 1639 | icq_fetch_url_with_method(ia, "POST", url->str, NULL, icq_session_start_cb, NULL); 1640 | 1641 | g_string_free(url, TRUE); 1642 | } 1643 | 1644 | static void 1645 | icq_login_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1646 | { 1647 | JsonObject *response = json_object_get_object_member(obj, "response"); 1648 | 1649 | if (json_object_get_int_member(response, "statusCode") == 200) { 1650 | JsonObject *data = json_object_get_object_member(response, "data"); 1651 | JsonObject *token = json_object_get_object_member(data, "token"); 1652 | const gchar *a = json_object_get_string_member(token, "a"); 1653 | const gchar *loginId = json_object_get_string_member(data, "loginId"); 1654 | const gchar *sessionSecret = json_object_get_string_member(data, "sessionSecret"); 1655 | const gint64 hostTime = json_object_get_int_member(data, "hostTime"); 1656 | 1657 | if (a != NULL) { 1658 | ia->token = g_strdup(purple_url_decode(a)); 1659 | ia->session_key = icq_generate_signature(sessionSecret, purple_connection_get_password(ia->pc)); 1660 | ia->server_time_offset = (gint64)time(NULL) - hostTime; 1661 | purple_connection_set_display_name(ia->pc, loginId); 1662 | 1663 | purple_account_set_string(ia->account, "token", ia->token); 1664 | purple_account_set_string(ia->account, "session_key", ia->session_key); 1665 | purple_account_set_int(ia->account, "server_time_offset", ia->server_time_offset); 1666 | 1667 | icq_session_start(ia); 1668 | 1669 | return; 1670 | } 1671 | } 1672 | 1673 | purple_connection_error(ia->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Bad username/password")); 1674 | } 1675 | 1676 | static void 1677 | icq_mfa_text_entry_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1678 | { 1679 | JsonObject *response = json_object_get_object_member(obj, "response"); 1680 | 1681 | if (json_object_get_int_member(response, "statusCode") == 200) { 1682 | JsonObject *data = json_object_get_object_member(response, "data"); 1683 | JsonObject *token = json_object_get_object_member(data, "token"); 1684 | const gchar *a = json_object_get_string_member(token, "a"); 1685 | const gchar *loginId = json_object_get_string_member(data, "loginId"); 1686 | const gchar *sessionKey = json_object_get_string_member(data, "sessionKey"); 1687 | 1688 | if (a != NULL) { 1689 | ia->token = g_strdup(purple_url_decode(a)); 1690 | ia->session_key = g_strdup(sessionKey); 1691 | purple_connection_set_display_name(ia->pc, loginId); 1692 | 1693 | purple_account_set_string(ia->account, "token", ia->token); 1694 | purple_account_set_string(ia->account, "session_key", ia->session_key); 1695 | 1696 | icq_session_start(ia); 1697 | 1698 | return; 1699 | } 1700 | } 1701 | 1702 | purple_connection_error(ia->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Bad username/password")); 1703 | } 1704 | 1705 | static void 1706 | icq_mfa_text_entry(gpointer user_data, const gchar *code) 1707 | { 1708 | IcyQueAccount *ia = user_data; 1709 | const gchar *username = purple_account_get_username(ia->account); 1710 | GString *postdata = g_string_new(NULL); 1711 | 1712 | g_string_append_printf(postdata, "msisdn=%s&", purple_url_encode(&username[1])); 1713 | g_string_append_printf(postdata, "trans_id=%s&", purple_url_encode(ia->sms_trans_id)); 1714 | g_string_append_printf(postdata, "sms_code=%s&", purple_url_encode(code)); 1715 | g_string_append(postdata, "locale=en&"); 1716 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 1717 | g_string_append(postdata, "platform=web&"); 1718 | g_string_append(postdata, "create_account=1&"); 1719 | g_string_append(postdata, "client=icq&"); 1720 | g_string_append_printf(postdata, "r=%d&", g_random_int()); 1721 | 1722 | icq_fetch_url_with_method(ia, "POST", "https://u.icq.net/smsreg/loginWithPhoneNumber.php", postdata->str, icq_mfa_text_entry_cb, NULL); 1723 | 1724 | g_string_free(postdata, TRUE); 1725 | 1726 | g_free(ia->sms_trans_id); 1727 | ia->sms_trans_id = NULL; 1728 | } 1729 | 1730 | static void 1731 | icq_mfa_cancel(gpointer user_data) 1732 | { 1733 | IcyQueAccount *ia = user_data; 1734 | 1735 | purple_connection_error(ia->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Cancelled 2FA auth")); 1736 | } 1737 | 1738 | static void 1739 | icq_sms_login_cb(IcyQueAccount *ia, JsonObject *obj, gpointer user_data) 1740 | { 1741 | JsonObject *response = json_object_get_object_member(obj, "response"); 1742 | 1743 | if (json_object_get_int_member(response, "statusCode") == 200) { 1744 | JsonObject *data = json_object_get_object_member(response, "data"); 1745 | const gchar *trans_id = json_object_get_string_member(data, "trans_id"); 1746 | ia->sms_trans_id = g_strdup(trans_id); 1747 | 1748 | purple_request_input(ia->pc, _("Two-factor authentication"), 1749 | _("Enter SMS code"), 1750 | _("You will be sent an SMS message containing your auth code."), 1751 | NULL, FALSE, FALSE, "", 1752 | _("_Login"), G_CALLBACK(icq_mfa_text_entry), 1753 | _("_Cancel"), G_CALLBACK(icq_mfa_cancel), 1754 | purple_request_cpar_from_connection(ia->pc), 1755 | ia); 1756 | } 1757 | } 1758 | 1759 | static void 1760 | icq_login(PurpleAccount *account) 1761 | { 1762 | IcyQueAccount *ia; 1763 | PurpleConnection *pc = purple_account_get_connection(account); 1764 | 1765 | ia = g_new0(IcyQueAccount, 1); 1766 | purple_connection_set_protocol_data(pc, ia); 1767 | ia->account = account; 1768 | ia->pc = pc; 1769 | ia->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); 1770 | ia->user_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); 1771 | ia->sent_messages_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 1772 | ia->device_id = g_strdup(purple_account_get_string(ia->account, "device_id", NULL)); 1773 | ia->keepalive_pool = purple_http_keepalive_pool_new(); 1774 | ia->token = g_strdup(purple_account_get_string(ia->account, "token", NULL)); 1775 | ia->session_key = g_strdup(purple_account_get_string(ia->account, "session_key", NULL)); 1776 | ia->server_time_offset = purple_account_get_int(ia->account, "server_time_offset", 0); 1777 | ia->robusto_client_id = -1; 1778 | 1779 | if (ia->device_id == NULL) { 1780 | //TODO pretend to be official client or Pidgin? 1781 | ia->device_id = g_strdup_printf("icq-%08x%08x", g_random_int(), g_random_int()); 1782 | purple_account_set_string(ia->account, "device_id", ia->device_id); 1783 | } 1784 | 1785 | ia->last_message_timestamp = purple_account_get_int(account, "last_message_timestamp_high", 0); 1786 | 1787 | if (ia->last_message_timestamp != 0) { 1788 | ia->last_message_timestamp = (ia->last_message_timestamp << 32) | ((guint64) purple_account_get_int(account, "last_message_timestamp_low", 0) & 0xFFFFFFFF); 1789 | } 1790 | 1791 | valid_icyque_accounts = g_list_append(valid_icyque_accounts, ia); 1792 | 1793 | 1794 | if (ia->token == NULL) { 1795 | const gchar *username = purple_account_get_username(account); 1796 | GString *postdata = g_string_new(NULL); 1797 | 1798 | if (username[0] != '+') { 1799 | //TODO do we pretend to be an official device? 1800 | g_string_append_printf(postdata, "clientName=%s&", purple_url_encode("ICQ")); 1801 | g_string_append_printf(postdata, "clientVersion=%s&", purple_url_encode("7.4")); 1802 | g_string_append_printf(postdata, "devId=%s&", purple_url_encode(ICQ_DEVID)); 1803 | g_string_append(postdata, "f=json&"); 1804 | g_string_append(postdata, "idType=ICQ&"); 1805 | g_string_append_printf(postdata, "pwd=%s&", purple_url_encode(purple_connection_get_password(pc))); 1806 | g_string_append_printf(postdata, "s=%s&", purple_url_encode(username)); 1807 | 1808 | icq_fetch_url_with_method(ia, "POST", "https://api.login.icq.net/auth/clientLogin", postdata->str, icq_login_cb, NULL); 1809 | 1810 | } else { 1811 | // Phone/SMS auth 1812 | g_string_append_printf(postdata, "msisdn=%s&", purple_url_encode(&username[1])); 1813 | g_string_append(postdata, "locale=en&"); 1814 | g_string_append(postdata, "countryCode=ru&"); 1815 | g_string_append_printf(postdata, "k=%s&", purple_url_encode(ICQ_DEVID)); 1816 | g_string_append(postdata, "version=1&"); 1817 | g_string_append(postdata, "platform=web&"); 1818 | g_string_append(postdata, "client=icq&"); 1819 | g_string_append(postdata, "checks=sms&"); 1820 | g_string_append_printf(postdata, "r=%d&", g_random_int()); 1821 | 1822 | icq_fetch_url_with_method(ia, "POST", "https://u.icq.net/smsreg/requestPhoneValidation.php", postdata->str, icq_sms_login_cb, NULL); 1823 | 1824 | } 1825 | 1826 | g_string_free(postdata, TRUE); 1827 | } else { 1828 | icq_session_start(ia); 1829 | } 1830 | 1831 | purple_connection_set_state(pc, PURPLE_CONNECTION_CONNECTING); 1832 | } 1833 | 1834 | 1835 | static void 1836 | icq_close(PurpleConnection *pc) 1837 | { 1838 | IcyQueAccount *ia = purple_connection_get_protocol_data(pc); 1839 | 1840 | g_return_if_fail(ia != NULL); 1841 | 1842 | if (ia->heartbeat_timeout) { 1843 | g_source_remove(ia->heartbeat_timeout); 1844 | } 1845 | 1846 | valid_icyque_accounts = g_list_remove(valid_icyque_accounts, ia); 1847 | 1848 | while (ia->http_conns) { 1849 | purple_http_conn_cancel(ia->http_conns->data); 1850 | ia->http_conns = g_slist_delete_link(ia->http_conns, ia->http_conns); 1851 | } 1852 | 1853 | purple_http_keepalive_pool_unref(ia->keepalive_pool); 1854 | 1855 | // Save cookies to accounts.xml to login with later 1856 | gchar *cookies = icq_cookies_to_string(ia); 1857 | purple_account_set_string(ia->account, "cookies", cookies); 1858 | g_free(cookies); 1859 | g_hash_table_destroy(ia->cookie_table); 1860 | ia->cookie_table = NULL; 1861 | g_hash_table_destroy(ia->sent_messages_hash); 1862 | ia->sent_messages_hash = NULL; 1863 | g_hash_table_destroy(ia->user_ids); 1864 | ia->user_ids = NULL; 1865 | 1866 | g_free(ia->last_fetchBaseURL); 1867 | g_free(ia->token); 1868 | g_free(ia->session_key); 1869 | g_free(ia->aimsid); 1870 | g_free(ia->robusto_token); 1871 | g_free(ia->sms_trans_id); 1872 | 1873 | g_free(ia); 1874 | } 1875 | 1876 | static PurpleCmdRet 1877 | icq_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data) 1878 | { 1879 | PurpleConnection *pc = NULL; 1880 | int id = -1; 1881 | 1882 | pc = purple_conversation_get_connection(conv); 1883 | id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv)); 1884 | 1885 | if (pc == NULL || id == -1) 1886 | return PURPLE_CMD_RET_FAILED; 1887 | 1888 | icq_chat_leave(pc, id); 1889 | 1890 | return PURPLE_CMD_RET_OK; 1891 | } 1892 | 1893 | static PurpleCmdRet 1894 | icq_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data) 1895 | { 1896 | PurpleConnection *pc = NULL; 1897 | int id = -1; 1898 | 1899 | pc = purple_conversation_get_connection(conv); 1900 | id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv)); 1901 | 1902 | if (pc == NULL || id == -1) 1903 | return PURPLE_CMD_RET_FAILED; 1904 | 1905 | icq_chat_kick(pc, id, args[0]); 1906 | 1907 | return PURPLE_CMD_RET_OK; 1908 | } 1909 | 1910 | static gboolean 1911 | plugin_load(PurplePlugin *plugin, GError **error) 1912 | { 1913 | purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT | 1914 | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, 1915 | "prpl-eionrobb-icyque", icq_cmd_leave, 1916 | _("leave: Leave the group chat"), NULL); 1917 | 1918 | purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT | 1919 | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, 1920 | "prpl-eionrobb-icyque", icq_cmd_kick, 1921 | _("kick : Kick a user from the room."), NULL); 1922 | 1923 | return TRUE; 1924 | } 1925 | 1926 | static gboolean 1927 | plugin_unload(PurplePlugin *plugin, GError **error) 1928 | { 1929 | purple_signals_disconnect_by_handle(plugin); 1930 | 1931 | return TRUE; 1932 | } 1933 | 1934 | /* Purple2 Plugin Load Functions */ 1935 | #if !PURPLE_VERSION_CHECK(3, 0, 0) 1936 | 1937 | // Normally set in core.c in purple3 1938 | void _purple_socket_init(void); 1939 | void _purple_socket_uninit(void); 1940 | 1941 | static gboolean 1942 | libpurple2_plugin_load(PurplePlugin *plugin) 1943 | { 1944 | _purple_socket_init(); 1945 | purple_http_init(); 1946 | 1947 | return plugin_load(plugin, NULL); 1948 | } 1949 | 1950 | static gboolean 1951 | libpurple2_plugin_unload(PurplePlugin *plugin) 1952 | { 1953 | _purple_socket_uninit(); 1954 | purple_http_uninit(); 1955 | 1956 | return plugin_unload(plugin, NULL); 1957 | } 1958 | 1959 | static void 1960 | plugin_init(PurplePlugin *plugin) 1961 | { 1962 | PurplePluginInfo *info; 1963 | PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1); 1964 | 1965 | info = plugin->info; 1966 | 1967 | if (info == NULL) { 1968 | plugin->info = info = g_new0(PurplePluginInfo, 1); 1969 | } 1970 | 1971 | info->extra_info = prpl_info; 1972 | #if PURPLE_MINOR_VERSION >= 5 1973 | prpl_info->struct_size = sizeof(PurplePluginProtocolInfo); 1974 | #endif 1975 | #if PURPLE_MINOR_VERSION >= 8 1976 | prpl_info->add_buddy_with_invite = icq_add_buddy_with_invite; 1977 | #endif 1978 | 1979 | prpl_info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_INVITE_MESSAGE | OPT_PROTO_PASSWORD_OPTIONAL; 1980 | // prpl_info->protocol_options = icyque_add_account_options(prpl_info->protocol_options); 1981 | prpl_info->icon_spec.format = "png,gif,jpeg"; 1982 | prpl_info->icon_spec.min_width = 0; 1983 | prpl_info->icon_spec.min_height = 0; 1984 | prpl_info->icon_spec.max_width = 96; 1985 | prpl_info->icon_spec.max_height = 96; 1986 | prpl_info->icon_spec.max_filesize = 0; 1987 | prpl_info->icon_spec.scale_rules = PURPLE_ICON_SCALE_DISPLAY; 1988 | 1989 | // prpl_info->get_account_text_table = icyque_get_account_text_table; 1990 | // prpl_info->list_emblem = icyque_list_emblem; 1991 | prpl_info->status_text = icq_status_text; 1992 | prpl_info->tooltip_text = icq_tooltip_text; 1993 | prpl_info->list_icon = icq_list_icon; 1994 | prpl_info->set_status = icq_set_status; 1995 | // prpl_info->set_idle = icyque_set_idle; 1996 | prpl_info->status_types = icq_status_types; 1997 | prpl_info->chat_info = icq_chat_info; 1998 | prpl_info->chat_info_defaults = icq_chat_info_defaults; 1999 | prpl_info->login = icq_login; 2000 | prpl_info->close = icq_close; 2001 | prpl_info->send_im = icq_send_im; 2002 | prpl_info->send_typing = icq_send_typing; 2003 | prpl_info->join_chat = icq_join_chat; 2004 | prpl_info->get_chat_name = icq_get_chat_name; 2005 | // prpl_info->find_blist_chat = icyque_find_chat; 2006 | prpl_info->chat_invite = icq_chat_invite; 2007 | prpl_info->chat_send = icq_chat_send; 2008 | // prpl_info->set_chat_topic = icyque_chat_set_topic; 2009 | // prpl_info->get_cb_real_name = icyque_get_real_name; 2010 | prpl_info->add_buddy = icq_add_buddy; 2011 | // prpl_info->remove_buddy = icyque_buddy_remove; 2012 | // prpl_info->group_buddy = icyque_fake_group_buddy; 2013 | // prpl_info->rename_group = icyque_fake_group_rename; 2014 | prpl_info->get_info = icq_get_info; 2015 | prpl_info->add_deny = icq_block_user; 2016 | prpl_info->rem_deny = icq_unblock_user; 2017 | 2018 | // prpl_info->roomlist_get_list = icyque_roomlist_get_list; 2019 | // prpl_info->roomlist_room_serialize = icyque_roomlist_serialize; 2020 | } 2021 | 2022 | static PurplePluginInfo info = { 2023 | PURPLE_PLUGIN_MAGIC, 2024 | /* PURPLE_MAJOR_VERSION, 2025 | PURPLE_MINOR_VERSION, 2026 | */ 2027 | 2, 1, 2028 | PURPLE_PLUGIN_PROTOCOL, /* type */ 2029 | NULL, /* ui_requirement */ 2030 | 0, /* flags */ 2031 | NULL, /* dependencies */ 2032 | PURPLE_PRIORITY_DEFAULT, /* priority */ 2033 | "prpl-eionrobb-icyque", /* id */ 2034 | "ICQ (WIM)", /* name */ 2035 | "0.1", /* version */ 2036 | "", /* summary */ 2037 | "", /* description */ 2038 | "Eion Robb ", /* author */ 2039 | "", /* homepage */ 2040 | libpurple2_plugin_load, /* load */ 2041 | libpurple2_plugin_unload, /* unload */ 2042 | NULL, /* destroy */ 2043 | NULL, /* ui_info */ 2044 | NULL, /* extra_info */ 2045 | NULL, /* prefs_info */ 2046 | NULL, /* actions */ 2047 | NULL, /* padding */ 2048 | NULL, 2049 | NULL, 2050 | NULL 2051 | }; 2052 | 2053 | PURPLE_INIT_PLUGIN(icyque, plugin_init, info); 2054 | 2055 | #else 2056 | 2057 | G_MODULE_EXPORT GType icyque_protocol_get_type(void); 2058 | #define ICYQUE_TYPE_PROTOCOL (icyque_protocol_get_type()) 2059 | #define ICYQUE_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ICYQUE_TYPE_PROTOCOL, IcyQueProtocol)) 2060 | #define ICYQUE_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ICYQUE_TYPE_PROTOCOL, IcyQueProtocolClass)) 2061 | #define ICYQUE_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ICYQUE_TYPE_PROTOCOL)) 2062 | #define ICYQUE_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ICYQUE_TYPE_PROTOCOL)) 2063 | #define ICYQUE_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ICYQUE_TYPE_PROTOCOL, IcyQueProtocolClass)) 2064 | 2065 | typedef struct _IcyQueProtocol 2066 | { 2067 | PurpleProtocol parent; 2068 | } IcyQueProtocol; 2069 | 2070 | typedef struct _IcyQueProtocolClass 2071 | { 2072 | PurpleProtocolClass parent_class; 2073 | } IcyQueProtocolClass; 2074 | 2075 | static void 2076 | icyque_protocol_init(PurpleProtocol *prpl_info) 2077 | { 2078 | PurpleProtocol *info = prpl_info; 2079 | 2080 | info->id = "prpl-eionrobb-icyque"; 2081 | info->name = "ICQ (WIM)"; 2082 | } 2083 | 2084 | static void 2085 | icyque_protocol_class_init(PurpleProtocolClass *prpl_info) 2086 | { 2087 | prpl_info->login = icq_login; 2088 | prpl_info->close = icq_close; 2089 | prpl_info->status_types = icq_status_types; 2090 | prpl_info->list_icon = icq_list_icon; 2091 | } 2092 | 2093 | static void 2094 | icyque_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info) 2095 | { 2096 | prpl_info->status_text = icq_status_text; 2097 | prpl_info->tooltip_text = icq_tooltip_text; 2098 | //prpl_info->buddy_free = icyque_buddy_free; 2099 | //prpl_info->offline_message = icyque_offline_message; 2100 | } 2101 | 2102 | static void 2103 | icyque_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info) 2104 | { 2105 | prpl_info->get_info = icq_get_info; 2106 | prpl_info->set_status = icq_set_status; 2107 | //prpl_info->set_idle = icyque_set_idle; 2108 | prpl_info->add_buddy = icq_add_buddy_with_invite; 2109 | } 2110 | 2111 | static void 2112 | icyque_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info) 2113 | { 2114 | prpl_info->add_deny = icq_block_user; 2115 | prpl_info->rem_deny = icq_unblock_user; 2116 | } 2117 | 2118 | static void 2119 | icyque_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info) 2120 | { 2121 | prpl_info->send = icq_send_im; 2122 | prpl_info->send_typing = icq_send_typing; 2123 | } 2124 | 2125 | static void 2126 | icyque_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info) 2127 | { 2128 | prpl_info->send = icq_chat_send; 2129 | prpl_info->info = icq_chat_info; 2130 | prpl_info->info_defaults = icq_chat_info_defaults; 2131 | prpl_info->join = icq_join_chat; 2132 | prpl_info->get_name = icq_get_chat_name; 2133 | prpl_info->invite = icq_chat_invite; 2134 | //prpl_info->set_topic = icyque_chat_set_topic; 2135 | } 2136 | 2137 | static void 2138 | icyque_protocol_media_iface_init(PurpleProtocolMediaIface *prpl_info) 2139 | { 2140 | //prpl_info->get_caps = icyque_get_media_caps; 2141 | //prpl_info->initiate_session = icyque_initiate_media; 2142 | } 2143 | 2144 | static void 2145 | icyque_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info) 2146 | { 2147 | //prpl_info->get_list = icyque_roomlist_get_list; 2148 | } 2149 | 2150 | static PurpleProtocol *icyque_protocol; 2151 | 2152 | PURPLE_DEFINE_TYPE_EXTENDED( 2153 | IcyQueProtocol, icyque_protocol, PURPLE_TYPE_PROTOCOL, 0, 2154 | 2155 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE, 2156 | icyque_protocol_im_iface_init) 2157 | 2158 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE, 2159 | icyque_protocol_chat_iface_init) 2160 | 2161 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE, 2162 | icyque_protocol_client_iface_init) 2163 | 2164 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE, 2165 | icyque_protocol_server_iface_init) 2166 | 2167 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE, 2168 | icyque_protocol_privacy_iface_init) 2169 | 2170 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_MEDIA_IFACE, 2171 | icyque_protocol_media_iface_init) 2172 | 2173 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE, 2174 | icyque_protocol_roomlist_iface_init) 2175 | ); 2176 | 2177 | static gboolean 2178 | libpurple3_plugin_load(PurplePlugin *plugin, GError **error) 2179 | { 2180 | icyque_protocol_register_type(plugin); 2181 | icyque_protocol = purple_protocols_add(ICYQUE_TYPE_PROTOCOL, error); 2182 | if (!icyque_protocol) 2183 | return FALSE; 2184 | 2185 | return plugin_load(plugin, error); 2186 | } 2187 | 2188 | static gboolean 2189 | libpurple3_plugin_unload(PurplePlugin *plugin, GError **error) 2190 | { 2191 | if (!plugin_unload(plugin, error)) 2192 | return FALSE; 2193 | 2194 | if (!purple_protocols_remove(icyque_protocol, error)) 2195 | return FALSE; 2196 | 2197 | return TRUE; 2198 | } 2199 | 2200 | static PurplePluginInfo * 2201 | plugin_query(GError **error) 2202 | { 2203 | return purple_plugin_info_new( 2204 | "id", "prpl-eionrobb-icyque", 2205 | "name", "ICQ (WIM)", 2206 | "version", "0.1", 2207 | "category", N_("Protocol"), 2208 | "summary", N_("ICQ-WIM Protocol Plugin."), 2209 | "description", N_("Adds ICQ protocol support to libpurple."), 2210 | "website", "https://github.com/EionRobb/icyque/", 2211 | "abi-version", PURPLE_ABI_VERSION, 2212 | "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | 2213 | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD, 2214 | NULL 2215 | ); 2216 | } 2217 | 2218 | PURPLE_PLUGIN_INIT(icyque, plugin_query, 2219 | libpurple3_plugin_load, libpurple3_plugin_unload); 2220 | 2221 | #endif 2222 | -------------------------------------------------------------------------------- /purple2compat/ciphers/sha1hash.h: -------------------------------------------------------------------------------- 1 | #ifndef _CIPHERS_SHA1HASH_H_ 2 | #define _CIPHERS_SHA1HASH_H_ 3 | 4 | #include "cipher.h" 5 | 6 | #define purple_sha1_hash_new() purple_cipher_context_new(purple_ciphers_find_cipher("sha1"), NULL) 7 | 8 | #ifndef PurpleHash 9 | # define PurpleHash PurpleCipherContext 10 | # define purple_hash_append purple_cipher_context_append 11 | # define purple_hash_digest_to_str(ctx, data, size) \ 12 | purple_cipher_context_digest_to_str(ctx, size, data, NULL) 13 | # define purple_hash_digest(ctx, data, size) \ 14 | purple_cipher_context_digest(ctx, size, data, NULL) 15 | # define purple_hash_destroy purple_cipher_context_destroy 16 | #endif /*PurpleHash*/ 17 | 18 | #endif /*_CIPHERS_SHA1HASH_H_*/ 19 | -------------------------------------------------------------------------------- /purple2compat/circularbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _CIRCULARBUFFER_H_ 2 | #define _CIRCULARBUFFER_H_ 3 | 4 | #include "circbuffer.h" 5 | 6 | #define PurpleCircularBuffer PurpleCircBuffer 7 | #define purple_circular_buffer_new purple_circ_buffer_new 8 | #define purple_circular_buffer_destroy purple_circ_buffer_destroy 9 | #define purple_circular_buffer_append purple_circ_buffer_append 10 | #define purple_circular_buffer_get_max_read purple_circ_buffer_get_max_read 11 | #define purple_circular_buffer_mark_read purple_circ_buffer_mark_read 12 | #define purple_circular_buffer_get_output(buf) ((const gchar *) (buf)->outptr) 13 | #define purple_circular_buffer_get_used(buf) ((buf)->bufused) 14 | 15 | #endif /*_CIRCULARBUFFER_H_*/ 16 | -------------------------------------------------------------------------------- /purple2compat/glibcompat.h: -------------------------------------------------------------------------------- 1 | #include "../glibcompat.h" 2 | -------------------------------------------------------------------------------- /purple2compat/http.h: -------------------------------------------------------------------------------- 1 | /* purple 2 | * 3 | * Purple is the legal property of its developers, whose names are too numerous 4 | * to list here. Please refer to the COPYRIGHT file distributed with this 5 | * source distribution. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA 20 | */ 21 | 22 | #ifndef _PURPLE_HTTP_H_ 23 | #define _PURPLE_HTTP_H_ 24 | /** 25 | * SECTION:http 26 | * @section_id: libpurple-http 27 | * @short_description: http.h 28 | * @title: HTTP API 29 | */ 30 | 31 | #include 32 | 33 | #include "connection.h" 34 | 35 | /** 36 | * PurpleHttpRequest: 37 | * 38 | * A structure containing all data required to generate a single HTTP request. 39 | */ 40 | typedef struct _PurpleHttpRequest PurpleHttpRequest; 41 | 42 | /** 43 | * PurpleHttpConnection: 44 | * 45 | * A representation of actually running HTTP request. Can be used to cancel the 46 | * request. 47 | */ 48 | typedef struct _PurpleHttpConnection PurpleHttpConnection; 49 | 50 | /** 51 | * PurpleHttpResponse: 52 | * 53 | * All information got with response for HTTP request. 54 | */ 55 | typedef struct _PurpleHttpResponse PurpleHttpResponse; 56 | 57 | /** 58 | * PurpleHttpURL: 59 | * 60 | * Parsed representation for the URL. 61 | */ 62 | typedef struct _PurpleHttpURL PurpleHttpURL; 63 | 64 | /** 65 | * PurpleHttpCookieJar: 66 | * 67 | * An collection of cookies, got from HTTP response or provided for HTTP 68 | * request. 69 | */ 70 | typedef struct _PurpleHttpCookieJar PurpleHttpCookieJar; 71 | 72 | /** 73 | * PurpleHttpKeepalivePool: 74 | * 75 | * A pool of TCP connections for HTTP Keep-Alive session. 76 | */ 77 | typedef struct _PurpleHttpKeepalivePool PurpleHttpKeepalivePool; 78 | 79 | /** 80 | * PurpleHttpConnectionSet: 81 | * 82 | * A set of running HTTP requests. Can be used to cancel all of them at once. 83 | */ 84 | typedef struct _PurpleHttpConnectionSet PurpleHttpConnectionSet; 85 | 86 | /** 87 | * PurpleHttpCallback: 88 | * 89 | * An callback called after performing (successfully or not) HTTP request. 90 | */ 91 | typedef void (*PurpleHttpCallback)(PurpleHttpConnection *http_conn, 92 | PurpleHttpResponse *response, gpointer user_data); 93 | 94 | /** 95 | * PurpleHttpContentReaderCb: 96 | * 97 | * An callback called after storing data requested by PurpleHttpContentReader. 98 | */ 99 | typedef void (*PurpleHttpContentReaderCb)(PurpleHttpConnection *http_conn, 100 | gboolean success, gboolean eof, size_t stored); 101 | 102 | /** 103 | * PurpleHttpContentReader: 104 | * @http_conn: Connection, which requests data. 105 | * @buffer: Buffer to store data to (with offset ignored). 106 | * @offset: Position, from where to read data. 107 | * @length: Length of data to read. 108 | * @user_data: The user data passed with callback function. 109 | * @cb: The function to call after storing data to buffer. 110 | * 111 | * An callback for getting large request contents (ie. from file stored on 112 | * disk). 113 | */ 114 | typedef void (*PurpleHttpContentReader)(PurpleHttpConnection *http_conn, 115 | gchar *buffer, size_t offset, size_t length, gpointer user_data, 116 | PurpleHttpContentReaderCb cb); 117 | 118 | /** 119 | * PurpleHttpContentWriter: 120 | * @http_conn: Connection, which requests data. 121 | * @response: Response at point got so far (may change later). 122 | * @buffer: Buffer to read data from (with offset ignored). 123 | * @offset: Position of data got (its value is offset + length of 124 | * previous call), can be safely ignored. 125 | * @length: Length of data read. 126 | * @user_data: The user data passed with callback function. 127 | * 128 | * An callback for writting large response contents. 129 | * 130 | * Returns: TRUE, if succeeded, FALSE otherwise. 131 | */ 132 | typedef gboolean (*PurpleHttpContentWriter)(PurpleHttpConnection *http_conn, 133 | PurpleHttpResponse *response, const gchar *buffer, size_t offset, 134 | size_t length, gpointer user_data); 135 | 136 | /** 137 | * PurpleHttpProgressWatcher: 138 | * @http_conn: The HTTP Connection. 139 | * @reading_state: FALSE, is we are sending the request, TRUE, when reading 140 | * the response. 141 | * @processed: The amount of data already processed. 142 | * @total: Total amount of data (in current state). 143 | * @user_data: The user data passed with callback function. 144 | * 145 | * An callback for watching HTTP connection progress. 146 | */ 147 | typedef void (*PurpleHttpProgressWatcher)(PurpleHttpConnection *http_conn, 148 | gboolean reading_state, int processed, int total, gpointer user_data); 149 | 150 | G_BEGIN_DECLS 151 | 152 | /**************************************************************************/ 153 | /* Performing HTTP requests */ 154 | /**************************************************************************/ 155 | 156 | /** 157 | * purple_http_get: 158 | * @gc: The connection for which the request is needed, or NULL. 159 | * @callback: (scope call): The callback function. 160 | * @user_data: The user data to pass to the callback function. 161 | * @url: The URL. 162 | * 163 | * Fetches the data from a URL with GET request, and passes it to a callback 164 | * function. 165 | * 166 | * Returns: The HTTP connection struct. 167 | */ 168 | PurpleHttpConnection * purple_http_get(PurpleConnection *gc, 169 | PurpleHttpCallback callback, gpointer user_data, const gchar *url); 170 | 171 | /** 172 | * purple_http_get_printf: 173 | * @gc: The connection for which the request is needed, or NULL. 174 | * @callback: (scope call): The callback function. 175 | * @user_data: The user data to pass to the callback function. 176 | * @format: The format string. 177 | * 178 | * Constructs an URL and fetches the data from it with GET request, then passes 179 | * it to a callback function. 180 | * 181 | * Returns: The HTTP connection struct. 182 | */ 183 | PurpleHttpConnection * purple_http_get_printf(PurpleConnection *gc, 184 | PurpleHttpCallback callback, gpointer user_data, 185 | const gchar *format, ...) G_GNUC_PRINTF(4, 5); 186 | 187 | /** 188 | * purple_http_request: 189 | * @gc: The connection for which the request is needed, or NULL. 190 | * @request: The request. 191 | * @callback: (scope call): The callback function. 192 | * @user_data: The user data to pass to the callback function. 193 | * 194 | * Fetches a HTTP request and passes the response to a callback function. 195 | * Provided request struct can be shared by multiple http requests but can not 196 | * be modified when any of these is running. 197 | * 198 | * Returns: The HTTP connection struct. 199 | */ 200 | PurpleHttpConnection * purple_http_request(PurpleConnection *gc, 201 | PurpleHttpRequest *request, PurpleHttpCallback callback, 202 | gpointer user_data); 203 | 204 | /**************************************************************************/ 205 | /* HTTP connection API */ 206 | /**************************************************************************/ 207 | 208 | /** 209 | * purple_http_conn_cancel: 210 | * @http_conn: The data returned when you initiated the HTTP request. 211 | * 212 | * Cancel a pending HTTP request. 213 | */ 214 | void purple_http_conn_cancel(PurpleHttpConnection *http_conn); 215 | 216 | /** 217 | * purple_http_conn_cancel_all: 218 | * @gc: The handle. 219 | * 220 | * Cancels all HTTP connections associated with the specified handle. 221 | */ 222 | void purple_http_conn_cancel_all(PurpleConnection *gc); 223 | 224 | /** 225 | * purple_http_conn_is_running: 226 | * @http_conn: The HTTP connection (may be invalid pointer). 227 | * 228 | * Checks, if provided HTTP request is running. 229 | * 230 | * Returns: TRUE, if provided connection is currently running. 231 | */ 232 | gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn); 233 | 234 | /** 235 | * purple_http_conn_get_request: 236 | * @http_conn: The HTTP connection. 237 | * 238 | * Gets PurpleHttpRequest used for specified HTTP connection. 239 | * 240 | * Returns: The PurpleHttpRequest object. 241 | */ 242 | PurpleHttpRequest * purple_http_conn_get_request( 243 | PurpleHttpConnection *http_conn); 244 | 245 | /** 246 | * purple_http_conn_get_cookie_jar: 247 | * @http_conn: The HTTP connection. 248 | * 249 | * Gets cookie jar used within connection. 250 | * 251 | * Returns: The cookie jar. 252 | */ 253 | PurpleHttpCookieJar * purple_http_conn_get_cookie_jar( 254 | PurpleHttpConnection *http_conn); 255 | 256 | /** 257 | * purple_http_conn_get_purple_connection: 258 | * @http_conn: The HTTP connection. 259 | * 260 | * Gets PurpleConnection tied with specified HTTP connection. 261 | * 262 | * Returns: The PurpleConnection object. 263 | */ 264 | PurpleConnection * purple_http_conn_get_purple_connection( 265 | PurpleHttpConnection *http_conn); 266 | 267 | /** 268 | * purple_http_conn_set_progress_watcher: 269 | * @http_conn: The HTTP connection. 270 | * @watcher: (scope call): The watcher. 271 | * @user_data: The user data to pass to the callback function. 272 | * @interval_threshold: Minimum interval (in microseconds) of calls to 273 | * watcher, or -1 for default. 274 | * 275 | * Sets the watcher, called after writing or reading data to/from HTTP stream. 276 | * May be used for updating transfer progress gauge. 277 | */ 278 | void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn, 279 | PurpleHttpProgressWatcher watcher, gpointer user_data, 280 | gint interval_threshold); 281 | 282 | 283 | /**************************************************************************/ 284 | /* URL processing API */ 285 | /**************************************************************************/ 286 | 287 | /** 288 | * purple_http_url_parse: 289 | * @url: The URL to parse. 290 | * 291 | * Parses a URL. 292 | * 293 | * The returned data must be freed with purple_http_url_free. 294 | * 295 | * Returns: The parsed url or NULL, if the URL is invalid. 296 | */ 297 | PurpleHttpURL * 298 | purple_http_url_parse(const char *url); 299 | 300 | /** 301 | * purple_http_url_free: 302 | * @parsed_url: The parsed URL struct, or NULL. 303 | * 304 | * Frees the parsed URL struct. 305 | */ 306 | void 307 | purple_http_url_free(PurpleHttpURL *parsed_url); 308 | 309 | /** 310 | * purple_http_url_relative: 311 | * @base_url: The base URL. The result is stored here. 312 | * @relative_url: The relative URL. 313 | * 314 | * Converts the base URL to the absolute form of the provided relative URL. 315 | * 316 | * Example: "https://example.com/path/to/file.html" + "subdir/other-file.html" = 317 | * "https://example.com/path/to/subdir/another-file.html" 318 | */ 319 | void 320 | purple_http_url_relative(PurpleHttpURL *base_url, PurpleHttpURL *relative_url); 321 | 322 | /** 323 | * purple_http_url_print: 324 | * @parsed_url: The URL struct. 325 | * 326 | * Converts the URL struct to the printable form. The result may not be a valid 327 | * URL (in cases, when the struct doesn't have all fields filled properly). 328 | * 329 | * The result must be g_free'd. 330 | * 331 | * Returns: The printable form of the URL. 332 | */ 333 | gchar * 334 | purple_http_url_print(PurpleHttpURL *parsed_url); 335 | 336 | /** 337 | * purple_http_url_get_protocol: 338 | * @parsed_url: The URL struct. 339 | * 340 | * Gets the protocol part of URL. 341 | * 342 | * Returns: The protocol. 343 | */ 344 | const gchar * 345 | purple_http_url_get_protocol(const PurpleHttpURL *parsed_url); 346 | 347 | /** 348 | * purple_http_url_get_username: 349 | * @parsed_url: The URL struct. 350 | * 351 | * Gets the username part of URL. 352 | * 353 | * Returns: The username. 354 | */ 355 | const gchar * 356 | purple_http_url_get_username(const PurpleHttpURL *parsed_url); 357 | 358 | /** 359 | * purple_http_url_get_password: 360 | * @parsed_url: The URL struct. 361 | * 362 | * Gets the password part of URL. 363 | * 364 | * Returns: The password. 365 | */ 366 | const gchar * 367 | purple_http_url_get_password(const PurpleHttpURL *parsed_url); 368 | 369 | /** 370 | * purple_http_url_get_host: 371 | * @parsed_url: The URL struct. 372 | * 373 | * Gets the hostname part of URL. 374 | * 375 | * Returns: The hostname. 376 | */ 377 | const gchar * 378 | purple_http_url_get_host(const PurpleHttpURL *parsed_url); 379 | 380 | /** 381 | * purple_http_url_get_port: 382 | * @parsed_url: The URL struct. 383 | * 384 | * Gets the port part of URL. 385 | * 386 | * Returns: The port number. 387 | */ 388 | int 389 | purple_http_url_get_port(const PurpleHttpURL *parsed_url); 390 | 391 | /** 392 | * purple_http_url_get_path: 393 | * @parsed_url: The URL struct. 394 | * 395 | * Gets the path part of URL. 396 | * 397 | * Returns: The path. 398 | */ 399 | const gchar * 400 | purple_http_url_get_path(const PurpleHttpURL *parsed_url); 401 | 402 | /** 403 | * purple_http_url_get_fragment: 404 | * @parsed_url: The URL struct. 405 | * 406 | * Gets the fragment part of URL. 407 | * 408 | * Returns: The fragment. 409 | */ 410 | const gchar * 411 | purple_http_url_get_fragment(const PurpleHttpURL *parsed_url); 412 | 413 | 414 | /**************************************************************************/ 415 | /* Cookie jar API */ 416 | /**************************************************************************/ 417 | 418 | /** 419 | * purple_http_cookie_jar_new: 420 | * 421 | * Creates new cookie jar, 422 | * 423 | * Returns: empty cookie jar. 424 | */ 425 | PurpleHttpCookieJar * purple_http_cookie_jar_new(void); 426 | 427 | /** 428 | * purple_http_cookie_jar_ref: 429 | * @cookie_jar: The cookie jar. 430 | * 431 | * Increment the reference count. 432 | */ 433 | void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar); 434 | 435 | /** 436 | * purple_http_cookie_jar_unref: 437 | * @cookie_jar: The cookie jar. 438 | * 439 | * Decrement the reference count. 440 | * 441 | * If the reference count reaches zero, the cookie jar will be freed. 442 | * 443 | * Returns: @cookie_jar or %NULL if the reference count reached zero. 444 | */ 445 | PurpleHttpCookieJar * purple_http_cookie_jar_unref( 446 | PurpleHttpCookieJar *cookie_jar); 447 | 448 | /** 449 | * purple_http_cookie_jar_set: 450 | * @cookie_jar: The cookie jar. 451 | * @name: Cookie name. 452 | * @value: Cookie contents. 453 | * 454 | * Sets the cookie. 455 | */ 456 | void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar, 457 | const gchar *name, const gchar *value); 458 | 459 | /** 460 | * purple_http_cookie_jar_get: 461 | * @cookie_jar: The cookie jar. 462 | * @name: Cookie name. 463 | * 464 | * Gets the cookie. 465 | * 466 | * The result must be g_free'd. 467 | * 468 | * Returns: Cookie contents, or NULL, if cookie doesn't exists. 469 | */ 470 | gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar, 471 | const gchar *name); 472 | 473 | /** 474 | * purple_http_cookie_jar_is_empty: 475 | * @cookie_jar: The cookie jar. 476 | * 477 | * Checks, if the cookie jar contains any cookies. 478 | * 479 | * Returns: TRUE, if cookie jar contains any cookie, FALSE otherwise. 480 | */ 481 | gboolean purple_http_cookie_jar_is_empty(PurpleHttpCookieJar *cookie_jar); 482 | 483 | 484 | /**************************************************************************/ 485 | /* HTTP Request API */ 486 | /**************************************************************************/ 487 | 488 | /** 489 | * purple_http_request_new: 490 | * @url: The URL to request for, or NULL to leave empty (to be set with 491 | * purple_http_request_set_url). 492 | * 493 | * Creates the new instance of HTTP request configuration. 494 | * 495 | * Returns: The new instance of HTTP request struct. 496 | */ 497 | PurpleHttpRequest * purple_http_request_new(const gchar *url); 498 | 499 | /** 500 | * purple_http_request_ref: 501 | * @request: The request. 502 | * 503 | * Increment the reference count. 504 | */ 505 | void purple_http_request_ref(PurpleHttpRequest *request); 506 | 507 | /** 508 | * purple_http_request_unref: 509 | * @request: The request. 510 | * 511 | * Decrement the reference count. 512 | * 513 | * If the reference count reaches zero, the http request struct will be freed. 514 | * 515 | * Returns: @request or %NULL if the reference count reached zero. 516 | */ 517 | PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request); 518 | 519 | /** 520 | * purple_http_request_set_url: 521 | * @request: The request. 522 | * @url: The url. 523 | * 524 | * Sets URL for HTTP request. 525 | */ 526 | void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url); 527 | 528 | /** 529 | * purple_http_request_set_url_printf: 530 | * @request: The request. 531 | * @format: The format string. 532 | * 533 | * Constructs and sets an URL for HTTP request. 534 | */ 535 | void purple_http_request_set_url_printf(PurpleHttpRequest *request, 536 | const gchar *format, ...) G_GNUC_PRINTF(2, 3); 537 | 538 | /** 539 | * purple_http_request_get_url: 540 | * @request: The request. 541 | * 542 | * Gets URL set for the HTTP request. 543 | * 544 | * Returns: URL set for this request. 545 | */ 546 | const gchar * purple_http_request_get_url(PurpleHttpRequest *request); 547 | 548 | /** 549 | * purple_http_request_set_method: 550 | * @request: The request. 551 | * @method: The method, or NULL for default. 552 | * 553 | * Sets custom HTTP method used for the request. 554 | */ 555 | void purple_http_request_set_method(PurpleHttpRequest *request, 556 | const gchar *method); 557 | 558 | /** 559 | * purple_http_request_get_method: 560 | * @request: The request. 561 | * 562 | * Gets HTTP method set for the request. 563 | * 564 | * Returns: The method. 565 | */ 566 | const gchar * purple_http_request_get_method(PurpleHttpRequest *request); 567 | 568 | /** 569 | * purple_http_request_set_keepalive_pool: 570 | * @request: The request. 571 | * @pool: The new KeepAlive pool, or NULL to reset. 572 | * 573 | * Sets HTTP KeepAlive connections pool for the request. 574 | * 575 | * It increases pool's reference count. 576 | */ 577 | void 578 | purple_http_request_set_keepalive_pool(PurpleHttpRequest *request, 579 | PurpleHttpKeepalivePool *pool); 580 | 581 | /** 582 | * purple_http_request_get_keepalive_pool: 583 | * @request: The request. 584 | * 585 | * Gets HTTP KeepAlive connections pool associated with the request. 586 | * 587 | * It doesn't affect pool's reference count. 588 | * 589 | * Returns: The KeepAlive pool, used for the request. 590 | */ 591 | PurpleHttpKeepalivePool * 592 | purple_http_request_get_keepalive_pool(PurpleHttpRequest *request); 593 | 594 | /** 595 | * purple_http_request_set_contents: 596 | * @request: The request. 597 | * @contents: The contents. 598 | * @length: The length of contents (-1 if it's a NULL-terminated string) 599 | * 600 | * Sets contents of HTTP request (for example, POST data). 601 | */ 602 | void purple_http_request_set_contents(PurpleHttpRequest *request, 603 | const gchar *contents, int length); 604 | 605 | /** 606 | * purple_http_request_set_contents_reader: 607 | * @request: The request. 608 | * @reader: (scope call): The reader callback. 609 | * @contents_length: The size of all contents. 610 | * @user_data: The user data to pass to the callback function. 611 | * 612 | * Sets contents reader for HTTP request, used mainly for possible large 613 | * uploads. 614 | */ 615 | void purple_http_request_set_contents_reader(PurpleHttpRequest *request, 616 | PurpleHttpContentReader reader, int contents_length, gpointer user_data); 617 | 618 | /** 619 | * purple_http_request_set_response_writer: 620 | * @request: The request. 621 | * @writer: (scope call): The writer callback, or %NULL to remove existing. 622 | * @user_data: The user data to pass to the callback function. 623 | * 624 | * Set contents writer for HTTP response. 625 | */ 626 | void purple_http_request_set_response_writer(PurpleHttpRequest *request, 627 | PurpleHttpContentWriter writer, gpointer user_data); 628 | 629 | /** 630 | * purple_http_request_set_timeout: 631 | * @request: The request. 632 | * @timeout: Time (in seconds) after that timeout will be cancelled, 633 | * -1 for infinite time. 634 | * 635 | * Set maximum amount of time, that request is allowed to run. 636 | */ 637 | void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout); 638 | 639 | /** 640 | * purple_http_request_get_timeout: 641 | * @request: The request. 642 | * 643 | * Get maximum amount of time, that request is allowed to run. 644 | * 645 | * Returns: Timeout currently set (-1 for infinite). 646 | */ 647 | int purple_http_request_get_timeout(PurpleHttpRequest *request); 648 | 649 | /** 650 | * purple_http_request_set_max_redirects: 651 | * @request: The request. 652 | * @max_redirects: Maximum amount of redirects, or -1 for unlimited. 653 | * 654 | * Sets maximum amount of redirects. 655 | */ 656 | void purple_http_request_set_max_redirects(PurpleHttpRequest *request, 657 | int max_redirects); 658 | 659 | /** 660 | * purple_http_request_get_max_redirects: 661 | * @request: The request. 662 | * 663 | * Gets maximum amount of redirects. 664 | * 665 | * Returns: Current maximum amount of redirects (-1 for unlimited). 666 | */ 667 | int purple_http_request_get_max_redirects(PurpleHttpRequest *request); 668 | 669 | /** 670 | * purple_http_request_set_cookie_jar: 671 | * @request: The request. 672 | * @cookie_jar: The cookie jar. 673 | * 674 | * Sets cookie jar used for the request. 675 | */ 676 | void purple_http_request_set_cookie_jar(PurpleHttpRequest *request, 677 | PurpleHttpCookieJar *cookie_jar); 678 | 679 | /** 680 | * purple_http_request_get_cookie_jar: 681 | * @request: The request. 682 | * 683 | * Gets cookie jar used for the request. 684 | * 685 | * Returns: The cookie jar. 686 | */ 687 | PurpleHttpCookieJar * purple_http_request_get_cookie_jar( 688 | PurpleHttpRequest *request); 689 | 690 | /** 691 | * purple_http_request_set_http11: 692 | * @request: The request. 693 | * @http11: TRUE for HTTP/1.1, FALSE for HTTP/1.0. 694 | * 695 | * Sets HTTP version to use. 696 | */ 697 | void purple_http_request_set_http11(PurpleHttpRequest *request, 698 | gboolean http11); 699 | 700 | /** 701 | * purple_http_request_is_http11: 702 | * @request: The request. 703 | * 704 | * Gets used HTTP version. 705 | * 706 | * Returns: TRUE, if we use HTTP/1.1, FALSE for HTTP/1.0. 707 | */ 708 | gboolean purple_http_request_is_http11(PurpleHttpRequest *request); 709 | 710 | /** 711 | * purple_http_request_set_max_len: 712 | * @request: The request. 713 | * @max_len: Maximum length of response to read (-1 for the maximum 714 | * supported amount). 715 | * 716 | * Sets maximum length of response content to read. 717 | * 718 | * Headers length doesn't count here. 719 | * 720 | */ 721 | void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len); 722 | 723 | /** 724 | * purple_http_request_get_max_len: 725 | * @request: The request. 726 | * 727 | * Gets maximum length of response content to read. 728 | * 729 | * Returns: Maximum length of response to read, or -1 if unlimited. 730 | */ 731 | int purple_http_request_get_max_len(PurpleHttpRequest *request); 732 | 733 | /** 734 | * purple_http_request_header_set: 735 | * @request: The request. 736 | * @key: A header to be set. 737 | * @value: A value to set, or NULL to remove specified header. 738 | * 739 | * Sets (replaces, if exists) specified HTTP request header with provided value. 740 | * 741 | * See purple_http_request_header_add(). 742 | */ 743 | void purple_http_request_header_set(PurpleHttpRequest *request, 744 | const gchar *key, const gchar *value); 745 | 746 | /** 747 | * purple_http_request_header_set_printf: 748 | * @request: The request. 749 | * @key: A header to be set. 750 | * @format: The format string. 751 | * 752 | * Constructs and sets (replaces, if exists) specified HTTP request header. 753 | */ 754 | void purple_http_request_header_set_printf(PurpleHttpRequest *request, 755 | const gchar *key, const gchar *format, ...) G_GNUC_PRINTF(3, 4); 756 | 757 | /** 758 | * purple_http_request_header_add: 759 | * @key: A header to be set. 760 | * @value: A value to set. 761 | * 762 | * Adds (without replacing, if exists) an HTTP request header. 763 | * 764 | * See purple_http_request_header_set(). 765 | */ 766 | void purple_http_request_header_add(PurpleHttpRequest *request, 767 | const gchar *key, const gchar *value); 768 | 769 | 770 | /**************************************************************************/ 771 | /* HTTP Keep-Alive pool API */ 772 | /**************************************************************************/ 773 | 774 | /** 775 | * purple_http_keepalive_pool_new: 776 | * 777 | * Creates a new HTTP Keep-Alive pool. 778 | */ 779 | PurpleHttpKeepalivePool * 780 | purple_http_keepalive_pool_new(void); 781 | 782 | /** 783 | * purple_http_keepalive_pool_ref: 784 | * @pool: The HTTP Keep-Alive pool. 785 | * 786 | * Increment the reference count. 787 | */ 788 | void 789 | purple_http_keepalive_pool_ref(PurpleHttpKeepalivePool *pool); 790 | 791 | /** 792 | * purple_http_keepalive_pool_unref: 793 | * @pool: The HTTP Keep-Alive pool. 794 | * 795 | * Decrement the reference count. 796 | * 797 | * If the reference count reaches zero, the pool will be freed and all 798 | * connections will be closed. 799 | * 800 | * Returns: @pool or %NULL if the reference count reached zero. 801 | */ 802 | PurpleHttpKeepalivePool * 803 | purple_http_keepalive_pool_unref(PurpleHttpKeepalivePool *pool); 804 | 805 | /** 806 | * purple_http_keepalive_pool_set_limit_per_host: 807 | * @pool: The HTTP Keep-Alive pool. 808 | * @limit: The new limit, 0 for unlimited. 809 | * 810 | * Sets maximum allowed number of connections to specific host-triple (is_ssl + 811 | * hostname + port). 812 | */ 813 | void 814 | purple_http_keepalive_pool_set_limit_per_host(PurpleHttpKeepalivePool *pool, 815 | guint limit); 816 | 817 | /** 818 | * purple_http_keepalive_pool_get_limit_per_host: 819 | * @pool: The HTTP Keep-Alive pool. 820 | * 821 | * Gets maximum allowed number of connections to specific host-triple (is_ssl + 822 | * hostname + port). 823 | * 824 | * Returns: The limit. 825 | */ 826 | guint 827 | purple_http_keepalive_pool_get_limit_per_host(PurpleHttpKeepalivePool *pool); 828 | 829 | 830 | /**************************************************************************/ 831 | /* HTTP connection set API */ 832 | /**************************************************************************/ 833 | 834 | PurpleHttpConnectionSet * 835 | purple_http_connection_set_new(void); 836 | 837 | void 838 | purple_http_connection_set_destroy(PurpleHttpConnectionSet *set); 839 | 840 | void 841 | purple_http_connection_set_add(PurpleHttpConnectionSet *set, 842 | PurpleHttpConnection *http_conn); 843 | 844 | 845 | /**************************************************************************/ 846 | /* HTTP response API */ 847 | /**************************************************************************/ 848 | 849 | /** 850 | * purple_http_response_is_successful: 851 | * @response: The response. 852 | * 853 | * Checks, if HTTP request was performed successfully. 854 | * 855 | * Returns: TRUE, if request was performed successfully. 856 | */ 857 | gboolean purple_http_response_is_successful(PurpleHttpResponse *response); 858 | 859 | /** 860 | * purple_http_response_get_code: 861 | * @response: The response. 862 | * 863 | * Gets HTTP response code. 864 | * 865 | * Returns: HTTP response code. 866 | */ 867 | int purple_http_response_get_code(PurpleHttpResponse *response); 868 | 869 | /** 870 | * purple_http_response_get_error: 871 | * @response: The response. 872 | * 873 | * Gets error description. 874 | * 875 | * Returns: Localized error description or NULL, if there was no error. 876 | */ 877 | const gchar * purple_http_response_get_error(PurpleHttpResponse *response); 878 | 879 | /** 880 | * purple_http_response_get_data_len: 881 | * @response: The response. 882 | * 883 | * Gets HTTP response data length. 884 | * 885 | * Returns: Data length; 886 | */ 887 | gsize purple_http_response_get_data_len(PurpleHttpResponse *response); 888 | 889 | /** 890 | * purple_http_response_get_data: 891 | * @response: The response. 892 | * @len: Return address for the size of the data. Can be NULL. 893 | * 894 | * Gets HTTP response data. 895 | * 896 | * Response data is not written, if writer callback was set for request. 897 | * 898 | * Returns: The data. 899 | */ 900 | const gchar * purple_http_response_get_data(PurpleHttpResponse *response, size_t *len); 901 | 902 | /** 903 | * purple_http_response_get_all_headers: 904 | * @response: The response. 905 | * 906 | * Gets all headers got with response. 907 | * 908 | * Returns: GList of PurpleKeyValuePair, which keys are header field 909 | * names (gchar*) and values are its contents (gchar*). 910 | */ 911 | const GList * purple_http_response_get_all_headers(PurpleHttpResponse *response); 912 | 913 | /** 914 | * purple_http_response_get_headers_by_name: 915 | * @response: The response. 916 | * @name: The name of header field. 917 | * 918 | * Gets all headers with specified name got with response. 919 | * 920 | * Returns: GList of header field records contents (gchar*). 921 | */ 922 | const GList * purple_http_response_get_headers_by_name( 923 | PurpleHttpResponse *response, const gchar *name); 924 | 925 | /** 926 | * purple_http_response_get_header: 927 | * @response: The response. 928 | * @name: The name of header field. 929 | * 930 | * Gets one header contents with specified name got with response. 931 | * 932 | * To get all headers with the same name, use 933 | * purple_http_response_get_headers_by_name instead. 934 | * 935 | * Returns: Header field contents or NULL, if there is no such one. 936 | */ 937 | const gchar * purple_http_response_get_header(PurpleHttpResponse *response, 938 | const gchar *name); 939 | 940 | 941 | /**************************************************************************/ 942 | /* HTTP Subsystem */ 943 | /**************************************************************************/ 944 | 945 | /** 946 | * purple_http_init: 947 | * 948 | * Initializes the http subsystem. 949 | */ 950 | void purple_http_init(void); 951 | 952 | /** 953 | * purple_http_uninit: 954 | * 955 | * Uninitializes the http subsystem. 956 | */ 957 | void purple_http_uninit(void); 958 | 959 | G_END_DECLS 960 | 961 | #endif /* _PURPLE_HTTP_H_ */ 962 | -------------------------------------------------------------------------------- /purple2compat/image-store.h: -------------------------------------------------------------------------------- 1 | #ifndef _IMAGE_STORE_H_ 2 | #define _IMAGE_STORE_H_ 3 | 4 | #include "imgstore.h" 5 | #include "image.h" 6 | 7 | 8 | #define purple_image_store_add(img) purple_imgstore_add_with_id( \ 9 | g_memdup(purple_imgstore_get_data(img), purple_imgstore_get_size(img)), \ 10 | purple_imgstore_get_size(img), purple_imgstore_get_filename(img)) 11 | #define purple_image_store_get purple_imgstore_find_by_id 12 | 13 | 14 | #endif /*_IMAGE_STORE_H_*/ 15 | -------------------------------------------------------------------------------- /purple2compat/image.h: -------------------------------------------------------------------------------- 1 | #ifndef _IMAGE_H_ 2 | #define _IMAGE_H_ 3 | 4 | #include "imgstore.h" 5 | 6 | #define PurpleImage PurpleStoredImage 7 | #define purple_image_new_from_file(p, e) purple_imgstore_new_from_file(p) 8 | #define purple_image_new_from_data(d, l) purple_imgstore_add(d, l, NULL) 9 | #define purple_image_get_path purple_imgstore_get_filename 10 | #define purple_image_get_data_size purple_imgstore_get_size 11 | #define purple_image_get_data purple_imgstore_get_data 12 | #define purple_image_get_extension purple_imgstore_get_extension 13 | 14 | #endif /* _IMAGE_H_ */ 15 | -------------------------------------------------------------------------------- /purple2compat/internal.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #ifdef _WIN32 6 | #include "win32/win32dep.h" 7 | #endif 8 | 9 | #include "../purplecompat.h" 10 | 11 | #ifndef N_ 12 | # define N_(a) (a) 13 | #endif 14 | 15 | #ifndef _ 16 | # define _(a) (a) 17 | #endif 18 | -------------------------------------------------------------------------------- /purple2compat/plugins.h: -------------------------------------------------------------------------------- 1 | #include "plugin.h" 2 | -------------------------------------------------------------------------------- /purple2compat/purple-socket.c: -------------------------------------------------------------------------------- 1 | /* purple 2 | * 3 | * Purple is the legal property of its developers, whose names are too numerous 4 | * to list here. Please refer to the COPYRIGHT file distributed with this 5 | * source distribution. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA 20 | */ 21 | 22 | #include "purple-socket.h" 23 | 24 | #ifndef _WIN32 25 | #include 26 | #include 27 | #endif 28 | 29 | #include "internal.h" 30 | 31 | #include "debug.h" 32 | #include "proxy.h" 33 | #include "sslconn.h" 34 | 35 | typedef enum { 36 | PURPLE_SOCKET_STATE_DISCONNECTED = 0, 37 | PURPLE_SOCKET_STATE_CONNECTING, 38 | PURPLE_SOCKET_STATE_CONNECTED, 39 | PURPLE_SOCKET_STATE_ERROR 40 | } PurpleSocketState; 41 | 42 | struct _PurpleSocket 43 | { 44 | PurpleConnection *gc; 45 | gchar *host; 46 | int port; 47 | gboolean is_tls; 48 | GHashTable *data; 49 | 50 | PurpleSocketState state; 51 | 52 | PurpleSslConnection *tls_connection; 53 | PurpleProxyConnectData *raw_connection; 54 | int fd; 55 | guint inpa; 56 | 57 | PurpleSocketConnectCb cb; 58 | gpointer cb_data; 59 | }; 60 | 61 | static GHashTable *handles = NULL; 62 | 63 | static void 64 | handle_add(PurpleSocket *ps) 65 | { 66 | PurpleConnection *gc = ps->gc; 67 | GSList *l; 68 | 69 | l = g_hash_table_lookup(handles, gc); 70 | l = g_slist_prepend(l, ps); 71 | g_hash_table_insert(handles, gc, l); 72 | } 73 | 74 | static void 75 | handle_remove(PurpleSocket *ps) 76 | { 77 | PurpleConnection *gc = ps->gc; 78 | GSList *l; 79 | 80 | l = g_hash_table_lookup(handles, gc); 81 | if (l != NULL) { 82 | l = g_slist_remove(l, ps); 83 | g_hash_table_insert(handles, gc, l); 84 | } 85 | } 86 | 87 | void 88 | _purple_socket_init(void) 89 | { 90 | handles = g_hash_table_new(g_direct_hash, g_direct_equal); 91 | } 92 | 93 | void 94 | _purple_socket_uninit(void) 95 | { 96 | g_hash_table_destroy(handles); 97 | handles = NULL; 98 | } 99 | 100 | PurpleSocket * 101 | purple_socket_new(PurpleConnection *gc) 102 | { 103 | PurpleSocket *ps = g_new0(PurpleSocket, 1); 104 | 105 | ps->gc = gc; 106 | ps->fd = -1; 107 | ps->port = -1; 108 | ps->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 109 | 110 | handle_add(ps); 111 | 112 | return ps; 113 | } 114 | 115 | PurpleConnection * 116 | purple_socket_get_connection(PurpleSocket *ps) 117 | { 118 | g_return_val_if_fail(ps != NULL, NULL); 119 | 120 | return ps->gc; 121 | } 122 | 123 | static gboolean 124 | purple_socket_check_state(PurpleSocket *ps, PurpleSocketState wanted_state) 125 | { 126 | g_return_val_if_fail(ps != NULL, FALSE); 127 | 128 | if (ps->state == wanted_state) 129 | return TRUE; 130 | 131 | purple_debug_error("socket", "invalid state: %d (should be: %d)", 132 | ps->state, wanted_state); 133 | ps->state = PURPLE_SOCKET_STATE_ERROR; 134 | return FALSE; 135 | } 136 | 137 | void 138 | purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls) 139 | { 140 | g_return_if_fail(ps != NULL); 141 | 142 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) 143 | return; 144 | 145 | ps->is_tls = is_tls; 146 | } 147 | 148 | void 149 | purple_socket_set_host(PurpleSocket *ps, const gchar *host) 150 | { 151 | g_return_if_fail(ps != NULL); 152 | 153 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) 154 | return; 155 | 156 | g_free(ps->host); 157 | ps->host = g_strdup(host); 158 | } 159 | 160 | void 161 | purple_socket_set_port(PurpleSocket *ps, int port) 162 | { 163 | g_return_if_fail(ps != NULL); 164 | g_return_if_fail(port >= 0); 165 | g_return_if_fail(port <= 65535); 166 | 167 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) 168 | return; 169 | 170 | ps->port = port; 171 | } 172 | 173 | static void 174 | _purple_socket_connected_raw(gpointer _ps, gint fd, const gchar *error_message) 175 | { 176 | PurpleSocket *ps = _ps; 177 | 178 | ps->raw_connection = NULL; 179 | 180 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) { 181 | if (fd > 0) 182 | close(fd); 183 | ps->cb(ps, _("Invalid socket state"), ps->cb_data); 184 | return; 185 | } 186 | 187 | if (fd <= 0 || error_message != NULL) { 188 | if (error_message == NULL) 189 | error_message = _("Unknown error"); 190 | ps->fd = -1; 191 | ps->state = PURPLE_SOCKET_STATE_ERROR; 192 | ps->cb(ps, error_message, ps->cb_data); 193 | return; 194 | } 195 | 196 | ps->state = PURPLE_SOCKET_STATE_CONNECTED; 197 | ps->fd = fd; 198 | ps->cb(ps, NULL, ps->cb_data); 199 | } 200 | 201 | static void 202 | _purple_socket_connected_tls(gpointer _ps, PurpleSslConnection *tls_connection, 203 | PurpleInputCondition cond) 204 | { 205 | PurpleSocket *ps = _ps; 206 | 207 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) { 208 | purple_ssl_close(tls_connection); 209 | ps->tls_connection = NULL; 210 | ps->cb(ps, _("Invalid socket state"), ps->cb_data); 211 | return; 212 | } 213 | 214 | if (ps->tls_connection->fd <= 0) { 215 | ps->state = PURPLE_SOCKET_STATE_ERROR; 216 | purple_ssl_close(tls_connection); 217 | ps->tls_connection = NULL; 218 | ps->cb(ps, _("Invalid file descriptor"), ps->cb_data); 219 | return; 220 | } 221 | 222 | ps->state = PURPLE_SOCKET_STATE_CONNECTED; 223 | ps->fd = ps->tls_connection->fd; 224 | ps->cb(ps, NULL, ps->cb_data); 225 | } 226 | 227 | static void 228 | _purple_socket_connected_tls_error(PurpleSslConnection *ssl_connection, 229 | PurpleSslErrorType error, gpointer _ps) 230 | { 231 | PurpleSocket *ps = _ps; 232 | 233 | ps->state = PURPLE_SOCKET_STATE_ERROR; 234 | ps->tls_connection = NULL; 235 | ps->cb(ps, purple_ssl_strerror(error), ps->cb_data); 236 | } 237 | 238 | gboolean 239 | purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb, 240 | gpointer user_data) 241 | { 242 | PurpleAccount *account = NULL; 243 | 244 | g_return_val_if_fail(ps != NULL, FALSE); 245 | 246 | if (ps->gc && purple_connection_is_disconnecting(ps->gc)) { 247 | purple_debug_error("socket", "connection is being destroyed"); 248 | ps->state = PURPLE_SOCKET_STATE_ERROR; 249 | return FALSE; 250 | } 251 | 252 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED)) 253 | return FALSE; 254 | ps->state = PURPLE_SOCKET_STATE_CONNECTING; 255 | 256 | if (ps->host == NULL || ps->port < 0) { 257 | purple_debug_error("socket", "Host or port is not specified"); 258 | ps->state = PURPLE_SOCKET_STATE_ERROR; 259 | return FALSE; 260 | } 261 | 262 | if (ps->gc != NULL) 263 | account = purple_connection_get_account(ps->gc); 264 | 265 | ps->cb = cb; 266 | ps->cb_data = user_data; 267 | 268 | if (ps->is_tls) { 269 | ps->tls_connection = purple_ssl_connect(account, ps->host, 270 | ps->port, _purple_socket_connected_tls, 271 | _purple_socket_connected_tls_error, ps); 272 | } else { 273 | ps->raw_connection = purple_proxy_connect(ps->gc, account, 274 | ps->host, ps->port, _purple_socket_connected_raw, ps); 275 | } 276 | 277 | if (ps->tls_connection == NULL && 278 | ps->raw_connection == NULL) 279 | { 280 | ps->state = PURPLE_SOCKET_STATE_ERROR; 281 | return FALSE; 282 | } 283 | 284 | return TRUE; 285 | } 286 | 287 | gssize 288 | purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len) 289 | { 290 | g_return_val_if_fail(ps != NULL, -1); 291 | g_return_val_if_fail(buf != NULL, -1); 292 | 293 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) 294 | return -1; 295 | 296 | if (ps->is_tls) 297 | return purple_ssl_read(ps->tls_connection, buf, len); 298 | else 299 | return read(ps->fd, buf, len); 300 | } 301 | 302 | gssize 303 | purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len) 304 | { 305 | g_return_val_if_fail(ps != NULL, -1); 306 | g_return_val_if_fail(buf != NULL, -1); 307 | 308 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) 309 | return -1; 310 | 311 | if (ps->is_tls) 312 | return purple_ssl_write(ps->tls_connection, buf, len); 313 | else 314 | return write(ps->fd, buf, len); 315 | } 316 | 317 | void 318 | purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond, 319 | PurpleInputFunction func, gpointer user_data) 320 | { 321 | g_return_if_fail(ps != NULL); 322 | 323 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) 324 | return; 325 | 326 | if (ps->inpa > 0) 327 | purple_input_remove(ps->inpa); 328 | ps->inpa = 0; 329 | 330 | g_return_if_fail(ps->fd > 0); 331 | 332 | if (func != NULL) 333 | ps->inpa = purple_input_add(ps->fd, cond, func, user_data); 334 | } 335 | 336 | int 337 | purple_socket_get_fd(PurpleSocket *ps) 338 | { 339 | g_return_val_if_fail(ps != NULL, -1); 340 | 341 | if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED)) 342 | return -1; 343 | 344 | g_return_val_if_fail(ps->fd > 0, -1); 345 | 346 | return ps->fd; 347 | } 348 | 349 | void 350 | purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data) 351 | { 352 | g_return_if_fail(ps != NULL); 353 | g_return_if_fail(key != NULL); 354 | 355 | if (data == NULL) 356 | g_hash_table_remove(ps->data, key); 357 | else 358 | g_hash_table_insert(ps->data, g_strdup(key), data); 359 | } 360 | 361 | gpointer 362 | purple_socket_get_data(PurpleSocket *ps, const gchar *key) 363 | { 364 | g_return_val_if_fail(ps != NULL, NULL); 365 | g_return_val_if_fail(key != NULL, NULL); 366 | 367 | return g_hash_table_lookup(ps->data, key); 368 | } 369 | 370 | static void 371 | purple_socket_cancel(PurpleSocket *ps) 372 | { 373 | if (ps->inpa > 0) 374 | purple_input_remove(ps->inpa); 375 | ps->inpa = 0; 376 | 377 | if (ps->tls_connection != NULL) { 378 | purple_ssl_close(ps->tls_connection); 379 | ps->fd = -1; 380 | } 381 | ps->tls_connection = NULL; 382 | 383 | if (ps->raw_connection != NULL) 384 | purple_proxy_connect_cancel(ps->raw_connection); 385 | ps->raw_connection = NULL; 386 | 387 | if (ps->fd > 0) 388 | close(ps->fd); 389 | ps->fd = 0; 390 | } 391 | 392 | void 393 | purple_socket_destroy(PurpleSocket *ps) 394 | { 395 | if (ps == NULL) 396 | return; 397 | 398 | handle_remove(ps); 399 | 400 | purple_socket_cancel(ps); 401 | 402 | g_free(ps->host); 403 | g_hash_table_destroy(ps->data); 404 | g_free(ps); 405 | } 406 | 407 | void 408 | _purple_socket_cancel_with_connection(PurpleConnection *gc) 409 | { 410 | GSList *it; 411 | 412 | it = g_hash_table_lookup(handles, gc); 413 | for (; it; it = g_slist_next(it)) { 414 | PurpleSocket *ps = it->data; 415 | purple_socket_cancel(ps); 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /purple2compat/purple-socket.h: -------------------------------------------------------------------------------- 1 | /* purple 2 | * 3 | * Purple is the legal property of its developers, whose names are too numerous 4 | * to list here. Please refer to the COPYRIGHT file distributed with this 5 | * source distribution. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA 20 | */ 21 | 22 | #ifndef _PURPLE_SOCKET_H_ 23 | #define _PURPLE_SOCKET_H_ 24 | /** 25 | * SECTION:purple-socket 26 | * @section_id: libpurple-purple-socket 27 | * @short_description: purple-socket.h 28 | * @title: Generic Sockets 29 | */ 30 | 31 | #include "connection.h" 32 | 33 | /** 34 | * PurpleSocket: 35 | * 36 | * A structure holding all resources needed for the TCP connection. 37 | */ 38 | typedef struct _PurpleSocket PurpleSocket; 39 | 40 | /** 41 | * PurpleSocketConnectCb: 42 | * @ps: The socket. 43 | * @error: Error message, or NULL if connection was successful. 44 | * @user_data: The user data passed with callback function. 45 | * 46 | * A callback fired after (successfully or not) establishing a connection. 47 | */ 48 | typedef void (*PurpleSocketConnectCb)(PurpleSocket *ps, const gchar *error, 49 | gpointer user_data); 50 | 51 | /** 52 | * purple_socket_new: 53 | * @gc: The connection for which the socket is needed, or NULL. 54 | * 55 | * Creates new, disconnected socket. 56 | * 57 | * Passing a PurpleConnection allows for proper proxy handling. 58 | * 59 | * Returns: The new socket struct. 60 | */ 61 | PurpleSocket * 62 | purple_socket_new(PurpleConnection *gc); 63 | 64 | /** 65 | * purple_socket_get_connection: 66 | * @ps: The socket. 67 | * 68 | * Gets PurpleConnection tied with specified socket. 69 | * 70 | * Returns: The PurpleConnection object. 71 | */ 72 | PurpleConnection * 73 | purple_socket_get_connection(PurpleSocket *ps); 74 | 75 | /** 76 | * purple_socket_set_tls: 77 | * @ps: The socket. 78 | * @is_tls: TRUE, if TLS should be handled transparently, FALSE otherwise. 79 | * 80 | * Determines, if socket should handle TLS. 81 | */ 82 | void 83 | purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls); 84 | 85 | /** 86 | * purple_socket_set_host: 87 | * @ps: The socket. 88 | * @host: The connection host. 89 | * 90 | * Sets connection host. 91 | */ 92 | void 93 | purple_socket_set_host(PurpleSocket *ps, const gchar *host); 94 | 95 | /** 96 | * purple_socket_set_port: 97 | * @ps: The socket. 98 | * @port: The connection port. 99 | * 100 | * Sets connection port. 101 | */ 102 | void 103 | purple_socket_set_port(PurpleSocket *ps, int port); 104 | 105 | /** 106 | * purple_socket_connect: 107 | * @ps: The socket. 108 | * @cb: The function to call after establishing a connection, or on 109 | * error. 110 | * @user_data: The user data to be passed to callback function. 111 | * 112 | * Establishes a connection. 113 | * 114 | * Returns: TRUE on success (this doesn't mean it's connected yet), FALSE 115 | * otherwise. 116 | */ 117 | gboolean 118 | purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb, 119 | gpointer user_data); 120 | 121 | /** 122 | * purple_socket_read: 123 | * @ps: The socket. 124 | * @buf: The buffer to write data to. 125 | * @len: The buffer size. 126 | * 127 | * Reads incoming data from socket. 128 | * 129 | * This function deals with TLS, if the socket is configured to do it. 130 | * 131 | * Returns: Amount of data written, or -1 on error (errno will be also be set). 132 | */ 133 | gssize 134 | purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len); 135 | 136 | /** 137 | * purple_socket_write: 138 | * @ps: The socket. 139 | * @buf: The buffer to read data from. 140 | * @len: The amount of data to read and send. 141 | * 142 | * Sends data through socket. 143 | * 144 | * This function deals with TLS, if the socket is configured to do it. 145 | * 146 | * Returns: Amount of data sent, or -1 on error (errno will albo be set). 147 | */ 148 | gssize 149 | purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len); 150 | 151 | /** 152 | * purple_socket_watch: 153 | * @ps: The socket. 154 | * @cond: The condition type. 155 | * @func: The callback function for data, or NULL to remove any 156 | * existing callbacks. 157 | * @user_data: The user data to be passed to callback function. 158 | * 159 | * Adds an input handler for the socket. 160 | * 161 | * If the specified socket had input handler already registered, it will be 162 | * removed. To remove any input handlers, pass an NULL handler function. 163 | */ 164 | void 165 | purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond, 166 | PurpleInputFunction func, gpointer user_data); 167 | 168 | /** 169 | * purple_socket_get_fd: 170 | * @ps: The socket 171 | * 172 | * Gets underlying file descriptor for socket. 173 | * 174 | * It's not meant to read/write data (use purple_socket_read/ 175 | * purple_socket_write), rather for watching for changes with select(). 176 | * 177 | * Returns: The file descriptor, or -1 on error. 178 | */ 179 | int 180 | purple_socket_get_fd(PurpleSocket *ps); 181 | 182 | /** 183 | * purple_socket_set_data: 184 | * @ps: The socket. 185 | * @key: The unique key. 186 | * @data: The data to assign, or NULL to remove. 187 | * 188 | * Sets extra data for a socket. 189 | */ 190 | void 191 | purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data); 192 | 193 | /** 194 | * purple_socket_get_data: 195 | * @ps: The socket. 196 | * @key: The unqiue key. 197 | * 198 | * Returns extra data in a socket. 199 | * 200 | * Returns: The data associated with the key. 201 | */ 202 | gpointer 203 | purple_socket_get_data(PurpleSocket *ps, const gchar *key); 204 | 205 | /** 206 | * purple_socket_destroy: 207 | * @ps: The socket. 208 | * 209 | * Destroys the socket, closes connection and frees all resources. 210 | * 211 | * If file descriptor for the socket was extracted with purple_socket_get_fd and 212 | * added to event loop, it have to be removed prior this. 213 | */ 214 | void 215 | purple_socket_destroy(PurpleSocket *ps); 216 | 217 | #endif /* _PURPLE_SOCKET_H_ */ 218 | -------------------------------------------------------------------------------- /purplecompat.h: -------------------------------------------------------------------------------- 1 | #ifndef _PURPLECOMPAT_H_ 2 | #define _PURPLECOMPAT_H_ 3 | 4 | #include 5 | #include "version.h" 6 | 7 | #if PURPLE_VERSION_CHECK(3, 0, 0) 8 | #include 9 | 10 | #define purple_conversation_set_data(conv, key, value) g_object_set_data(G_OBJECT(conv), key, value) 11 | #define purple_conversation_get_data(conv, key) g_object_get_data(G_OBJECT(conv), key) 12 | 13 | #define purple_circular_buffer_destroy g_object_unref 14 | #define purple_hash_destroy g_object_unref 15 | #define purple_message_destroy g_object_unref 16 | #define purple_buddy_destroy g_object_unref 17 | 18 | #define PURPLE_TYPE_STRING G_TYPE_STRING 19 | 20 | #define purple_protocol_action_get_connection(action) ((action)->connection) 21 | 22 | #define purple_chat_user_set_alias(cb, alias) g_object_set((cb), "alias", (alias), NULL) 23 | #define purple_chat_get_alias(chat) g_object_get_data(G_OBJECT(chat), "alias") 24 | 25 | #else /*!PURPLE_VERSION_CHECK(3, 0, 0)*/ 26 | 27 | #include "connection.h" 28 | 29 | #define purple_blist_find_buddy purple_find_buddy 30 | #define purple_blist_find_buddies purple_find_buddies 31 | #define purple_blist_find_group purple_find_group 32 | #define PURPLE_IS_BUDDY PURPLE_BLIST_NODE_IS_BUDDY 33 | #define PURPLE_IS_CHAT PURPLE_BLIST_NODE_IS_CHAT 34 | #define purple_chat_get_name_only purple_chat_get_name 35 | #define purple_chat_set_alias purple_blist_alias_chat 36 | #define purple_chat_get_alias(chat) ((chat)->alias) 37 | #define purple_buddy_set_server_alias purple_blist_server_alias_buddy 38 | static inline void 39 | purple_blist_node_set_transient(PurpleBlistNode *node, gboolean transient) 40 | { 41 | PurpleBlistNodeFlags old_flags = purple_blist_node_get_flags(node); 42 | PurpleBlistNodeFlags new_flags; 43 | 44 | if (transient) 45 | new_flags = old_flags | PURPLE_BLIST_NODE_FLAG_NO_SAVE; 46 | else 47 | new_flags = old_flags & ~PURPLE_BLIST_NODE_FLAG_NO_SAVE; 48 | 49 | purple_blist_node_set_flags(node, new_flags); 50 | } 51 | 52 | #define PURPLE_CMD_FLAG_PROTOCOL_ONLY PURPLE_CMD_FLAG_PRPL_ONLY 53 | 54 | #define PURPLE_TYPE_CONNECTION purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION) 55 | #define PURPLE_IS_CONNECTION PURPLE_CONNECTION_IS_VALID 56 | 57 | #define PURPLE_CONNECTION_DISCONNECTED PURPLE_DISCONNECTED 58 | #define PURPLE_CONNECTION_CONNECTING PURPLE_CONNECTING 59 | #define PURPLE_CONNECTION_CONNECTED PURPLE_CONNECTED 60 | #define PURPLE_CONNECTION_FLAG_HTML PURPLE_CONNECTION_HTML 61 | #define PURPLE_CONNECTION_FLAG_NO_BGCOLOR PURPLE_CONNECTION_NO_BGCOLOR 62 | #define PURPLE_CONNECTION_FLAG_NO_FONTSIZE PURPLE_CONNECTION_NO_FONTSIZE 63 | #define PURPLE_CONNECTION_FLAG_NO_IMAGES PURPLE_CONNECTION_NO_IMAGES 64 | 65 | #define purple_request_cpar_from_connection(a) purple_connection_get_account(a), NULL, NULL 66 | #define purple_connection_get_protocol purple_connection_get_prpl 67 | #define purple_connection_error purple_connection_error_reason 68 | #define purple_connection_is_disconnecting(c) FALSE 69 | #define purple_connection_set_flags(pc, f) ((pc)->flags = (f)) 70 | #define purple_connection_get_flags(pc) ((pc)->flags) 71 | 72 | #define PurpleConversationUpdateType PurpleConvUpdateType 73 | #define PURPLE_CONVERSATION_UPDATE_TOPIC PURPLE_CONV_UPDATE_TOPIC 74 | #define PURPLE_CONVERSATION_UPDATE_UNSEEN PURPLE_CONV_UPDATE_UNSEEN 75 | #define PurpleChatConversation PurpleConvChat 76 | #define PurpleIMConversation PurpleConvIm 77 | #define purple_conversations_find_chat_with_account(id, account) \ 78 | PURPLE_CONV_CHAT(purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, id, account)) 79 | #define purple_conversations_find_chat(pc, id) PURPLE_CONV_CHAT(purple_find_chat(pc, id)) 80 | #define purple_conversations_get_all purple_get_conversations 81 | #define purple_conversation_get_connection purple_conversation_get_gc 82 | #define purple_chat_conversation_get_id purple_conv_chat_get_id 83 | #define purple_conversations_find_im_with_account(name, account) \ 84 | PURPLE_CONV_IM(purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, account)) 85 | #define purple_im_conversation_new(account, from) PURPLE_CONV_IM(purple_conversation_new(PURPLE_CONV_TYPE_IM, account, from)) 86 | #define PURPLE_CONVERSATION(chatorim) ((chatorim) == NULL ? NULL : (chatorim)->conv) 87 | #define PURPLE_IM_CONVERSATION(conv) PURPLE_CONV_IM(conv) 88 | #define PURPLE_CHAT_CONVERSATION(conv) PURPLE_CONV_CHAT(conv) 89 | #define PURPLE_IS_IM_CONVERSATION(conv) (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) 90 | #define PURPLE_IS_CHAT_CONVERSATION(conv) (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) 91 | #define purple_chat_conversation_add_user purple_conv_chat_add_user 92 | #define purple_chat_conversation_add_users purple_conv_chat_add_users 93 | #define purple_chat_conversation_has_left purple_conv_chat_has_left 94 | #define purple_chat_conversation_remove_user purple_conv_chat_remove_user 95 | #define purple_chat_conversation_remove_users purple_conv_chat_remove_users 96 | #define purple_chat_conversation_set_topic purple_conv_chat_set_topic 97 | 98 | #define PurpleMessage PurpleConvMessage 99 | #define purple_message_set_time(msg, time) ((msg)->when = (time)) 100 | #define purple_conversation_write_message(conv, msg) \ 101 | purple_conversation_write(conv, msg->who, msg->what, msg->flags, msg->when); \ 102 | purple_message_destroy(msg) 103 | static inline PurpleMessage * 104 | purple_message_new_outgoing(const gchar *who, const gchar *contents, PurpleMessageFlags flags) 105 | { 106 | PurpleMessage *message = g_new0(PurpleMessage, 1); 107 | 108 | message->who = g_strdup(who); 109 | message->what = g_strdup(contents); 110 | message->flags = flags; 111 | message->when = time(NULL); 112 | 113 | return message; 114 | } 115 | static inline void 116 | purple_message_destroy(PurpleMessage *message) 117 | { 118 | g_free(message->who); 119 | g_free(message->what); 120 | g_free(message); 121 | } 122 | #if !PURPLE_VERSION_CHECK(2, 12, 0) 123 | # define PURPLE_MESSAGE_REMOTE_SEND 0x10000 124 | #endif 125 | 126 | #define PurpleProtocolChatEntry struct proto_chat_entry 127 | #define PurpleChatUserFlags PurpleConvChatBuddyFlags 128 | #define PURPLE_CHAT_USER_NONE PURPLE_CBFLAGS_NONE 129 | #define PURPLE_CHAT_USER_OP PURPLE_CBFLAGS_OP 130 | #define PURPLE_CHAT_USER_FOUNDER PURPLE_CBFLAGS_FOUNDER 131 | #define PURPLE_CHAT_USER_TYPING PURPLE_CBFLAGS_TYPING 132 | #define PURPLE_CHAT_USER_AWAY PURPLE_CBFLAGS_AWAY 133 | #define PURPLE_CHAT_USER_HALFOP PURPLE_CBFLAGS_HALFOP 134 | #define PURPLE_CHAT_USER_VOICE PURPLE_CBFLAGS_VOICE 135 | #define PURPLE_CHAT_USER_TYPING PURPLE_CBFLAGS_TYPING 136 | #define PurpleChatUser PurpleConvChatBuddy 137 | 138 | static inline PurpleChatUser * 139 | purple_chat_conversation_find_user(PurpleChatConversation *chat, const char *name) 140 | { 141 | PurpleChatUser *cb = purple_conv_chat_cb_find(chat, name); 142 | 143 | if (cb != NULL) { 144 | g_dataset_set_data(cb, "chat", chat); 145 | } 146 | 147 | return cb; 148 | } 149 | #define purple_chat_user_get_flags(cb) purple_conv_chat_user_get_flags(g_dataset_get_data((cb), "chat"), (cb)->name) 150 | #define purple_chat_user_set_flags(cb, f) purple_conv_chat_user_set_flags(g_dataset_get_data((cb), "chat"), (cb)->name, (f)) 151 | #define purple_chat_user_set_alias(cb, a) (g_free((cb)->alias), (cb)->alias = g_strdup(a)) 152 | #define PurpleIMTypingState PurpleTypingState 153 | #define PURPLE_IM_NOT_TYPING PURPLE_NOT_TYPING 154 | #define PURPLE_IM_TYPING PURPLE_TYPING 155 | #define PURPLE_IM_TYPED PURPLE_TYPED 156 | 157 | #define purple_media_set_protocol_data purple_media_set_prpl_data 158 | #if PURPLE_VERSION_CHECK(2, 10, 12) 159 | // Handle ABI breakage 160 | # define PURPLE_MEDIA_NETWORK_PROTOCOL_TCP PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE 161 | #endif 162 | 163 | #define purple_account_privacy_deny_add purple_privacy_deny_add 164 | #define purple_account_privacy_deny_remove purple_privacy_deny_remove 165 | #define purple_buddy_set_name purple_blist_rename_buddy 166 | #define purple_request_cpar_from_connection(a) purple_connection_get_account(a), NULL, NULL 167 | #define purple_notify_user_info_add_pair_html purple_notify_user_info_add_pair 168 | 169 | #ifdef purple_notify_error 170 | #undef purple_notify_error 171 | #endif 172 | #define purple_notify_error(handle, title, primary, secondary, cpar) \ 173 | purple_notify_message((handle), PURPLE_NOTIFY_MSG_ERROR, (title), \ 174 | (primary), (secondary), NULL, NULL) 175 | #undef purple_notify_warning 176 | #define purple_notify_warning(handle, title, primary, secondary, cpar) \ 177 | purple_notify_message((handle), PURPLE_NOTIFY_MSG_WARNING, (title), \ 178 | (primary), (secondary), NULL, NULL) 179 | #define purple_notify_user_info_add_pair_html purple_notify_user_info_add_pair 180 | 181 | #define PurpleProtocolAction PurplePluginAction 182 | #define purple_protocol_action_get_connection(action) ((PurpleConnection *) (action)->context) 183 | #define purple_protocol_action_new purple_plugin_action_new 184 | #define purple_protocol_get_id purple_plugin_get_id 185 | 186 | #define purple_protocol_got_user_status purple_prpl_got_user_status 187 | 188 | #define purple_account_privacy_deny_add purple_privacy_deny_add 189 | #define purple_account_privacy_deny_remove purple_privacy_deny_remove 190 | #define purple_account_set_password(account, password, dummy1, dummy2) \ 191 | purple_account_set_password(account, password); 192 | #define purple_account_set_private_alias purple_account_set_alias 193 | #define purple_account_get_private_alias purple_account_get_alias 194 | 195 | #define purple_proxy_info_get_proxy_type purple_proxy_info_get_type 196 | 197 | #define purple_serv_got_im serv_got_im 198 | #define purple_serv_got_typing serv_got_typing 199 | #define purple_serv_got_alias serv_got_alias 200 | #define purple_serv_got_chat_in serv_got_chat_in 201 | #define purple_serv_got_chat_left serv_got_chat_left 202 | #define purple_serv_got_joined_chat(pc, id, name) PURPLE_CONV_CHAT(serv_got_joined_chat(pc, id, name)) 203 | 204 | #define purple_status_get_status_type purple_status_get_type 205 | 206 | #define g_timeout_add_seconds purple_timeout_add_seconds 207 | #define g_timeout_add purple_timeout_add 208 | #define g_source_remove purple_timeout_remove 209 | 210 | #define PurpleXmlNode xmlnode 211 | #define purple_xmlnode_new xmlnode_new 212 | #define purple_xmlnode_new_child xmlnode_new_child 213 | #define purple_xmlnode_from_str xmlnode_from_str 214 | #define purple_xmlnode_to_str xmlnode_to_str 215 | #define purple_xmlnode_get_child xmlnode_get_child 216 | #define purple_xmlnode_get_next_twin xmlnode_get_next_twin 217 | #define purple_xmlnode_get_data xmlnode_get_data 218 | #define purple_xmlnode_get_attrib xmlnode_get_attrib 219 | #define purple_xmlnode_set_attrib xmlnode_set_attrib 220 | #define purple_xmlnode_insert_data xmlnode_insert_data 221 | #define purple_xmlnode_free xmlnode_free 222 | 223 | 224 | #endif 225 | 226 | #endif /*_PURPLECOMPAT_H_*/ 227 | --------------------------------------------------------------------------------