├── .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 | 
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 | 
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 |
--------------------------------------------------------------------------------