├── .hgignore
├── GTS CA 1O1.pem
├── Google Internet Authority G2.pem
├── Google Internet Authority G3.pem
├── Google Trust Services - GlobalSign Root CA-R2.pem
├── Makefile
├── README.md
├── convey.yml
├── glibcompat.h
├── gmail.proto
├── gpl3.txt
├── hangout_media.proto
├── hangouts.proto
├── hangouts16.png
├── hangouts22.png
├── hangouts24.png
├── hangouts48.png
├── hangouts_auth.c
├── hangouts_auth.h
├── hangouts_connection.c
├── hangouts_connection.h
├── hangouts_conversation.c
├── hangouts_conversation.h
├── hangouts_events.c
├── hangouts_events.h
├── hangouts_json.c
├── hangouts_json.h
├── hangouts_media.c
├── hangouts_media.h
├── hangouts_pblite.c
├── hangouts_pblite.h
├── libhangouts.c
├── libhangouts.h
├── libprotobuf-c-1.dll
├── purple-hangouts.nsi
├── purple-hangouts.spec
├── 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
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 | *.so
3 | *.dll
4 | hangouts.pb-c.*
5 |
6 |
--------------------------------------------------------------------------------
/GTS CA 1O1.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIESjCCAzKgAwIBAgINAeO0mqGNiqmBJWlQuDANBgkqhkiG9w0BAQsFADBMMSAw
3 | HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
4 | U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
5 | MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg
6 | U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxTzEwggEiMA0GCSqGSIb3DQEBAQUA
7 | A4IBDwAwggEKAoIBAQDQGM9F1IvN05zkQO9+tN1pIRvJzzyOTHW5DzEZhD2ePCnv
8 | UA0Qk28FgICfKqC9EksC4T2fWBYk/jCfC3R3VZMdS/dN4ZKCEPZRrAzDsiKUDzRr
9 | mBBJ5wudgzndIMYcLe/RGGFl5yODIKgjEv/SJH/UL+dEaltN11BmsK+eQmMF++Ac
10 | xGNhr59qM/9il71I2dN8FGfcddwuaej4bXhp0LcQBbjxMcI7JP0aM3T4I+DsaxmK
11 | FsbjzaTNC9uzpFlgOIg7rR25xoynUxv8vNmkq7zdPGHXkxWY7oG9j+JkRyBABk7X
12 | rJfoucBZEqFJJSPk7XA0LKW0Y3z5oz2D0c1tJKwHAgMBAAGjggEzMIIBLzAOBgNV
13 | HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud
14 | EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJjR+G4Q68+b7GCfGJAboOt9Cf0rMB8G
15 | A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl
16 | BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp
17 | MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g
18 | BDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y
19 | ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAGoA+Nnn78y6pRjd9XlQWNa7H
20 | TgiZ/r3RNGkmUmYHPQq6Scti9PEajvwRT2iWTHQr02fesqOqBY2ETUwgZQ+lltoN
21 | FvhsO9tvBCOIazpswWC9aJ9xju4tWDQH8NVU6YZZ/XteDSGU9YzJqPjY8q3MDxrz
22 | mqepBCf5o8mw/wJ4a2G6xzUr6Fb6T8McDO22PLRL6u3M4Tzs3A2M1j6bykJYi8wW
23 | IRdAvKLWZu/axBVbzYmqmwkm5zLSDW5nIAJbELCQCZwMH56t2Dvqofxs6BBcCFIZ
24 | USpxu6x6td0V7SvJCCosirSmIatj/9dSSVDQibet8q/7UK4v4ZUN80atnZz1yg==
25 | -----END CERTIFICATE-----
26 |
--------------------------------------------------------------------------------
/Google Internet Authority G2.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC
3 | MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS
4 | R2VvVHJ1c3QgR2xvYmFsIENBMB4XDTE3MDUyMjExMzIzN1oXDTE4MTIzMTIzNTk1
5 | OVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMT
6 | HEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwggEiMA0GCSqGSIb3DQEBAQUA
7 | A4IBDwAwggEKAoIBAQCcKgR3XNhQkToGo4Lg2FBIvIk/8RlwGohGfuCPxfGJziHu
8 | Wv5hDbcyRImgdAtTT1WkzoJile7rWV/G4QWAEsRelD+8W0g49FP3JOb7kekVxM/0
9 | Uw30SvyfVN59vqBrb4fA0FAfKDADQNoIc1Fsf/86PKc3Bo69SxEE630k3ub5/DFx
10 | +5TVYPMuSq9C0svqxGoassxT3RVLix/IGWEfzZ2oPmMrhDVpZYTIGcVGIvhTlb7j
11 | gEoQxirsupcgEcc5mRAEoPBhepUljE5SdeK27QjKFPzOImqzTs9GA5eXA37Asd57
12 | r0Uzz7o+cbfe9CUlwg01iZ2d+w4ReYkeN8WvjnJpAgMBAAGjggERMIIBDTAfBgNV
13 | HSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1
14 | dvWBtrtiGrpagS8wDgYDVR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggr
15 | BgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAw
16 | NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9i
17 | YWwuY3JsMCEGA1UdIAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwHQYDVR0l
18 | BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQDKSeWs
19 | 12Rkd1u+cfrP9B4jx5ppY1Rf60zWGSgjZGaOHMeHgGRfBIsmr5jfCnC8vBk97nsz
20 | qX+99AXUcLsFJnnqmseYuQcZZTTMPOk/xQH6bwx+23pwXEz+LQDwyr4tjrSogPsB
21 | E4jLnD/lu3fKOmc2887VJwJyQ6C9bgLxRwVxPgFZ6RGeGvOED4Cmong1L7bHon8X
22 | fOGLVq7uZ4hRJzBgpWJSwzfVO+qFKgE4h6LPcK2kesnE58rF2rwjMvL+GMJ74N87
23 | L9TQEOaWTPtEtyFkDbkAlDASJodYmDkFOA/MgkgMCkdm7r+0X8T/cKjhf4t5K7hl
24 | MqO5tzHpCvX2HzLc
25 | -----END CERTIFICATE-----
26 |
--------------------------------------------------------------------------------
/Google Internet Authority G3.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw
3 | HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
4 | U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
5 | MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg
6 | U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw
7 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW
8 | XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK
9 | 71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9
10 | RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z
11 | ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT
12 | kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz
13 | AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH
14 | AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa
15 | Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu
16 | MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv
17 | b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz
18 | cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc
19 | aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA
20 | HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e
21 | ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq
22 | wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu
23 | FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy
24 | 7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV
25 | c7o835DLAFshEWfC7TIe3g==
26 | -----END CERTIFICATE-----
27 |
--------------------------------------------------------------------------------
/Google Trust Services - GlobalSign Root CA-R2.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
3 | A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
4 | Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
5 | MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
6 | A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
7 | hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
8 | v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
9 | eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
10 | tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
11 | C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
12 | zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
13 | mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
14 | V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
15 | bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
16 | 3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
17 | J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
18 | 291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
19 | ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
20 | AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
21 | TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/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 | PROTOBUF_C_DIR ?= $(WIN32_DEV_TOP)/protobuf-c-Release-2.6
7 |
8 | WIN32_CC ?= $(WIN32_DEV_TOP)/mingw-4.7.2/bin/gcc
9 | MAKENSIS ?= makensis
10 |
11 | PROTOC_C ?= protoc-c
12 | PKG_CONFIG ?= pkg-config
13 |
14 | CFLAGS ?= -O2 -g -pipe
15 | LDFLAGS ?=
16 |
17 | # Do some nasty OS and purple version detection
18 | ifeq ($(OS),Windows_NT)
19 | #only defined on 64-bit windows
20 | PROGFILES32 = ${ProgramFiles(x86)}
21 | ifndef PROGFILES32
22 | PROGFILES32 = $(PROGRAMFILES)
23 | endif
24 | HANGOUTS_TARGET = libhangouts.dll
25 | HANGOUTS_DEST = "$(PROGFILES32)/Pidgin/plugins"
26 | HANGOUTS_ICONS_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/protocols"
27 | MAKENSIS = "$(PROGFILES32)/NSIS/makensis.exe"
28 | else
29 |
30 | UNAME_S := $(shell uname -s)
31 |
32 | #.. There are special flags we need for OSX
33 | ifeq ($(UNAME_S), Darwin)
34 | #
35 | #.. /opt/local/include and subdirs are included here to ensure this compiles
36 | # for folks using Macports. I believe Homebrew uses /usr/local/include
37 | # so things should "just work". You *must* make sure your packages are
38 | # all up to date or you will most likely get compilation errors.
39 | #
40 | INCLUDES = -I/opt/local/include/protobuf-c -I/opt/local/include -lz $(OS)
41 |
42 | CC = gcc
43 | else
44 | INCLUDES = -I/usr/include/protobuf-c
45 | CC ?= gcc
46 | endif
47 |
48 | ifeq ($(shell $(PKG_CONFIG) --exists libprotobuf-c && echo "true"),true)
49 | PROTOBUF_OPTS := $(shell $(PKG_CONFIG) --cflags --libs libprotobuf-c)
50 | else
51 | PROTOBUF_OPTS := -I/usr/include/google -I/usr/include/google/protobuf-c -lprotobuf-c
52 | endif
53 |
54 | ifeq ($(shell $(PKG_CONFIG) --exists purple-3 2>/dev/null && echo "true"),)
55 | ifeq ($(shell $(PKG_CONFIG) --exists purple 2>/dev/null && echo "true"),)
56 | HANGOUTS_TARGET = FAILNOPURPLE
57 | HANGOUTS_DEST =
58 | HANGOUTS_ICONS_DEST =
59 | else
60 | HANGOUTS_TARGET = libhangouts.so
61 | HANGOUTS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple`
62 | HANGOUTS_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/protocols
63 | endif
64 | else
65 | HANGOUTS_TARGET = libhangouts3.so
66 | HANGOUTS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple-3`
67 | HANGOUTS_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/protocols
68 | endif
69 | endif
70 |
71 | 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 -I$(WIN32_DEV_TOP)/protobuf-c-Release-2.6/include -DENABLE_NLS -DPACKAGE_VERSION='"$(PLUGIN_VERSION)"' -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-unused-parameter -fno-strict-aliasing -Wformat
72 | WIN32_LDFLAGS = -L$(WIN32_DEV_TOP)/glib-2.28.8/lib -L$(PROTOBUF_C_DIR)/bin -L$(WIN32_DEV_TOP)/json-glib-0.14/lib -lpurple -lintl -lglib-2.0 -lgobject-2.0 -ljson-glib-1.0 -lprotobuf-c-1 -g -ggdb -static-libgcc -lz
73 | WIN32_PIDGIN2_CFLAGS = -I$(PIDGIN_TREE_TOP)/libpurple -I$(PIDGIN_TREE_TOP) $(WIN32_CFLAGS)
74 | WIN32_PIDGIN3_CFLAGS = -I$(PIDGIN3_TREE_TOP)/libpurple -I$(PIDGIN3_TREE_TOP) -I$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_CFLAGS)
75 | WIN32_PIDGIN2_LDFLAGS = -L$(PIDGIN_TREE_TOP)/libpurple $(WIN32_LDFLAGS)
76 | WIN32_PIDGIN3_LDFLAGS = -L$(PIDGIN3_TREE_TOP)/libpurple -L$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_LDFLAGS) -lgplugin
77 |
78 | C_FILES := hangouts.pb-c.c hangout_media.pb-c.c gmail.pb-c.c hangouts_json.c hangouts_pblite.c hangouts_connection.c hangouts_auth.c hangouts_events.c hangouts_conversation.c hangouts_media.c
79 | PURPLE_COMPAT_FILES := purple2compat/http.c purple2compat/purple-socket.c
80 | PURPLE_C_FILES := libhangouts.c $(C_FILES)
81 | TEST_C_FILES := hangouts_test.c $(C_FILES)
82 |
83 |
84 |
85 | .PHONY: all install FAILNOPURPLE clean
86 |
87 | all: $(HANGOUTS_TARGET)
88 |
89 | hangouts.pb-c.c: hangouts.proto
90 | $(PROTOC_C) --c_out=. hangouts.proto
91 |
92 | hangout_media.pb-c.c: hangout_media.proto
93 | $(PROTOC_C) --c_out=. hangout_media.proto
94 |
95 | gmail.pb-c.c: gmail.proto
96 | $(PROTOC_C) --c_out=. gmail.proto
97 |
98 | libhangouts.so: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES)
99 | $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple glib-2.0 json-glib-1.0 zlib --libs --cflags` -ldl $(INCLUDES) -Ipurple2compat -g -ggdb
100 |
101 | libhangouts3.so: $(PURPLE_C_FILES)
102 | $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple-3 glib-2.0 json-glib-1.0 zlib --libs --cflags` -ldl $(INCLUDES) -g -ggdb
103 |
104 | libhangouts.dll: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES)
105 | $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat
106 |
107 | libhangouts3.dll: $(PURPLE_C_FILES)
108 | $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN3_CFLAGS) $(WIN32_PIDGIN3_LDFLAGS)
109 |
110 | hangouts-test.exe: $(TEST_C_FILES) $(PURPLE_COMPAT_FILES)
111 | $(WIN32_CC) -o $@ -DDEBUG $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat
112 |
113 | install: $(HANGOUTS_TARGET) install-icons
114 | mkdir -p $(HANGOUTS_DEST)
115 | install -p $(HANGOUTS_TARGET) $(HANGOUTS_DEST)
116 |
117 | install-icons: hangouts16.png hangouts22.png hangouts48.png
118 | mkdir -p $(HANGOUTS_ICONS_DEST)/16
119 | mkdir -p $(HANGOUTS_ICONS_DEST)/22
120 | mkdir -p $(HANGOUTS_ICONS_DEST)/48
121 | install hangouts16.png $(HANGOUTS_ICONS_DEST)/16/hangouts.png
122 | install hangouts22.png $(HANGOUTS_ICONS_DEST)/22/hangouts.png
123 | install hangouts48.png $(HANGOUTS_ICONS_DEST)/48/hangouts.png
124 |
125 | FAILNOPURPLE:
126 | echo "You need libpurple development headers installed to be able to compile this plugin"
127 |
128 | clean:
129 | rm -f $(HANGOUTS_TARGET) hangouts.pb-c.h hangouts.pb-c.c hangout_media.pb-c.h hangout_media.pb-c.c
130 |
131 |
132 | installer: purple-hangouts.nsi libhangouts.dll
133 | $(MAKENSIS) purple-hangouts.nsi
134 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Hangouts Plugin for libpurple #
3 |
4 | A replacement prpl for Hangouts in Pidgin/libpurple to support the proprietary protocol that Google uses for its Hangouts service. So far it supports all the fun things that aren't part of the XMPP interface, such as Group Chats, synchronised history between devices and SMS support via Google Voice.
5 |
6 | This plugin is written by [Eion Robb](http://eion.robbmob.com/blog/) and [Mike 'Maiku' Ruprecht](https://bitbucket.org/CMaiku/).
7 | Heavily inspired by the [hangups library](https://github.com/tdryer/hangups) by Tom Dryer (et. al.) using code from [Nakul Gulati](https://hg.pidgin.im/soc/2015/nakulgulati/main/) and protobufs from [Darryl Pogue](http://dpogue.ca/)
8 |
9 | Please read [the FAQ](https://github.com/EionRobb/purple-hangouts/wiki#faq) before posting any issues
10 |
11 | ## Compiling ##
12 | To compile, just do the standard `make && sudo make install` dance. You'll need development packages for libpurple, libjson-glib, glib and libprotobuf-c to be able to compile.
13 |
14 | ## Debian/Ubuntu ##
15 | Run the following commands from a terminal
16 |
17 | ```
18 | #!sh
19 | sudo apt-get install -y libpurple-dev libjson-glib-dev libglib2.0-dev libprotobuf-c-dev protobuf-c-compiler git make;
20 | git clone https://github.com/EionRobb/purple-hangouts && cd purple-hangouts;
21 | make && sudo make install
22 | ```
23 |
24 | ## Fedora ##
25 | On Fedora you can install [package](https://apps.fedoraproject.org/packages/purple-hangouts) from Fedora's main repository:
26 | ```
27 | #!sh
28 | sudo dnf install purple-hangouts pidgin-hangouts
29 | ```
30 |
31 | ## CentOS/RHEL ##
32 | On CentOS/RHEL you can install [package](https://apps.fedoraproject.org/packages/purple-hangouts) from Fedora's [EPEL7](http://fedoraproject.org/wiki/EPEL) repository:
33 | ```
34 | #!sh
35 | sudo yum install purple-hangouts pidgin-hangouts
36 | ```
37 |
38 | ## Arch Linux ##
39 | On Arch Linux you can install a [package](https://aur.archlinux.org/packages/purple-hangouts-hg) from the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository):
40 | ```
41 | #!sh
42 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/purple-hangouts-hg.tar.gz
43 | tar -xvf purple-hangouts-hg.tar.gz
44 | cd purple-hangouts-hg
45 | makepkg -sri
46 | ```
47 |
48 | ## Building RPM package for Fedora/openSUSE/CentOS/RHEL ##
49 | ```
50 | #!sh
51 | mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
52 | wget https://raw.githubusercontent.com/EionRobb/purple-hangouts/master/purple-hangouts.spec -O ~/rpmbuild/SPECS/purple-hangouts.spec
53 | sudo dnf builddep ~/rpmbuild/SPECS/purple-hangouts.spec
54 | spectool --all --get-files ~/rpmbuild/SPECS/purple-hangouts.spec --directory ~/rpmbuild/SOURCES/
55 | rpmbuild -ba ~/rpmbuild/SPECS/purple-hangouts.spec
56 | ```
57 | The result can be found in ``~/rpmbuild/RPMS/`uname -m`/`` directory.
58 |
59 | ## Windows ##
60 | Use the [Windows installer](http://eion.robbmob.com/purple-hangouts.exe) to make life easier, otherwise development builds of Windows dll's live at http://eion.robbmob.com/libhangouts.dll (you'll also need libprotobuf-c-1.dll and libjson-glib-1.0.dll in your Pidgin folder, included in the installer)
61 |
62 | ## Like this plugin? ##
63 | Say "Thanks" by [sending us $1](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=PZMBF2QVF69GA)
64 |
--------------------------------------------------------------------------------
/convey.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | import:
3 | type: docker/import
4 | files:
5 | - .:.
6 | debian-stretch-amd64:
7 | type: docker/run
8 | image: pidgin/pidgin2-dev:debian-stretch-amd64
9 | script:
10 | - set -ex
11 | - make
12 | - mv libhangouts.so libhangouts-debian-stretch-amd64.so
13 | workdir: ${CONVEY_WORKSPACE}
14 | plans:
15 | default:
16 | stages:
17 | - name: setup
18 | tasks:
19 | - import
20 | - name: build
21 | tasks:
22 | - debian-stretch-amd64
23 |
24 |
--------------------------------------------------------------------------------
/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_*/
--------------------------------------------------------------------------------
/gmail.proto:
--------------------------------------------------------------------------------
1 |
2 | // proto2 is required because we need to be able to serialize default values:
3 | syntax = "proto2";
4 |
5 |
6 | message GmailNotification {
7 | optional string thread_id = 3;
8 | //optional string previous_id = 4;
9 | optional string id = 5;
10 | repeated string labels = 6;
11 | optional string subject = 7;
12 | optional string snippet = 8;
13 | optional string sender_name = 9;
14 | optional string sender_email = 10;
15 | //repeated unknown = 11;
16 | }
17 |
--------------------------------------------------------------------------------
/hangouts16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EionRobb/purple-hangouts/55b9f01d040b240b794700f44d9c21a6cb51251e/hangouts16.png
--------------------------------------------------------------------------------
/hangouts22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EionRobb/purple-hangouts/55b9f01d040b240b794700f44d9c21a6cb51251e/hangouts22.png
--------------------------------------------------------------------------------
/hangouts24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EionRobb/purple-hangouts/55b9f01d040b240b794700f44d9c21a6cb51251e/hangouts24.png
--------------------------------------------------------------------------------
/hangouts48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EionRobb/purple-hangouts/55b9f01d040b240b794700f44d9c21a6cb51251e/hangouts48.png
--------------------------------------------------------------------------------
/hangouts_auth.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #include "hangouts_auth.h"
20 |
21 | #include "core.h"
22 | #include "debug.h"
23 | #include "http.h"
24 | #include "hangouts_json.h"
25 | #include "hangouts_connection.h"
26 | #include "hangouts_conversation.h"
27 |
28 |
29 | typedef struct {
30 | gpointer unused1;
31 | gpointer unused2;
32 | gpointer unused3;
33 | gpointer unused4;
34 | gpointer unused5;
35 | int unused6;
36 | int unused7;
37 | int unused8;
38 | int unused9;
39 |
40 | gpointer set;
41 | } bitlbee_account_t;
42 |
43 | typedef struct {
44 | bitlbee_account_t *acc;
45 | } bitlbee_im_connection;
46 |
47 | static gpointer bitlbee_module;
48 | static bitlbee_im_connection *(*bitlbee_purple_ic_by_pa)(PurpleAccount *);
49 | static int (*bitlbee_set_setstr)(gpointer *, const char *, const char *);
50 | static gboolean bitlbee_password_funcs_loaded = FALSE;
51 |
52 | #ifdef _WIN32
53 | # include
54 | # define dlopen(filename, flag) GetModuleHandleA(filename)
55 | # define dlsym(handle, symbol) GetProcAddress(handle, symbol)
56 | # define dlclose(handle) FreeLibrary(handle)
57 | static gchar *last_dlopen_error = NULL;
58 | # define dlerror() (g_free(last_dlopen_error),last_dlopen_error=g_win32_error_message(GetLastError()))
59 | # define RTLD_LAZY 0x0001
60 | #else
61 | # include
62 | #endif
63 |
64 | static void
65 | save_bitlbee_password(PurpleAccount *account, const gchar *password)
66 | {
67 | bitlbee_account_t *acc;
68 | bitlbee_im_connection *imconn;
69 |
70 | gboolean result = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_accounts_get_handle(), "bitlbee-set-account-password", account, password));
71 |
72 | if (result) {
73 | return;
74 | }
75 |
76 | if (bitlbee_password_funcs_loaded == FALSE) {
77 | bitlbee_module = dlopen(NULL, RTLD_LAZY);
78 | if (bitlbee_module == NULL) {
79 | purple_debug_error("hangouts", "Couldn't acquire address of bitlbee handle: %s\n", dlerror());
80 | g_return_if_fail(bitlbee_module);
81 | }
82 |
83 | bitlbee_purple_ic_by_pa = (gpointer) dlsym(bitlbee_module, "purple_ic_by_pa");
84 | bitlbee_set_setstr = (gpointer) dlsym(bitlbee_module, "set_setstr");
85 |
86 | bitlbee_password_funcs_loaded = TRUE;
87 | }
88 |
89 | imconn = bitlbee_purple_ic_by_pa(account);
90 | acc = imconn->acc;
91 | bitlbee_set_setstr(&acc->set, "password", password ? password : "");
92 | }
93 |
94 | static void
95 | hangouts_save_refresh_token_password(PurpleAccount *account, const gchar *password)
96 | {
97 | purple_account_set_password(account, password, NULL, NULL);
98 |
99 | if (g_strcmp0(purple_core_get_ui(), "BitlBee") == 0) {
100 | save_bitlbee_password(account, password);
101 | }
102 | }
103 |
104 |
105 | static void
106 | hangouts_oauth_refresh_token_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
107 | {
108 | HangoutsAccount *ha = user_data;
109 | JsonObject *obj;
110 | const gchar *raw_response;
111 | gsize response_len;
112 |
113 | raw_response = purple_http_response_get_data(response, &response_len);
114 | obj = json_decode_object(raw_response, response_len);
115 |
116 | if (purple_http_response_is_successful(response) && obj)
117 | {
118 | ha->access_token = g_strdup(json_object_get_string_member(obj, "access_token"));
119 | hangouts_auth_get_session_cookies(ha);
120 | } else {
121 | if (obj != NULL) {
122 | if (json_object_has_member(obj, "error")) {
123 | if (g_strcmp0(json_object_get_string_member(obj, "error"), "invalid_grant") == 0) {
124 | hangouts_save_refresh_token_password(ha->account, NULL);
125 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
126 | json_object_get_string_member(obj, "error_description"));
127 | } else {
128 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
129 | json_object_get_string_member(obj, "error_description"));
130 | }
131 | } else {
132 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
133 | _("Invalid response"));
134 | }
135 | }
136 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
137 | _("Invalid response"));
138 | }
139 |
140 | json_object_unref(obj);
141 | }
142 |
143 | void
144 | hangouts_oauth_refresh_token(HangoutsAccount *ha)
145 | {
146 | PurpleHttpRequest *request;
147 | PurpleConnection *pc;
148 | GString *postdata;
149 |
150 | pc = ha->pc;
151 |
152 | postdata = g_string_new(NULL);
153 | g_string_append_printf(postdata, "client_id=%s&", purple_url_encode(GOOGLE_CLIENT_ID));
154 | g_string_append_printf(postdata, "client_secret=%s&", purple_url_encode(GOOGLE_CLIENT_SECRET));
155 | g_string_append_printf(postdata, "refresh_token=%s&", purple_url_encode(ha->refresh_token));
156 | g_string_append(postdata, "grant_type=refresh_token&");
157 |
158 | request = purple_http_request_new(HANGOUTS_API_OAUTH2_TOKEN_URL);
159 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
160 | purple_http_request_set_method(request, "POST");
161 | purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
162 | purple_http_request_set_contents(request, postdata->str, postdata->len);
163 |
164 | purple_http_request(pc, request, hangouts_oauth_refresh_token_cb, ha);
165 | purple_http_request_unref(request);
166 |
167 | g_string_free(postdata, TRUE);
168 | }
169 |
170 |
171 | static void
172 | hangouts_oauth_with_code_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
173 | {
174 | HangoutsAccount *ha = user_data;
175 | JsonObject *obj;
176 | const gchar *raw_response;
177 | gsize response_len;
178 | PurpleAccount *account = ha->account;
179 |
180 | raw_response = purple_http_response_get_data(response, &response_len);
181 | obj = json_decode_object(raw_response, response_len);
182 |
183 | if (purple_http_response_is_successful(response) && obj)
184 | {
185 | ha->access_token = g_strdup(json_object_get_string_member(obj, "access_token"));
186 | ha->refresh_token = g_strdup(json_object_get_string_member(obj, "refresh_token"));
187 |
188 | purple_account_set_remember_password(account, TRUE);
189 | hangouts_save_refresh_token_password(account, ha->refresh_token);
190 |
191 | hangouts_auth_get_session_cookies(ha);
192 | } else {
193 | if (obj != NULL) {
194 | if (json_object_has_member(obj, "error")) {
195 | if (g_strcmp0(json_object_get_string_member(obj, "error"), "invalid_grant") == 0) {
196 | hangouts_save_refresh_token_password(ha->account, NULL);
197 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
198 | json_object_get_string_member(obj, "error_description"));
199 | } else {
200 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
201 | json_object_get_string_member(obj, "error_description"));
202 | }
203 | } else {
204 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
205 | _("Invalid response"));
206 | }
207 | }
208 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
209 | _("Invalid response"));
210 | }
211 |
212 | json_object_unref(obj);
213 | }
214 |
215 | void
216 | hangouts_oauth_with_code(HangoutsAccount *ha, const gchar *auth_code)
217 | {
218 | PurpleHttpRequest *request;
219 | PurpleConnection *pc;
220 | GString *postdata;
221 |
222 | pc = ha->pc;
223 |
224 | postdata = g_string_new(NULL);
225 | g_string_append_printf(postdata, "client_id=%s&", purple_url_encode(GOOGLE_CLIENT_ID));
226 | g_string_append_printf(postdata, "client_secret=%s&", purple_url_encode(GOOGLE_CLIENT_SECRET));
227 | g_string_append_printf(postdata, "code=%s&", purple_url_encode(auth_code));
228 | g_string_append_printf(postdata, "redirect_uri=%s&", purple_url_encode(HANGOUTS_API_OAUTH2_REDIRECT_URI));
229 | g_string_append(postdata, "grant_type=authorization_code&");
230 |
231 | request = purple_http_request_new(HANGOUTS_API_OAUTH2_TOKEN_URL);
232 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
233 | purple_http_request_set_method(request, "POST");
234 | purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
235 | purple_http_request_set_contents(request, postdata->str, postdata->len);
236 |
237 | purple_http_request(pc, request, hangouts_oauth_with_code_cb, ha);
238 | purple_http_request_unref(request);
239 |
240 | g_string_free(postdata, TRUE);
241 | }
242 |
243 |
244 | /*****************************************************************************/
245 |
246 |
247 | void
248 | hangouts_auth_get_session_cookies_got_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
249 | {
250 | HangoutsAccount *ha = user_data;
251 | guint64 last_event_timestamp;
252 |
253 | gchar *sapisid_cookie = purple_http_cookie_jar_get(ha->cookie_jar, "SAPISID");
254 |
255 | if (sapisid_cookie == NULL) {
256 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
257 | _("SAPISID Cookie not received"));
258 | return;
259 | }
260 |
261 | purple_http_cookie_jar_set(ha->cookie_jar, "__Secure-3PSID", NULL);
262 | purple_http_cookie_jar_set(ha->cookie_jar, "__Host-3PLSID", NULL);
263 | purple_http_cookie_jar_set(ha->cookie_jar, "__Secure-3PAPISID", NULL);
264 | purple_http_cookie_jar_set(ha->cookie_jar, "__Host-GAPS", NULL);
265 |
266 | //Restore the last_event_timestamp before it gets overridden by new events
267 | last_event_timestamp = purple_account_get_int(ha->account, "last_event_timestamp_high", 0);
268 | if (last_event_timestamp != 0) {
269 | last_event_timestamp = (last_event_timestamp << 32) | ((guint64) purple_account_get_int(ha->account, "last_event_timestamp_low", 0) & 0xFFFFFFFF);
270 | ha->last_event_timestamp = last_event_timestamp;
271 | }
272 |
273 | // SOUND THE TRUMPETS
274 | hangouts_fetch_channel_sid(ha);
275 | purple_connection_set_state(ha->pc, PURPLE_CONNECTION_CONNECTED);
276 |
277 | //TODO trigger event instead
278 | hangouts_get_self_info(ha);
279 | hangouts_get_conversation_list(ha);
280 | ha->poll_buddy_status_timeout = g_timeout_add_seconds(120, hangouts_poll_buddy_status, ha);
281 |
282 | g_free(sapisid_cookie);
283 | }
284 |
285 | static void
286 | hangouts_auth_get_session_cookies_uberauth_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
287 | {
288 | HangoutsAccount *ha = user_data;
289 | PurpleHttpRequest *request;
290 | const gchar *uberauth;
291 |
292 | uberauth = purple_http_response_get_data(response, NULL);
293 |
294 | if (purple_http_response_get_error(response) != NULL) {
295 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
296 | _("Auth error"));
297 | return;
298 | }
299 |
300 | request = purple_http_request_new(NULL);
301 | purple_http_request_set_url_printf(request, "https://accounts.google.com/MergeSession" "?service=mail&continue=http://www.google.com&uberauth=%s", purple_url_encode(uberauth));
302 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
303 | purple_http_request_header_set_printf(request, "Authorization", "Bearer %s", ha->access_token);
304 | purple_http_request_set_max_redirects(request, 0);
305 |
306 | purple_http_request(ha->pc, request, hangouts_auth_get_session_cookies_got_cb, ha);
307 | purple_http_request_unref(request);
308 | }
309 |
310 | void
311 | hangouts_auth_get_session_cookies(HangoutsAccount *ha)
312 | {
313 | PurpleHttpRequest *request;
314 |
315 | request = purple_http_request_new("https://accounts.google.com/accounts/OAuthLogin"
316 | "?source=pidgin&issueuberauth=1");
317 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
318 | purple_http_request_header_set_printf(request, "Authorization", "Bearer %s", ha->access_token);
319 |
320 | purple_http_request(ha->pc, request, hangouts_auth_get_session_cookies_uberauth_cb, ha);
321 | purple_http_request_unref(request);
322 | }
323 |
--------------------------------------------------------------------------------
/hangouts_auth.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_AUTH_H_
21 | #define _HANGOUTS_AUTH_H_
22 |
23 | #include "libhangouts.h"
24 |
25 | void hangouts_oauth_with_code(HangoutsAccount *ha, const gchar *auth_code);
26 | void hangouts_oauth_refresh_token(HangoutsAccount *ha);
27 | void hangouts_auth_get_session_cookies(HangoutsAccount *ha);
28 |
29 | #endif /*_HANGOUTS_AUTH_H_*/
--------------------------------------------------------------------------------
/hangouts_connection.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #define PURPLE_PLUGINS
20 |
21 |
22 | #include "hangouts_connection.h"
23 |
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | #include "debug.h"
31 | #include "request.h"
32 |
33 | #include "hangouts_pblite.h"
34 | #include "hangouts_json.h"
35 | #include "hangouts.pb-c.h"
36 | #include "hangouts_conversation.h"
37 |
38 | #include "gmail.pb-c.h"
39 |
40 | void
41 | hangouts_process_data_chunks(HangoutsAccount *ha, const gchar *data, gsize len)
42 | {
43 | JsonArray *chunks;
44 | guint i, num_chunks;
45 |
46 | chunks = json_decode_array(data, len);
47 |
48 | for (i = 0, num_chunks = json_array_get_length(chunks); i < num_chunks; i++) {
49 | JsonArray *chunk;
50 | JsonArray *array;
51 | JsonNode *array0;
52 |
53 | chunk = json_array_get_array_element(chunks, i);
54 |
55 | array = json_array_get_array_element(chunk, 1);
56 | array0 = json_array_get_element(array, 0);
57 | if (JSON_NODE_HOLDS_VALUE(array0)) {
58 | //probably a nooooop
59 | if (g_strcmp0(json_node_get_string(array0), "noop") == 0) {
60 | //A nope ninja delivers a wicked dragon kick
61 | #ifdef DEBUG
62 | printf("noop\n");
63 | #endif
64 | }
65 | } else {
66 | const gchar *p = json_object_get_string_member(json_node_get_object(array0), "p");
67 | JsonObject *wrapper = json_decode_object(p, -1);
68 |
69 | if (wrapper == NULL) {
70 | continue;
71 | }
72 |
73 | if (json_object_has_member(wrapper, "3")) {
74 | const gchar *new_client_id = json_object_get_string_member(json_object_get_object_member(wrapper, "3"), "2");
75 | purple_debug_info("hangouts", "Received new client_id: %s\n", new_client_id);
76 |
77 | g_free(ha->client_id);
78 | ha->client_id = g_strdup(new_client_id);
79 |
80 | hangouts_add_channel_services(ha);
81 | hangouts_set_active_client(ha->pc);
82 | hangouts_set_status(ha->account, purple_account_get_active_status(ha->account));
83 | }
84 | if (json_object_has_member(wrapper, "2")) {
85 | const gchar *wrapper22 = json_object_get_string_member(json_object_get_object_member(wrapper, "2"), "2");
86 | JsonArray *pblite_message = json_decode_array(wrapper22, -1);
87 | const gchar *message_type;
88 |
89 | if (pblite_message == NULL) {
90 | #ifdef DEBUG
91 | printf("bad wrapper22 %s\n", wrapper22);
92 | #endif
93 | json_object_unref(wrapper);
94 | continue;
95 | }
96 |
97 | message_type = json_array_get_string_element(pblite_message, 0);
98 |
99 | //cbu == ClientBatchUpdate
100 | if (purple_strequal(message_type, "cbu")) {
101 | BatchUpdate batch_update = BATCH_UPDATE__INIT;
102 | guint j;
103 |
104 | #ifdef DEBUG
105 | printf("----------------------\n");
106 | #endif
107 | pblite_decode((ProtobufCMessage *) &batch_update, pblite_message, TRUE);
108 | #ifdef DEBUG
109 | printf("======================\n");
110 | printf("Is valid? %s\n", protobuf_c_message_check((ProtobufCMessage *) &batch_update) ? "Yes" : "No");
111 | printf("======================\n");
112 | printf("CBU %s", pblite_dump_json((ProtobufCMessage *)&batch_update));
113 | JsonArray *debug = pblite_encode((ProtobufCMessage *) &batch_update);
114 | JsonNode *node = json_node_new(JSON_NODE_ARRAY);
115 | json_node_take_array(node, debug);
116 | gchar *json = json_encode(node, NULL);
117 | printf("Old: %s\nNew: %s\n", wrapper22, json);
118 |
119 |
120 | pblite_decode((ProtobufCMessage *) &batch_update, debug, TRUE);
121 | debug = pblite_encode((ProtobufCMessage *) &batch_update);
122 | json_node_take_array(node, debug);
123 | gchar *json2 = json_encode(node, NULL);
124 | printf("Mine1: %s\nMine2: %s\n", json, json2);
125 |
126 | g_free(json);
127 | g_free(json2);
128 | printf("----------------------\n");
129 | #endif
130 | for(j = 0; j < batch_update.n_state_update; j++) {
131 | purple_signal_emit(purple_connection_get_protocol(ha->pc), "hangouts-received-stateupdate", ha->pc, batch_update.state_update[j]);
132 | }
133 | } else if (purple_strequal(message_type, "n_nm")) {
134 | GmailNotification gmail_notification = GMAIL_NOTIFICATION__INIT;
135 | const gchar *username = json_object_get_string_member(json_object_get_object_member(json_object_get_object_member(wrapper, "2"), "1"), "2");
136 |
137 | pblite_decode((ProtobufCMessage *) &gmail_notification, pblite_message, TRUE);
138 | purple_signal_emit(purple_connection_get_protocol(ha->pc), "hangouts-gmail-notification", ha->pc, username, &gmail_notification);
139 | }
140 |
141 | json_array_unref(pblite_message);
142 | }
143 |
144 | json_object_unref(wrapper);
145 | }
146 | }
147 |
148 | json_array_unref(chunks);
149 | }
150 |
151 | static int
152 | read_all(int fd, void *buf, size_t len)
153 | {
154 | unsigned int rs = 0;
155 | while(rs < len)
156 | {
157 | int rval = read(fd, buf + rs, len - rs);
158 | if (rval == 0)
159 | break;
160 | if (rval < 0)
161 | return rval;
162 |
163 | rs += rval;
164 | }
165 | return rs;
166 | }
167 |
168 | void
169 | hangouts_process_channel(int fd)
170 | {
171 | gsize len, lenpos = 0;
172 | gchar len_str[256];
173 | gchar *chunk;
174 |
175 | while(read(fd, len_str + lenpos, 1) > 0) {
176 | //read up until \n
177 | if (len_str[lenpos] == '\n') {
178 | //convert to int, use as length of string to read
179 | len_str[lenpos] = '\0';
180 | #ifdef DEBUG
181 | printf("len_str is %s\n", len_str);
182 | #endif
183 | len = atoi(len_str);
184 |
185 | chunk = g_new(gchar, len * 2);
186 | //XX - could be a utf-16 length*2 though, so read up until \n????
187 |
188 | if (read_all(fd, chunk, len) > 0) {
189 | //throw chunk to hangouts_process_data_chunks
190 | hangouts_process_data_chunks(NULL, chunk, len);
191 | }
192 |
193 | g_free(chunk);
194 |
195 | lenpos = 0;
196 | } else {
197 | lenpos = lenpos + 1;
198 | }
199 | }
200 | }
201 |
202 | void
203 | hangouts_process_channel_buffer(HangoutsAccount *ha)
204 | {
205 | const gchar *bufdata;
206 | gsize bufsize;
207 | gchar *len_end;
208 | gchar *len_str;
209 | guint len_len; //len len len len len len len len len
210 | gsize len;
211 |
212 | g_return_if_fail(ha);
213 | g_return_if_fail(ha->channel_buffer);
214 |
215 | while (ha->channel_buffer->len) {
216 | bufdata = (gchar *) ha->channel_buffer->data;
217 | bufsize = ha->channel_buffer->len;
218 |
219 | len_end = g_strstr_len(bufdata, bufsize, "\n");
220 | if (len_end == NULL) {
221 | // Not enough data to read
222 | if (purple_debug_is_verbose()) {
223 | purple_debug_info("hangouts", "Couldn't find length of chunk\n");
224 | }
225 | return;
226 | }
227 | len_len = len_end - bufdata;
228 | len_str = g_strndup(bufdata, len_len);
229 | len = (gsize) atoi(len_str);
230 | g_free(len_str);
231 |
232 | // Len was 0 ? Must have been a bad read :(
233 | g_return_if_fail(len);
234 |
235 | bufsize = bufsize - len_len - 1;
236 |
237 | if (len > bufsize) {
238 | // Not enough data to read
239 | if (purple_debug_is_verbose()) {
240 | purple_debug_info("hangouts", "Couldn't read %" G_GSIZE_FORMAT " bytes when we only have %" G_GSIZE_FORMAT "\n", len, bufsize);
241 | }
242 | return;
243 | }
244 |
245 | hangouts_process_data_chunks(ha, bufdata + len_len + 1, len);
246 |
247 | g_byte_array_remove_range(ha->channel_buffer, 0, len + len_len + 1);
248 |
249 | }
250 | }
251 |
252 | static void
253 | hangouts_set_auth_headers(HangoutsAccount *ha, PurpleHttpRequest *request)
254 | {
255 | gint64 mstime;
256 | gchar *mstime_str;
257 | GTimeVal time;
258 | GChecksum *hash;
259 | const gchar *sha1;
260 | gchar *sapisid_cookie;
261 |
262 | g_get_current_time(&time);
263 | mstime = (((gint64) time.tv_sec) * 1000) + (time.tv_usec / 1000);
264 | mstime_str = g_strdup_printf("%" G_GINT64_FORMAT, mstime);
265 | sapisid_cookie = purple_http_cookie_jar_get(ha->cookie_jar, "SAPISID");
266 |
267 | hash = g_checksum_new(G_CHECKSUM_SHA1);
268 | g_checksum_update(hash, (guchar *) mstime_str, strlen(mstime_str));
269 | g_checksum_update(hash, (guchar *) " ", 1);
270 | if (sapisid_cookie && *sapisid_cookie) {
271 | // Should we just bail out if we dont have the cookie?
272 | g_checksum_update(hash, (guchar *) sapisid_cookie, strlen(sapisid_cookie));
273 | }
274 | g_checksum_update(hash, (guchar *) " ", 1);
275 | g_checksum_update(hash, (guchar *) HANGOUTS_PBLITE_XORIGIN_URL, strlen(HANGOUTS_PBLITE_XORIGIN_URL));
276 | sha1 = g_checksum_get_string(hash);
277 |
278 | purple_http_request_header_set_printf(request, "Authorization", "SAPISIDHASH %s_%s", mstime_str, sha1);
279 | purple_http_request_header_set(request, "X-Origin", HANGOUTS_PBLITE_XORIGIN_URL);
280 | purple_http_request_header_set(request, "X-Goog-AuthUser", "0");
281 |
282 | g_free(sapisid_cookie);
283 | g_free(mstime_str);
284 | g_checksum_free(hash);
285 | }
286 |
287 |
288 | static gboolean
289 | hangouts_longpoll_request_content(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, const gchar *buffer, size_t offset, size_t length, gpointer user_data)
290 | {
291 | HangoutsAccount *ha = user_data;
292 |
293 | ha->last_data_received = time(NULL);
294 |
295 | if (!purple_http_response_is_successful(response)) {
296 | purple_debug_error("hangouts", "longpoll_request_content had error: '%s'\n", purple_http_response_get_error(response));
297 | return FALSE;
298 | }
299 |
300 | g_byte_array_append(ha->channel_buffer, (guint8 *) buffer, length);
301 |
302 | hangouts_process_channel_buffer(ha);
303 |
304 | return TRUE;
305 | }
306 |
307 | static void
308 | hangouts_longpoll_request_closed(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
309 | {
310 | HangoutsAccount *ha = user_data;
311 |
312 | if (!PURPLE_IS_CONNECTION(purple_http_conn_get_purple_connection(http_conn))) {
313 | return;
314 | }
315 |
316 | if (ha->channel_watchdog) {
317 | g_source_remove(ha->channel_watchdog);
318 | ha->channel_watchdog = 0;
319 | }
320 |
321 | // remaining data 'should' have been dealt with in hangouts_longpoll_request_content
322 | g_byte_array_free(ha->channel_buffer, TRUE);
323 | ha->channel_buffer = g_byte_array_sized_new(HANGOUTS_BUFFER_DEFAULT_SIZE);
324 |
325 | if (purple_http_response_get_error(response) != NULL) {
326 | //TODO error checking
327 | purple_debug_error("hangouts", "longpoll_request_closed %d %s\n", purple_http_response_get_code(response), purple_http_response_get_error(response));
328 | hangouts_fetch_channel_sid(ha);
329 | } else {
330 | hangouts_longpoll_request(ha);
331 | }
332 | }
333 |
334 | static gboolean
335 | channel_watchdog_check(gpointer data)
336 | {
337 | PurpleConnection *pc = data;
338 | HangoutsAccount *ha;
339 | PurpleHttpConnection *conn;
340 |
341 | if (PURPLE_IS_CONNECTION(pc)) {
342 | ha = purple_connection_get_protocol_data(pc);
343 | conn = ha->channel_connection;
344 |
345 | if (ha->last_data_received && ha->last_data_received < (time(NULL) - 60)) {
346 | // should have been something within the last 60 seconds
347 | purple_http_conn_cancel(conn);
348 | ha->last_data_received = 0;
349 | }
350 |
351 | if (!purple_http_conn_is_running(conn)) {
352 | hangouts_longpoll_request(ha);
353 | }
354 |
355 | return TRUE;
356 | }
357 |
358 | return FALSE;
359 | }
360 |
361 | void
362 | hangouts_longpoll_request(HangoutsAccount *ha)
363 | {
364 | PurpleHttpRequest *request;
365 | GString *url;
366 |
367 |
368 | url = g_string_new(HANGOUTS_CHANNEL_URL_PREFIX "channel/bind" "?");
369 | g_string_append(url, "VER=8&"); // channel protocol version
370 | g_string_append_printf(url, "gsessionid=%s&", purple_url_encode(ha->gsessionid_param));
371 | g_string_append(url, "RID=rpc&"); // request identifier
372 | g_string_append(url, "t=1&"); // trial
373 | g_string_append_printf(url, "SID=%s&", purple_url_encode(ha->sid_param)); // session ID
374 | g_string_append(url, "CI=0&"); // 0 if streaming/chunked requests should be used
375 | g_string_append(url, "ctype=hangouts&"); // client type
376 | g_string_append(url, "TYPE=xmlhttp&"); // type of request
377 |
378 | request = purple_http_request_new(NULL);
379 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
380 | purple_http_request_set_url(request, url->str);
381 | purple_http_request_set_timeout(request, -1); // to infinity and beyond!
382 | purple_http_request_set_response_writer(request, hangouts_longpoll_request_content, ha);
383 | purple_http_request_set_keepalive_pool(request, ha->channel_keepalive_pool);
384 |
385 | hangouts_set_auth_headers(ha, request);
386 |
387 | ha->channel_connection = purple_http_request(ha->pc, request, hangouts_longpoll_request_closed, ha);
388 |
389 | g_string_free(url, TRUE);
390 |
391 | if (ha->channel_watchdog) {
392 | g_source_remove(ha->channel_watchdog);
393 | }
394 | ha->channel_watchdog = g_timeout_add_seconds(1, channel_watchdog_check, ha->pc);
395 | }
396 |
397 |
398 |
399 | static void
400 | hangouts_send_maps_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
401 | {
402 | /*111
403 | * [
404 | * [0,["c","","",8]],
405 | * [1,[{"gsid":""}]]
406 | * ]
407 | */
408 | JsonNode *node;
409 | HangoutsAccount *ha = user_data;
410 | const gchar *res_raw;
411 | gchar *json_start;
412 | size_t res_len;
413 | gchar *gsid;
414 | gchar *sid;
415 |
416 | if (purple_http_response_get_error(response) != NULL) {
417 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, purple_http_response_get_error(response));
418 | return;
419 | }
420 |
421 | res_raw = purple_http_response_get_data(response, &res_len);
422 | json_start = g_strstr_len(res_raw, res_len, "\n");
423 | if (json_start == NULL) {
424 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "Blank maps response");
425 | return;
426 | }
427 | *json_start = '\0';
428 | json_start++;
429 | node = json_decode(json_start, atoi(res_raw));
430 | sid = hangouts_json_path_query_string(node, "$[0][1][1]", NULL);
431 | gsid = hangouts_json_path_query_string(node, "$[1][1][0].gsid", NULL);
432 |
433 | if (sid != NULL) {
434 | g_free(ha->sid_param);
435 | ha->sid_param = sid;
436 | }
437 | if (gsid != NULL) {
438 | g_free(ha->gsessionid_param);
439 | ha->gsessionid_param = gsid;
440 | }
441 |
442 | json_node_free(node);
443 |
444 | hangouts_longpoll_request(ha);
445 | }
446 |
447 | void
448 | hangouts_fetch_channel_sid(HangoutsAccount *ha)
449 | {
450 | g_free(ha->sid_param);
451 | g_free(ha->gsessionid_param);
452 | ha->sid_param = NULL;
453 | ha->gsessionid_param = NULL;
454 |
455 | hangouts_send_maps(ha, NULL, hangouts_send_maps_cb);
456 | }
457 |
458 | void
459 | hangouts_send_maps(HangoutsAccount *ha, JsonArray *map_list, PurpleHttpCallback send_maps_callback)
460 | {
461 | PurpleHttpRequest *request;
462 | GString *url, *postdata;
463 | guint map_list_len, i;
464 |
465 | url = g_string_new(HANGOUTS_CHANNEL_URL_PREFIX "channel/bind" "?");
466 | g_string_append(url, "VER=8&"); // channel protocol version
467 | g_string_append(url, "RID=81188&"); // request identifier
468 | g_string_append(url, "ctype=hangouts&"); // client type
469 | if (ha->gsessionid_param)
470 | g_string_append_printf(url, "gsessionid=%s&", purple_url_encode(ha->gsessionid_param));
471 | if (ha->sid_param)
472 | g_string_append_printf(url, "SID=%s&", purple_url_encode(ha->sid_param)); // session ID
473 |
474 | request = purple_http_request_new(NULL);
475 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
476 | purple_http_request_set_url(request, url->str);
477 | purple_http_request_set_method(request, "POST");
478 | purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
479 |
480 | hangouts_set_auth_headers(ha, request);
481 |
482 | postdata = g_string_new(NULL);
483 | if (map_list != NULL) {
484 | map_list_len = json_array_get_length(map_list);
485 | g_string_append_printf(postdata, "count=%u&", map_list_len);
486 | g_string_append(postdata, "ofs=0&");
487 | for(i = 0; i < map_list_len; i++) {
488 | JsonObject *obj = json_array_get_object_element(map_list, i);
489 | GList *members = json_object_get_members(obj);
490 | GList *l;
491 |
492 | for (l = members; l != NULL; l = l->next) {
493 | const gchar *member_name = l->data;
494 | JsonNode *value = json_object_get_member(obj, member_name);
495 |
496 | g_string_append_printf(postdata, "req%u_%s=", i, purple_url_encode(member_name));
497 | g_string_append_printf(postdata, "%s&", purple_url_encode(json_node_get_string(value)));
498 | }
499 |
500 | g_list_free(members);
501 | }
502 | }
503 | purple_http_request_set_contents(request, postdata->str, postdata->len);
504 |
505 | purple_http_request(ha->pc, request, send_maps_callback, ha);
506 | purple_http_request_unref(request);
507 |
508 | g_string_free(postdata, TRUE);
509 | g_string_free(url, TRUE);
510 | }
511 |
512 | void
513 | hangouts_add_channel_services(HangoutsAccount *ha)
514 | {
515 | JsonArray *map_list = json_array_new();
516 | JsonObject *obj;
517 |
518 | // TODO Work out what this is for
519 | obj = json_object_new();
520 | json_object_set_string_member(obj, "p", "{\"3\":{\"1\":{\"1\":\"tango_service\"}}}");
521 | json_array_add_object_element(map_list, obj);
522 |
523 | // This is for the chat messages
524 | obj = json_object_new();
525 | json_object_set_string_member(obj, "p", "{\"3\":{\"1\":{\"1\":\"babel\"}}}");
526 | json_array_add_object_element(map_list, obj);
527 |
528 | // This is for the presence updates
529 | obj = json_object_new();
530 | json_object_set_string_member(obj, "p", "{\"3\":{\"1\":{\"1\":\"babel_presence_last_seen\"}}}");
531 | json_array_add_object_element(map_list, obj);
532 |
533 | // TODO Work out what this is for
534 | obj = json_object_new();
535 | json_object_set_string_member(obj, "p", "{\"3\":{\"1\":{\"1\":\"hangout_invite\"}}}");
536 | json_array_add_object_element(map_list, obj);
537 |
538 | obj = json_object_new();
539 | json_object_set_string_member(obj, "p", "{\"3\":{\"1\":{\"1\":\"gmail\"}}}");
540 | json_array_add_object_element(map_list, obj);
541 |
542 | hangouts_send_maps(ha, map_list, NULL);
543 |
544 | json_array_unref(map_list);
545 | }
546 |
547 |
548 | typedef struct {
549 | HangoutsAccount *ha;
550 | HangoutsPbliteResponseFunc callback;
551 | ProtobufCMessage *response_message;
552 | gpointer user_data;
553 | } LazyPblistRequestStore;
554 |
555 | static void
556 | hangouts_pblite_request_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
557 | {
558 | LazyPblistRequestStore *request_info = user_data;
559 | HangoutsAccount *ha = request_info->ha;
560 | HangoutsPbliteResponseFunc callback = request_info->callback;
561 | gpointer real_user_data = request_info->user_data;
562 | ProtobufCMessage *response_message = request_info->response_message;
563 | ProtobufCMessage *unpacked_message;
564 | const gchar *raw_response;
565 | guchar *decoded_response;
566 | gsize response_len;
567 | const gchar *content_type;
568 |
569 | if (purple_http_response_get_error(response) != NULL) {
570 | g_free(request_info);
571 | g_free(response_message);
572 | purple_debug_error("hangouts", "Error from server: (%s) %s\n", purple_http_response_get_error(response), purple_http_response_get_data(response, NULL));
573 | return; //TODO should we send NULL to the callee?
574 | }
575 |
576 | if (callback != NULL) {
577 | raw_response = purple_http_response_get_data(response, NULL);
578 |
579 | content_type = purple_http_response_get_header(response, "X-Goog-Safety-Content-Type");
580 | if (g_strcmp0(content_type, "application/x-protobuf") == 0) {
581 | decoded_response = g_base64_decode(raw_response, &response_len);
582 | unpacked_message = protobuf_c_message_unpack(response_message->descriptor, NULL, response_len, decoded_response);
583 |
584 | if (unpacked_message != NULL) {
585 | if (purple_debug_is_verbose()) {
586 | gchar *pretty_json = pblite_dump_json(unpacked_message);
587 | purple_debug_misc("hangouts", "Response: %s", pretty_json);
588 | g_free(pretty_json);
589 | }
590 |
591 | callback(ha, unpacked_message, real_user_data);
592 | protobuf_c_message_free_unpacked(unpacked_message, NULL);
593 | } else {
594 | purple_debug_error("hangouts", "Error decoding protobuf!\n");
595 | }
596 | } else {
597 | gchar *tidied_json = hangouts_json_tidy_blank_arrays(raw_response);
598 | JsonArray *response_array = json_decode_array(tidied_json, -1);
599 | const gchar *first_element = json_array_get_string_element(response_array, 0);
600 | gboolean ignore_first_element = (first_element != NULL);
601 |
602 | pblite_decode(response_message, response_array, ignore_first_element);
603 | if (ignore_first_element) {
604 | purple_debug_info("hangouts", "A '%s' says '%s'\n", response_message->descriptor->name, first_element);
605 | }
606 |
607 | if (purple_debug_is_verbose()) {
608 | gchar *pretty_json = pblite_dump_json(response_message);
609 | purple_debug_misc("hangouts", "Response: %s", pretty_json);
610 | g_free(pretty_json);
611 | }
612 |
613 | callback(ha, response_message, real_user_data);
614 |
615 | json_array_unref(response_array);
616 | g_free(tidied_json);
617 | }
618 | }
619 |
620 | g_free(request_info);
621 | g_free(response_message);
622 | }
623 |
624 | PurpleHttpConnection *
625 | hangouts_client6_request(HangoutsAccount *ha, const gchar *path, HangoutsContentType request_type, const gchar *request_data, gssize request_len, HangoutsContentType response_type, PurpleHttpCallback callback, gpointer user_data)
626 | {
627 | PurpleHttpRequest *request;
628 | PurpleHttpConnection *connection;
629 | const gchar *response_type_str;
630 |
631 | switch (response_type) {
632 | default:
633 | case HANGOUTS_CONTENT_TYPE_NONE:
634 | case HANGOUTS_CONTENT_TYPE_JSON:
635 | response_type_str = "json";
636 | break;
637 | case HANGOUTS_CONTENT_TYPE_PBLITE:
638 | response_type_str = "protojson";
639 | break;
640 | case HANGOUTS_CONTENT_TYPE_PROTOBUF:
641 | response_type_str = "proto";
642 | break;
643 | }
644 |
645 | request = purple_http_request_new(NULL);
646 | purple_http_request_set_url_printf(request, HANGOUTS_PBLITE_API_URL "%s%ckey=" GOOGLE_GPLUS_KEY "&alt=%s", path, (strchr(path, '?') ? '&' : '?'), response_type_str);
647 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
648 | purple_http_request_set_keepalive_pool(request, ha->client6_keepalive_pool);
649 | purple_http_request_set_max_len(request, G_MAXINT32 - 1);
650 |
651 | purple_http_request_header_set(request, "X-Goog-Encode-Response-If-Executable", "base64");
652 | if (request_type != HANGOUTS_CONTENT_TYPE_NONE) {
653 | purple_http_request_set_method(request, "POST");
654 | purple_http_request_set_contents(request, request_data, request_len);
655 | if (request_type == HANGOUTS_CONTENT_TYPE_PROTOBUF) {
656 | purple_http_request_header_set(request, "Content-Type", "application/x-protobuf");
657 | } else if (request_type == HANGOUTS_CONTENT_TYPE_PBLITE) {
658 | purple_http_request_header_set(request, "Content-Type", "application/json+protobuf");
659 | } else if (request_type == HANGOUTS_CONTENT_TYPE_JSON) {
660 | purple_http_request_header_set(request, "Content-Type", "application/json");
661 | }
662 | }
663 |
664 | hangouts_set_auth_headers(ha, request);
665 | connection = purple_http_request(ha->pc, request, callback, user_data);
666 | purple_http_request_unref(request);
667 |
668 | return connection;
669 | }
670 |
671 | void
672 | hangouts_pblite_request(HangoutsAccount *ha, const gchar *endpoint, ProtobufCMessage *request_message, HangoutsPbliteResponseFunc callback, ProtobufCMessage *response_message, gpointer user_data)
673 | {
674 | gsize request_len;
675 | gchar *request_data;
676 | LazyPblistRequestStore *request_info = g_new0(LazyPblistRequestStore, 1);
677 |
678 | JsonArray *request_encoded = pblite_encode(request_message);
679 | JsonNode *node = json_node_new(JSON_NODE_ARRAY);
680 | json_node_take_array(node, request_encoded);
681 | request_data = json_encode(node, &request_len);
682 | json_node_free(node);
683 |
684 | request_info->ha = ha;
685 | request_info->callback = callback;
686 | request_info->response_message = response_message;
687 | request_info->user_data = user_data;
688 |
689 | if (purple_debug_is_verbose()) {
690 | gchar *pretty_json = pblite_dump_json(request_message);
691 | purple_debug_misc("hangouts", "Request: %s", pretty_json);
692 | g_free(pretty_json);
693 | }
694 |
695 | hangouts_client6_request(ha, endpoint, HANGOUTS_CONTENT_TYPE_PBLITE, request_data, request_len, HANGOUTS_CONTENT_TYPE_PBLITE, hangouts_pblite_request_cb, request_info);
696 |
697 | g_free(request_data);
698 | }
699 |
700 |
701 | void
702 | hangouts_default_response_dump(HangoutsAccount *ha, ProtobufCMessage *response, gpointer user_data)
703 | {
704 | gchar *dump = pblite_dump_json(response);
705 | purple_debug_info("hangouts", "%s\n", dump);
706 | g_free(dump);
707 | }
708 |
709 | gboolean
710 | hangouts_set_active_client(PurpleConnection *pc)
711 | {
712 | HangoutsAccount *ha;
713 | SetActiveClientRequest request;
714 |
715 | switch(purple_connection_get_state(pc)) {
716 | case PURPLE_CONNECTION_DISCONNECTED:
717 | // I couldn't eat another bite
718 | return FALSE;
719 | case PURPLE_CONNECTION_CONNECTING:
720 | // Come back for more later
721 | return TRUE;
722 | default:
723 | break;
724 | }
725 |
726 | ha = purple_connection_get_protocol_data(pc);
727 | if (ha == NULL) {
728 | g_warn_if_reached();
729 | return TRUE;
730 | }
731 |
732 | if (ha->active_client_state == ACTIVE_CLIENT_STATE__ACTIVE_CLIENT_STATE_IS_ACTIVE) {
733 | //We're already the active client
734 | return TRUE;
735 | }
736 | if (ha->idle_time > HANGOUTS_ACTIVE_CLIENT_TIMEOUT) {
737 | //We've gone idle
738 | return TRUE;
739 | }
740 | if (!purple_presence_is_status_primitive_active(purple_account_get_presence(ha->account), PURPLE_STATUS_AVAILABLE)) {
741 | //We're marked as not available somehow
742 | return TRUE;
743 | }
744 | ha->active_client_state = ACTIVE_CLIENT_STATE__ACTIVE_CLIENT_STATE_IS_ACTIVE;
745 |
746 | set_active_client_request__init(&request);
747 |
748 | request.request_header = hangouts_get_request_header(ha);
749 | request.has_is_active = TRUE;
750 | request.is_active = TRUE;
751 | request.full_jid = g_strdup_printf("%s/%s", purple_account_get_username(ha->account), ha->client_id);
752 | request.has_timeout_secs = TRUE;
753 | request.timeout_secs = HANGOUTS_ACTIVE_CLIENT_TIMEOUT;
754 |
755 | hangouts_pblite_set_active_client(ha, &request, (HangoutsPbliteSetActiveClientResponseFunc)hangouts_default_response_dump, NULL);
756 |
757 | hangouts_request_header_free(request.request_header);
758 | g_free(request.full_jid);
759 |
760 | return TRUE;
761 | }
762 |
763 |
764 | void
765 | hangouts_search_results_send_im(PurpleConnection *pc, GList *row, void *user_data)
766 | {
767 | PurpleAccount *account = purple_connection_get_account(pc);
768 | const gchar *who = g_list_nth_data(row, 0);
769 | PurpleIMConversation *imconv;
770 |
771 | imconv = purple_conversations_find_im_with_account(who, account);
772 | if (imconv == NULL) {
773 | imconv = purple_im_conversation_new(account, who);
774 | }
775 | purple_conversation_present(PURPLE_CONVERSATION(imconv));
776 | }
777 |
778 | void
779 | hangouts_search_results_get_info(PurpleConnection *pc, GList *row, void *user_data)
780 | {
781 | hangouts_get_info(pc, g_list_nth_data(row, 0));
782 | }
783 |
784 | void
785 | hangouts_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data)
786 | {
787 | PurpleAccount *account = purple_connection_get_account(pc);
788 |
789 | if (!purple_blist_find_buddy(account, g_list_nth_data(row, 0)))
790 | purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Hangouts", g_list_nth_data(row, 1));
791 | }
792 |
793 | void
794 | hangouts_search_users_text_cb(PurpleHttpConnection *connection, PurpleHttpResponse *response, gpointer user_data)
795 | {
796 | HangoutsAccount *ha = user_data;
797 | const gchar *response_data;
798 | size_t response_size;
799 | JsonArray *resultsarray;
800 | JsonObject *node;
801 | gint index, length;
802 | gchar *search_term;
803 | JsonObject *status;
804 |
805 | PurpleNotifySearchResults *results;
806 | PurpleNotifySearchColumn *column;
807 |
808 | if (purple_http_response_get_error(response) != NULL) {
809 | purple_notify_error(ha->pc, _("Search Error"), _("There was an error searching for the user"), purple_http_response_get_error(response), purple_request_cpar_from_connection(ha->pc));
810 | g_dataset_destroy(connection);
811 | return;
812 | }
813 |
814 | response_data = purple_http_response_get_data(response, &response_size);
815 | node = json_decode_object(response_data, response_size);
816 |
817 | search_term = g_dataset_get_data(connection, "search_term");
818 | resultsarray = json_object_get_array_member(node, "results");
819 | length = json_array_get_length(resultsarray);
820 |
821 | if (length == 0) {
822 | status = json_object_get_object_member(node, "status");
823 |
824 | if (!json_object_has_member(status, "personalResultsNotReady") || json_object_get_boolean_member(status, "personalResultsNotReady") == TRUE) {
825 | //Not ready yet, retry
826 | hangouts_search_users_text(ha, search_term);
827 |
828 | } else {
829 | gchar *primary_text = g_strdup_printf(_("Your search for the user \"%s\" returned no results"), search_term);
830 | purple_notify_warning(ha->pc, _("No users found"), primary_text, "", purple_request_cpar_from_connection(ha->pc));
831 | g_free(primary_text);
832 | }
833 |
834 | g_dataset_destroy(connection);
835 | json_object_unref(node);
836 | return;
837 | }
838 |
839 | results = purple_notify_searchresults_new();
840 | if (results == NULL)
841 | {
842 | g_dataset_destroy(connection);
843 | json_object_unref(node);
844 | return;
845 | }
846 |
847 | /* columns: Friend ID, Name, Network */
848 | column = purple_notify_searchresults_column_new(_("ID"));
849 | purple_notify_searchresults_column_add(results, column);
850 | column = purple_notify_searchresults_column_new(_("Display Name"));
851 | purple_notify_searchresults_column_add(results, column);
852 |
853 | purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, hangouts_search_results_add_buddy);
854 | purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_INFO, hangouts_search_results_get_info);
855 | purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, hangouts_search_results_send_im);
856 |
857 | for(index = 0; index < length; index++)
858 | {
859 | JsonNode *result = json_array_get_element(resultsarray, index);
860 |
861 | gchar *id = hangouts_json_path_query_string(result, "$.person.personId", NULL);
862 | gchar *displayname = hangouts_json_path_query_string(result, "$.person.name[*].displayName", NULL);
863 | GList *row = NULL;
864 |
865 | row = g_list_append(row, id);
866 | row = g_list_append(row, displayname);
867 |
868 | purple_notify_searchresults_row_add(results, row);
869 | }
870 |
871 | purple_notify_searchresults(ha->pc, NULL, search_term, NULL, results, NULL, NULL);
872 |
873 | g_dataset_destroy(connection);
874 | json_object_unref(node);
875 | }
876 |
877 | /*
878 |
879 | POST https://people-pa.clients6.google.com/v2/people/lookup
880 | id=actual_email_address%40gmail.com&type=EMAIL&matchType=LENIENT&requestMask.includeField.paths=person.email&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo&requestMask.includeField.paths=person.read_only_profile_info&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_LOOKUP&extensionSet.extensionNames=HANGOUTS_PHONE_DATA&coreIdParams.useRealtimeNotificationExpandedAcls=true&key=AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM
881 |
882 | id=%2B123456789&type=PHONE&matchType=LENIENT&requestMask.includeField.paths=person.email&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo&requestMask.includeField.paths=person.read_only_profile_info&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_LOOKUP&extensionSet.extensionNames=HANGOUTS_PHONE_DATA&coreIdParams.useRealtimeNotificationExpandedAcls=true"aFilterType=PHONE&key=AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM
883 |
884 | */
885 |
886 |
887 | void
888 | hangouts_search_users_text(HangoutsAccount *ha, const gchar *text)
889 | {
890 | PurpleHttpRequest *request;
891 | GString *url = g_string_new("https://people-pa.clients6.google.com/v2/people/autocomplete?");
892 | PurpleHttpConnection *connection;
893 |
894 | g_string_append_printf(url, "query=%s&", purple_url_encode(text));
895 | g_string_append(url, "client=HANGOUTS_WITH_DATA&");
896 | g_string_append(url, "pageSize=20&");
897 | g_string_append_printf(url, "key=%s&", purple_url_encode(GOOGLE_GPLUS_KEY));
898 |
899 | request = purple_http_request_new(NULL);
900 | purple_http_request_set_cookie_jar(request, ha->cookie_jar);
901 | purple_http_request_set_url(request, url->str);
902 |
903 | hangouts_set_auth_headers(ha, request);
904 |
905 | connection = purple_http_request(ha->pc, request, hangouts_search_users_text_cb, ha);
906 | purple_http_request_unref(request);
907 |
908 | g_dataset_set_data_full(connection, "search_term", g_strdup(text), g_free);
909 |
910 | g_string_free(url, TRUE);
911 | }
912 |
913 | void
914 | hangouts_search_users(PurpleProtocolAction *action)
915 | {
916 | PurpleConnection *pc = purple_protocol_action_get_connection(action);
917 | HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
918 |
919 | purple_request_input(pc, _("Search for friends..."),
920 | _("Search for friends..."),
921 | NULL,
922 | NULL, FALSE, FALSE, NULL,
923 | _("_Search"), G_CALLBACK(hangouts_search_users_text),
924 | _("_Cancel"), NULL,
925 | purple_request_cpar_from_connection(pc),
926 | ha);
927 |
928 | }
929 |
--------------------------------------------------------------------------------
/hangouts_connection.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_CONNECTION_H_
21 | #define _HANGOUTS_CONNECTION_H_
22 |
23 | #include
24 |
25 | #include "http.h"
26 |
27 | #include "libhangouts.h"
28 | #include "hangouts_pblite.h"
29 | #include "hangouts.pb-c.h"
30 |
31 | #define HANGOUTS_PBLITE_XORIGIN_URL "https://hangouts.google.com"
32 | //#define HANGOUTS_PBLITE_XORIGIN_URL "https://talkgadget.google.com"
33 | #define HANGOUTS_PBLITE_API_URL "https://clients6.google.com"
34 | //#define HANGOUTS_PBLITE_API_URL "https://www.googleapis.com"
35 | #define HANGOUTS_CHANNEL_URL_PREFIX "https://0.client-channel.google.com/client-channel/"
36 |
37 | void hangouts_process_data_chunks(HangoutsAccount *ha, const gchar *data, gsize len);
38 |
39 | void hangouts_process_channel(int fd);
40 |
41 |
42 | void hangouts_longpoll_request(HangoutsAccount *ha);
43 | void hangouts_fetch_channel_sid(HangoutsAccount *ha);
44 | void hangouts_send_maps(HangoutsAccount *ha, JsonArray *map_list, PurpleHttpCallback send_maps_callback);
45 | void hangouts_add_channel_services(HangoutsAccount *ha);
46 |
47 | void hangouts_default_response_dump(HangoutsAccount *ha, ProtobufCMessage *response, gpointer user_data);
48 | gboolean hangouts_set_active_client(PurpleConnection *pc);
49 | void hangouts_search_users(PurpleProtocolAction *action);
50 | void hangouts_search_users_text(HangoutsAccount *ha, const gchar *text);
51 |
52 | typedef enum {
53 | HANGOUTS_CONTENT_TYPE_NONE = 0,
54 | HANGOUTS_CONTENT_TYPE_JSON,
55 | HANGOUTS_CONTENT_TYPE_PBLITE,
56 | HANGOUTS_CONTENT_TYPE_PROTOBUF
57 | } HangoutsContentType;
58 | PurpleHttpConnection *hangouts_client6_request(HangoutsAccount *ha, const gchar *path, HangoutsContentType request_type, const gchar *request_data, gssize request_len, HangoutsContentType response_type, PurpleHttpCallback callback, gpointer user_data);
59 |
60 | typedef void(* HangoutsPbliteResponseFunc)(HangoutsAccount *ha, ProtobufCMessage *response, gpointer user_data);
61 | void hangouts_pblite_request(HangoutsAccount *ha, const gchar *endpoint, ProtobufCMessage *request, HangoutsPbliteResponseFunc callback, ProtobufCMessage *response_message, gpointer user_data);
62 |
63 |
64 | #define HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(name, type, url) \
65 | typedef void(* HangoutsPblite##type##ResponseFunc)(HangoutsAccount *ha, type##Response *response, gpointer user_data);\
66 | static inline void \
67 | hangouts_pblite_##name(HangoutsAccount *ha, type##Request *request, HangoutsPblite##type##ResponseFunc callback, gpointer user_data)\
68 | {\
69 | type##Response *response = g_new0(type##Response, 1);\
70 | \
71 | name##_response__init(response);\
72 | hangouts_pblite_request(ha, "/chat/v1/" url, (ProtobufCMessage *)request, (HangoutsPbliteResponseFunc)callback, (ProtobufCMessage *)response, user_data);\
73 | }
74 |
75 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(send_chat_message, SendChatMessage, "conversations/sendchatmessage");
76 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(set_typing, SetTyping, "conversations/settyping");
77 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(get_self_info, GetSelfInfo, "contacts/getselfinfo");
78 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(sync_recent_conversations, SyncRecentConversations, "conversations/syncrecentconversations");
79 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(sync_all_new_events, SyncAllNewEvents, "conversations/syncallnewevents");
80 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(set_presence, SetPresence, "presence/setpresence");
81 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(query_presence, QueryPresence, "presence/querypresence");
82 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(get_conversation, GetConversation, "conversations/getconversation");
83 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(create_conversation, CreateConversation, "conversations/createconversation");
84 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(delete_conversation, DeleteConversation, "conversations/deleteconversation");
85 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(rename_conversation, RenameConversation, "conversations/renameconversation");
86 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(modify_conversation_view, ModifyConversationView, "conversations/modifyconversationview");
87 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(add_user, AddUser, "conversations/adduser");
88 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(remove_user, RemoveUser, "conversations/removeuser");
89 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(update_watermark, UpdateWatermark, "conversations/updatewatermark");
90 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(set_focus, SetFocus, "conversations/setfocus");
91 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(set_active_client, SetActiveClient, "clients/setactiveclient");
92 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(get_entity_by_id, GetEntityById, "contacts/getentitybyid");
93 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(get_group_conversation_url, GetGroupConversationUrl, "conversations/getgroupconversationurl");
94 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(set_group_link_sharing_enabled, SetGroupLinkSharingEnabled, "conversations/setgrouplinksharingenabled");
95 | HANGOUTS_DEFINE_PBLITE_REQUEST_FUNC(open_group_conversation_from_url, OpenGroupConversationFromUrl, "conversations/opengroupconversationfromurl");
96 |
97 | #endif /*_HANGOUTS_CONNECTION_H_*/
--------------------------------------------------------------------------------
/hangouts_conversation.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_CONVERSATION_H_
21 | #define _HANGOUTS_CONVERSATION_H_
22 |
23 | #include "libhangouts.h"
24 | #include "conversation.h"
25 | #include "connection.h"
26 |
27 | #include "hangouts.pb-c.h"
28 |
29 | RequestHeader *hangouts_get_request_header(HangoutsAccount *ha);
30 | void hangouts_request_header_free(RequestHeader *header);
31 |
32 | GList *hangouts_chat_info(PurpleConnection *pc);
33 | GHashTable *hangouts_chat_info_defaults(PurpleConnection *pc, const char *chatname);
34 | void hangouts_join_chat(PurpleConnection *pc, GHashTable *data);
35 | gchar *hangouts_get_chat_name(GHashTable *data);
36 |
37 | void hangouts_join_chat_from_url(HangoutsAccount *ha, const gchar *url);
38 |
39 | void hangouts_get_all_events(HangoutsAccount *ha, guint64 since_timestamp);
40 | void hangouts_add_conversation_to_blist(HangoutsAccount *ha, Conversation *conversation, GHashTable *unique_user_ids);
41 |
42 | void hangouts_get_self_info(HangoutsAccount *ha);
43 | void hangouts_get_conversation_list(HangoutsAccount *ha);
44 | void hangouts_get_buddy_list(HangoutsAccount *ha);
45 |
46 | gint hangouts_send_im(PurpleConnection *pc,
47 | #if PURPLE_VERSION_CHECK(3, 0, 0)
48 | PurpleMessage *msg
49 | #else
50 | const gchar *who, const gchar *message, PurpleMessageFlags flags
51 | #endif
52 | );
53 |
54 | gint hangouts_chat_send(PurpleConnection *pc, gint id,
55 | #if PURPLE_VERSION_CHECK(3, 0, 0)
56 | PurpleMessage *msg
57 | #else
58 | const gchar *message, PurpleMessageFlags flags
59 | #endif
60 | );
61 |
62 | guint hangouts_send_typing(PurpleConnection *pc, const gchar *name, PurpleIMTypingState state);
63 | guint hangouts_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state, HangoutsAccount *ha);
64 |
65 |
66 | void hangouts_get_users_presence(HangoutsAccount *ha, GList *user_ids);
67 | void hangouts_get_users_information(HangoutsAccount *ha, GList *user_ids);
68 | void hangouts_get_info(PurpleConnection *pc, const gchar *who);
69 | gboolean hangouts_poll_buddy_status(gpointer ha_pointer);
70 |
71 | void hangouts_chat_leave_by_conv_id(PurpleConnection *pc, const gchar *conv_id, const gchar *who);
72 | void hangouts_chat_leave(PurpleConnection *pc, int id);
73 | void hangouts_chat_kick(PurpleConnection *pc, int id, const char *who);
74 | void hangouts_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who);
75 | void hangouts_create_conversation(HangoutsAccount *ha, gboolean is_one_to_one, const char *who, const gchar *optional_message);
76 | void hangouts_archive_conversation(HangoutsAccount *ha, const gchar *conv_id);
77 | void hangouts_rename_conversation(HangoutsAccount *ha, const gchar *conv_id, const gchar *alias);
78 | void hangouts_initiate_chat_from_node(PurpleBlistNode *node, gpointer userdata);
79 |
80 | void hangouts_mark_conversation_seen(PurpleConversation *conv, PurpleConversationUpdateType type);
81 |
82 | void hangouts_set_status(PurpleAccount *account, PurpleStatus *status);
83 |
84 | void hangouts_block_user(PurpleConnection *pc, const char *who);
85 | void hangouts_unblock_user(PurpleConnection *pc, const char *who);
86 |
87 | PurpleRoomlist *hangouts_roomlist_get_list(PurpleConnection *pc);
88 |
89 | #endif /*_HANGOUTS_CONVERSATION_H_*/
90 |
--------------------------------------------------------------------------------
/hangouts_events.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_EVENTS_H_
21 | #define _HANGOUTS_EVENTS_H_
22 |
23 | #include "libhangouts.h"
24 | #include "hangouts.pb-c.h"
25 | #include "gmail.pb-c.h"
26 |
27 | void hangouts_register_events(gpointer plugin);
28 |
29 | void hangouts_received_other_notification(PurpleConnection *pc, StateUpdate *state_update);
30 | void hangouts_received_event_notification(PurpleConnection *pc, StateUpdate *state_update);
31 | void hangouts_received_presence_notification(PurpleConnection *pc, StateUpdate *state_update);
32 | void hangouts_received_typing_notification(PurpleConnection *pc, StateUpdate *state_update);
33 | void hangouts_received_watermark_notification(PurpleConnection *pc, StateUpdate *state_update);
34 | void hangouts_received_block_notification(PurpleConnection *pc, StateUpdate *state_update);
35 | void hangouts_received_view_modification(PurpleConnection *pc, StateUpdate *state_update);
36 | void hangouts_received_delete_notification(PurpleConnection *pc, StateUpdate *state_update);
37 | void hangouts_received_state_update(PurpleConnection *pc, StateUpdate *state_update);
38 |
39 | void hangouts_received_gmail_notification(PurpleConnection *pc, const gchar *username, GmailNotification *msg);
40 |
41 | void hangouts_process_presence_result(HangoutsAccount *ha, PresenceResult *presence);
42 | void hangouts_process_conversation_event(HangoutsAccount *ha, Conversation *conversation, Event *event, gint64 current_server_time);
43 |
44 | #endif /*_HANGOUTS_EVENTS_H_*/
--------------------------------------------------------------------------------
/hangouts_json.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #include "hangouts_json.h"
20 |
21 | #include
22 |
23 | gchar *
24 | json_encode(JsonNode *node, gsize *len)
25 | {
26 | gchar *data;
27 | JsonGenerator *generator = json_generator_new();
28 |
29 | json_generator_set_root(generator, node);
30 |
31 | data = json_generator_to_data(generator, len);
32 |
33 | g_object_unref(generator);
34 |
35 | return data;
36 | }
37 |
38 | gchar *
39 | json_pretty_encode(JsonNode *node, gsize *len)
40 | {
41 | gchar *data;
42 | JsonGenerator *generator = json_generator_new();
43 |
44 | g_object_set(generator, "pretty", TRUE, NULL);
45 | g_object_set(generator, "indent-char", '\t', NULL);
46 | g_object_set(generator, "indent", 1, NULL);
47 |
48 | json_generator_set_root(generator, node);
49 |
50 | data = json_generator_to_data(generator, len);
51 |
52 | g_object_unref(generator);
53 |
54 | return data;
55 | }
56 |
57 | gchar *
58 | json_encode_array(JsonArray *array, gsize *len)
59 | {
60 | JsonNode *node = json_node_new(JSON_NODE_ARRAY);
61 | gchar *json;
62 |
63 | json_node_set_array(node, array);
64 | json = json_encode(node, len);
65 |
66 | json_node_free(node);
67 | return json;
68 | }
69 |
70 | gchar *
71 | json_encode_object(JsonObject *object, gsize *len)
72 | {
73 | JsonNode *node = json_node_new(JSON_NODE_OBJECT);
74 | gchar *json;
75 |
76 | json_node_set_object(node, object);
77 | json = json_encode(node, len);
78 |
79 | json_node_free(node);
80 | return json;
81 | }
82 |
83 | JsonNode *
84 | json_decode(const gchar *data, gssize len)
85 | {
86 | JsonParser *parser = json_parser_new();
87 | JsonNode *root = NULL;
88 |
89 | if (!data || !json_parser_load_from_data(parser, data, len, NULL))
90 | {
91 | purple_debug_error("hangouts", "Error parsing JSON: %s\n", data);
92 | } else {
93 | root = json_parser_get_root(parser);
94 | if (root != NULL) {
95 | root = json_node_copy(root);
96 | }
97 | }
98 | g_object_unref(parser);
99 |
100 | return root;
101 | }
102 |
103 | JsonArray *
104 | json_decode_array(const gchar *data, gssize len)
105 | {
106 | JsonNode *root = json_decode(data, len);
107 | JsonArray *ret;
108 |
109 | g_return_val_if_fail(root, NULL);
110 |
111 | if (!JSON_NODE_HOLDS_ARRAY(root)) {
112 | // That ain't my belly button!
113 | json_node_free(root);
114 | return NULL;
115 | }
116 |
117 | ret = json_node_dup_array(root);
118 |
119 | json_node_free(root);
120 |
121 | return ret;
122 | }
123 |
124 | JsonObject *
125 | json_decode_object(const gchar *data, gssize len)
126 | {
127 | JsonNode *root = json_decode(data, len);
128 | JsonObject *ret;
129 |
130 | g_return_val_if_fail(root, NULL);
131 |
132 | if (!JSON_NODE_HOLDS_OBJECT(root)) {
133 | // That ain't my thumb, neither!
134 | json_node_free(root);
135 | return NULL;
136 | }
137 |
138 | ret = json_node_dup_object(root);
139 |
140 | json_node_free(root);
141 |
142 | return ret;
143 | }
144 |
145 |
146 | JsonNode *
147 | hangouts_json_path_query(JsonNode *root, const gchar *expr, GError **error)
148 | {
149 | JsonNode *ret;
150 | JsonNode *node;
151 | JsonArray *result;
152 |
153 | if (g_str_equal(expr, "$")) {
154 | return root;
155 | }
156 |
157 | node = json_path_query(expr, root, error);
158 |
159 | if (error != NULL)
160 | {
161 | json_node_free(node);
162 | return NULL;
163 | }
164 |
165 | result = json_node_get_array(node);
166 | if (json_array_get_length(result) == 0)
167 | {
168 | json_node_free(node);
169 | return NULL;
170 | }
171 | ret = json_array_dup_element(result, 0);
172 | json_node_free(node);
173 | return ret;
174 |
175 | }
176 |
177 | gchar *
178 | hangouts_json_path_query_string(JsonNode *root, const gchar *expr, GError **error)
179 | {
180 | gchar *ret;
181 | JsonNode *rslt;
182 |
183 | rslt = hangouts_json_path_query(root, expr, error);
184 |
185 | if (rslt == NULL)
186 | {
187 | return NULL;
188 | }
189 |
190 | ret = g_strdup(json_node_get_string(rslt));
191 | json_node_free(rslt);
192 | return ret;
193 | }
194 |
195 | gint64
196 | hangouts_json_path_query_int(JsonNode *root, const gchar *expr, GError **error)
197 | {
198 | gint64 ret;
199 | JsonNode *rslt;
200 |
201 | rslt = hangouts_json_path_query(root, expr, error);
202 |
203 | if (rslt == NULL)
204 | {
205 | return 0;
206 | }
207 |
208 | ret = json_node_get_int(rslt);
209 | json_node_free(rslt);
210 | return ret;
211 | }
212 |
213 | gchar *
214 | hangouts_json_tidy_blank_arrays(const gchar *json)
215 | {
216 | static GRegex *tidy_regex = NULL;
217 |
218 | if (tidy_regex == NULL) {
219 | tidy_regex = g_regex_new("\"(\\\\\"|.)*?\"(*SKIP)(*FAIL)|\\[\\](*SKIP)(*FAIL)|(?<=,|\\[)\\s*(?=,|\\])", G_REGEX_OPTIMIZE, 0, NULL);
220 | }
221 |
222 | return g_regex_replace_literal(tidy_regex, json, -1, 0, "null", 0, NULL);
223 | }
224 |
225 |
--------------------------------------------------------------------------------
/hangouts_json.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_JSON_H_
21 | #define _HANGOUTS_JSON_H_
22 |
23 | #include
24 | #include
25 |
26 |
27 | #ifndef JSON_NODE_HOLDS
28 | #define JSON_NODE_HOLDS(node,t) (json_node_get_node_type ((node)) == (t))
29 | #endif
30 | #ifndef JSON_NODE_HOLDS_VALUE
31 | #define JSON_NODE_HOLDS_VALUE(node) (JSON_NODE_HOLDS ((node), JSON_NODE_VALUE))
32 | #endif
33 | #ifndef JSON_NODE_HOLDS_OBJECT
34 | #define JSON_NODE_HOLDS_OBJECT(node) (JSON_NODE_HOLDS ((node), JSON_NODE_OBJECT))
35 | #endif
36 | #ifndef JSON_NODE_HOLDS_ARRAY
37 | #define JSON_NODE_HOLDS_ARRAY(node) (JSON_NODE_HOLDS ((node), JSON_NODE_ARRAY))
38 | #endif
39 | #ifndef JSON_NODE_HOLDS_NULL
40 | #define JSON_NODE_HOLDS_NULL(node) (JSON_NODE_HOLDS ((node), JSON_NODE_NULL))
41 | #endif
42 |
43 |
44 | // Supress overzealous json-glib 'critical errors'
45 | #define json_object_get_int_member(JSON_OBJECT, MEMBER) \
46 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_int_member(JSON_OBJECT, MEMBER) : 0)
47 | #define json_object_get_string_member(JSON_OBJECT, MEMBER) \
48 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_string_member(JSON_OBJECT, MEMBER) : NULL)
49 | #define json_object_get_array_member(JSON_OBJECT, MEMBER) \
50 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_array_member(JSON_OBJECT, MEMBER) : NULL)
51 | #define json_object_get_object_member(JSON_OBJECT, MEMBER) \
52 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_object_member(JSON_OBJECT, MEMBER) : NULL)
53 | #define json_object_get_boolean_member(JSON_OBJECT, MEMBER) \
54 | (json_object_has_member(JSON_OBJECT, MEMBER) ? json_object_get_boolean_member(JSON_OBJECT, MEMBER) : FALSE)
55 |
56 | /**
57 | * Returns a string of the encoded JSON object.
58 | *
59 | * \param node
60 | * The node of data to encode
61 | * \param len (optional, out)
62 | * Returns the length of the encoded string
63 | * \return
64 | * The JSON string or NULL if there was an error. You are required to g_free() this when you are done.
65 | */
66 | gchar *json_encode(JsonNode *node, gsize *len);
67 | gchar *json_pretty_encode(JsonNode *node, gsize *len);
68 |
69 | gchar *json_encode_object(JsonObject *object, gsize *len);
70 | gchar *json_encode_array(JsonArray *array, gsize *len);
71 |
72 | /**
73 | * Decode a JSON string into a JsonNode object
74 | *
75 | * \param data
76 | * The JSON-encoded string to decode.
77 | * \param len
78 | * The length of the string, or -1 if the string is NUL-terminated.
79 | * \return
80 | * The JsonNode object or NULL if there was an error.
81 | */
82 | JsonNode *json_decode(const gchar *data, gssize len);
83 |
84 | JsonArray *json_decode_array(const gchar *data, gssize len);
85 | JsonObject *json_decode_object(const gchar *data, gssize len);
86 |
87 |
88 | JsonNode *hangouts_json_path_query(JsonNode *root, const gchar *expr, GError **error);
89 |
90 | gchar *hangouts_json_path_query_string(JsonNode *root, const gchar *expr, GError **error);
91 |
92 | gint64 hangouts_json_path_query_int(JsonNode *root, const gchar *expr, GError **error);
93 |
94 |
95 | /**
96 | * Tidies invalid JSON from Google into slightly-more-valid JSON,
97 | * so that it can be parsed by json-glib
98 | * e.g. turns [,,,,,] into [null,null,null,null,null]
99 | *
100 | * \param json
101 | * The JSON-encoded string to clean up.
102 | * \return
103 | * The JSON-encoded string that has been tidied or NULL if there was an error. You are required to g_free() this when you are done.
104 | */
105 | gchar *hangouts_json_tidy_blank_arrays(const gchar *json);
106 |
107 | #endif /* _HANGOUTS_JSON_H_ */
108 |
--------------------------------------------------------------------------------
/hangouts_media.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_MEDIA_H_
21 | #define _HANGOUTS_MEDIA_H_
22 |
23 | #include "hangouts_connection.h"
24 | #include "hangout_media.pb-c.h"
25 |
26 | #define SRTP_KEY_LEN 30
27 |
28 | gboolean hangouts_initiate_media(PurpleAccount *account, const gchar *who, PurpleMediaSessionType type);
29 |
30 |
31 | #define HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(name, type, url) \
32 | typedef void(* HangoutsPblite##type##ResponseFunc)(HangoutsAccount *ha, type##Response *response, gpointer user_data);\
33 | static inline void \
34 | hangouts_pblite_media_##name(HangoutsAccount *ha, type##Request *request, HangoutsPblite##type##ResponseFunc callback, gpointer user_data)\
35 | {\
36 | type##Response *response = g_new0(type##Response, 1);\
37 | \
38 | name##_response__init(response);\
39 | hangouts_pblite_request(ha, "/hangouts/v1/" url, (ProtobufCMessage *)request, (HangoutsPbliteResponseFunc)callback, (ProtobufCMessage *)response, user_data);\
40 | }
41 |
42 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(hangout_resolve, HangoutResolve, "hangouts/resolve");
43 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(hangout_query, HangoutQuery, "hangouts/query");
44 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_session_add, MediaSessionAdd, "media_sessions/add");
45 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_session_modify, MediaSessionModify, "media_sessions/modify");
46 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_session_query, MediaSessionQuery, "media_sessions/query");
47 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(hangout_participant_add, HangoutParticipantAdd, "hangout_participants/add");
48 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(hangout_participant_remove, HangoutParticipantRemove, "hangout_participants/remove");
49 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(hangout_invitation_add, HangoutInvitationAdd, "hangout_invitations/add");
50 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(hangout_bulk, HangoutBulk, "hangouts/bulk");
51 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_source_add, MediaSourceAdd, "media_sources/add");
52 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_stream_add, MediaStreamAdd, "media_streams/add");
53 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_stream_search, MediaStreamSearch, "media_streams/search");
54 | HANGOUTS_DEFINE_PBLITE_MEDIA_REQUEST_FUNC(media_stream_modify, MediaStreamModify, "media_streams/modify");
55 |
56 | #endif /*_HANGOUTS_MEDIA_H_*/
--------------------------------------------------------------------------------
/hangouts_pblite.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #include "hangouts_pblite.h"
20 |
21 | #include
22 | #include
23 |
24 | static void *glib_protobufc_allocator_alloc(void *allocator_data, size_t size) { return g_try_malloc0(size); };
25 | static void glib_protobufc_allocator_free(void *allocator_data, void *pointer) { g_free(pointer); };
26 |
27 | static ProtobufCAllocator glib_protobufc_allocator = {
28 | .alloc = &glib_protobufc_allocator_alloc,
29 | .free = &glib_protobufc_allocator_free,
30 | .allocator_data = NULL,
31 | };
32 |
33 | /**
34 | * Given a field type, return the in-memory size.
35 | *
36 | * \todo Implement as a table lookup.
37 | *
38 | * \param type
39 | * Field type.
40 | * \return
41 | * Size of the field.
42 | */
43 | static inline size_t
44 | sizeof_elt_in_repeated_array(ProtobufCType type)
45 | {
46 | switch (type) {
47 | case PROTOBUF_C_TYPE_SINT32:
48 | case PROTOBUF_C_TYPE_INT32:
49 | case PROTOBUF_C_TYPE_UINT32:
50 | case PROTOBUF_C_TYPE_SFIXED32:
51 | case PROTOBUF_C_TYPE_FIXED32:
52 | case PROTOBUF_C_TYPE_FLOAT:
53 | case PROTOBUF_C_TYPE_ENUM:
54 | return 4;
55 | case PROTOBUF_C_TYPE_SINT64:
56 | case PROTOBUF_C_TYPE_INT64:
57 | case PROTOBUF_C_TYPE_UINT64:
58 | case PROTOBUF_C_TYPE_SFIXED64:
59 | case PROTOBUF_C_TYPE_FIXED64:
60 | case PROTOBUF_C_TYPE_DOUBLE:
61 | return 8;
62 | case PROTOBUF_C_TYPE_BOOL:
63 | return sizeof(protobuf_c_boolean);
64 | case PROTOBUF_C_TYPE_STRING:
65 | case PROTOBUF_C_TYPE_MESSAGE:
66 | return sizeof(void *);
67 | case PROTOBUF_C_TYPE_BYTES:
68 | return sizeof(ProtobufCBinaryData);
69 | }
70 | g_return_val_if_reached(0);
71 | return 0;
72 | }
73 |
74 |
75 |
76 | #define STRUCT_MEMBER_P(struct_p, struct_offset) \
77 | ((void *) ((uint8_t *) (struct_p) + (struct_offset)))
78 | #define STRUCT_MEMBER_PTR(member_type, struct_p, struct_offset) \
79 | ((member_type *) STRUCT_MEMBER_P((struct_p), (struct_offset)))
80 | #define STRUCT_MEMBER(member_type, struct_p, struct_offset) \
81 | (*(member_type *) STRUCT_MEMBER_P((struct_p), (struct_offset)))
82 |
83 | static gboolean
84 | pblite_decode_field(const ProtobufCFieldDescriptor *field, JsonNode *value, gpointer member)
85 | {
86 |
87 | switch (field->type) {
88 | case PROTOBUF_C_TYPE_INT32:
89 | case PROTOBUF_C_TYPE_UINT32:
90 | case PROTOBUF_C_TYPE_SFIXED32:
91 | case PROTOBUF_C_TYPE_FIXED32:
92 | case PROTOBUF_C_TYPE_FLOAT:
93 | case PROTOBUF_C_TYPE_ENUM:
94 | *(uint32_t *) member = json_node_get_int(value);
95 | return TRUE;
96 |
97 | case PROTOBUF_C_TYPE_SINT32:
98 | *(int32_t *) member = json_node_get_int(value);
99 | return TRUE;
100 |
101 | case PROTOBUF_C_TYPE_INT64:
102 | case PROTOBUF_C_TYPE_UINT64:
103 | case PROTOBUF_C_TYPE_SFIXED64:
104 | case PROTOBUF_C_TYPE_FIXED64:
105 | case PROTOBUF_C_TYPE_DOUBLE:
106 | *(uint64_t *) member = json_node_get_int(value);
107 | return TRUE;
108 |
109 | case PROTOBUF_C_TYPE_SINT64:
110 | *(int64_t *) member = json_node_get_int(value);
111 | return TRUE;
112 |
113 | case PROTOBUF_C_TYPE_BOOL:
114 | *(protobuf_c_boolean *) member = json_node_get_int(value);
115 | return TRUE;
116 |
117 | case PROTOBUF_C_TYPE_STRING: {
118 | char **pstr = member;
119 |
120 | //TODO - free old data
121 | // if (maybe_clear && *pstr != NULL) {
122 | // const char *def = scanned_member->field->default_value;
123 | // if (*pstr != NULL && *pstr != def)
124 | // do_free(allocator, *pstr);
125 | // }
126 | *pstr = g_strdup(json_node_get_string(value));
127 | return TRUE;
128 | }
129 | case PROTOBUF_C_TYPE_BYTES: {
130 | ProtobufCBinaryData *bd = member;
131 |
132 | //TODO - free old data
133 | // def_bd = scanned_member->field->default_value;
134 | // if (maybe_clear &&
135 | // bd->data != NULL &&
136 | // (def_bd == NULL || bd->data != def_bd->data))
137 | // {
138 | // do_free(allocator, bd->data);
139 | // }
140 | bd->data = g_base64_decode(json_node_get_string(value), &bd->len);
141 | return TRUE;
142 | }
143 |
144 | case PROTOBUF_C_TYPE_MESSAGE: {
145 | ProtobufCMessage **pmessage = member;
146 | const ProtobufCMessageDescriptor *desc = field->descriptor;
147 |
148 | *pmessage = g_malloc0(desc->sizeof_message);
149 | (void) (glib_protobufc_allocator);
150 | protobuf_c_message_init(desc, *pmessage);
151 |
152 | #ifdef DEBUG
153 | switch(json_node_get_node_type(value)) {
154 | case JSON_NODE_OBJECT:
155 | printf("object\n");
156 | break;
157 | case JSON_NODE_ARRAY:
158 | printf("array\n");
159 | break;
160 | case JSON_NODE_NULL:
161 | printf("null\n");
162 | break;
163 | case JSON_NODE_VALUE:
164 | printf("value %s %" G_GINT64_FORMAT "\n", json_node_get_string(value), json_node_get_int(value));
165 | break;
166 | }
167 | #endif
168 |
169 | return pblite_decode(*pmessage, json_node_get_array(value), FALSE);
170 | }
171 |
172 | default: {
173 | //todo glib fail thing
174 | return FALSE;
175 | }
176 | }
177 | }
178 |
179 |
180 | static JsonNode *
181 | pblite_encode_field(const ProtobufCFieldDescriptor *field, gpointer value)
182 | {
183 | JsonNode *node = NULL;
184 |
185 | switch (field->type) {
186 | case PROTOBUF_C_TYPE_INT32:
187 | case PROTOBUF_C_TYPE_UINT32:
188 | case PROTOBUF_C_TYPE_SFIXED32:
189 | case PROTOBUF_C_TYPE_FIXED32:
190 | case PROTOBUF_C_TYPE_FLOAT:
191 | case PROTOBUF_C_TYPE_ENUM: {
192 | uint32_t * member = value;
193 | node = json_node_new(JSON_NODE_VALUE);
194 | json_node_set_int(node, *member);
195 | break;
196 | }
197 |
198 | case PROTOBUF_C_TYPE_SINT32: {
199 | int32_t * member = value;
200 | node = json_node_new(JSON_NODE_VALUE);
201 | json_node_set_int(node, *member);
202 | break;
203 | }
204 |
205 | case PROTOBUF_C_TYPE_INT64:
206 | case PROTOBUF_C_TYPE_UINT64:
207 | case PROTOBUF_C_TYPE_SFIXED64:
208 | case PROTOBUF_C_TYPE_FIXED64:
209 | case PROTOBUF_C_TYPE_DOUBLE: {
210 | uint64_t * member = value;
211 | node = json_node_new(JSON_NODE_VALUE);
212 | json_node_set_int(node, *member);
213 | break;
214 | }
215 |
216 | case PROTOBUF_C_TYPE_SINT64: {
217 | int64_t * member = value;
218 | node = json_node_new(JSON_NODE_VALUE);
219 | json_node_set_int(node, *member);
220 | break;
221 | }
222 |
223 | case PROTOBUF_C_TYPE_BOOL: {
224 | protobuf_c_boolean * member = value;
225 | node = json_node_new(JSON_NODE_VALUE);
226 | json_node_set_boolean(node, *member);
227 | break;
228 | }
229 |
230 | case PROTOBUF_C_TYPE_STRING: {
231 | char **pstr = value;
232 | node = json_node_new(JSON_NODE_VALUE);
233 | json_node_set_string(node, *pstr);
234 | break;
235 | }
236 |
237 | case PROTOBUF_C_TYPE_BYTES: {
238 | ProtobufCBinaryData *bd = value;
239 | gchar *b64_data;
240 |
241 | b64_data = g_base64_encode(bd->data, bd->len);
242 | node = json_node_new(JSON_NODE_VALUE);
243 | json_node_set_string(node, b64_data);
244 | g_free(b64_data);
245 | break;
246 | }
247 |
248 | case PROTOBUF_C_TYPE_MESSAGE: {
249 | ProtobufCMessage **pmessage = value;
250 |
251 | node = json_node_new(JSON_NODE_ARRAY);
252 | if (pmessage != NULL) {
253 | json_node_take_array(node, pblite_encode(*pmessage));
254 | }
255 | break;
256 | }
257 | }
258 |
259 | return node;
260 | }
261 |
262 | static gboolean
263 | pblite_decode_element(ProtobufCMessage *message, guint index, JsonNode *value)
264 | {
265 | const ProtobufCFieldDescriptor *field;
266 | gboolean success = TRUE;
267 |
268 | #ifdef DEBUG
269 | printf("pblite_decode_element field %d ", index);
270 | #endif
271 | field = protobuf_c_message_descriptor_get_field(message->descriptor, index);
272 | if (!field) {
273 | #ifdef DEBUG
274 | gchar *json = json_pretty_encode(value, NULL);
275 | printf("skipped unknown %s\n", json);
276 | g_free(json);
277 | #endif
278 | return TRUE;
279 | }
280 | #ifdef DEBUG
281 | printf("is %s\n", field->name);
282 | #endif
283 |
284 | if (JSON_NODE_HOLDS_NULL(value)) {
285 | #ifdef DEBUG
286 | printf("pblite_decode_element field %d skipped null\n", index);
287 | #endif
288 | if (field->default_value != NULL) {
289 | *(const void **) STRUCT_MEMBER(void *, message, field->offset) = field->default_value;
290 | }
291 | return TRUE;
292 | }
293 |
294 | if (field->label == PROTOBUF_C_LABEL_REPEATED) {
295 | JsonArray *value_array = json_node_get_array(value);
296 | guint j;
297 | size_t siz;
298 | size_t array_len;
299 | void *tmp;
300 |
301 | array_len = json_array_get_length(value_array);
302 | #ifdef DEBUG
303 | printf(" which is an array of length %d\n", array_len);
304 | #endif
305 |
306 | //see protobuf-c.c:3068
307 | siz = sizeof_elt_in_repeated_array(field->type);
308 | STRUCT_MEMBER(size_t, message, field->quantifier_offset) = array_len;
309 | STRUCT_MEMBER(void *, message, field->offset) = tmp = g_malloc0(siz * array_len);
310 |
311 | for (j = 0; j < array_len; j++) {
312 | #ifdef DEBUG
313 | if (JSON_NODE_HOLDS_NULL(json_array_get_element(value_array, j))) {
314 | printf("array %d contains null\n", j);
315 | }
316 | #endif
317 | success = pblite_decode_field(field, json_array_get_element(value_array, j), STRUCT_MEMBER(void *, message, field->offset) + (siz * j));
318 | if (!success) {
319 | g_free(tmp);
320 | g_return_val_if_fail(success, FALSE);
321 | }
322 | }
323 | } else {
324 | success = pblite_decode_field(field, value, STRUCT_MEMBER_P(message, field->offset));
325 | g_return_val_if_fail(success, FALSE);
326 |
327 | if (field->label == PROTOBUF_C_LABEL_OPTIONAL && field->quantifier_offset) {
328 | STRUCT_MEMBER(protobuf_c_boolean, message, field->quantifier_offset) = TRUE;
329 | }
330 | }
331 |
332 | #ifdef DEBUG
333 | printf("end\n");
334 | #endif
335 | return TRUE;
336 | }
337 |
338 | gboolean
339 | pblite_decode(ProtobufCMessage *message, JsonArray *pblite_array, gboolean ignore_first_item)
340 | {
341 | const ProtobufCMessageDescriptor *descriptor = message->descriptor;
342 | guint i, len;
343 | guint offset = (ignore_first_item ? 1 : 0);
344 | gboolean last_element_is_object = FALSE;
345 |
346 | g_return_val_if_fail(descriptor, FALSE);
347 |
348 | len = json_array_get_length(pblite_array);
349 | #ifdef DEBUG
350 | printf("pblite_decode of %s with length %d\n", descriptor->name, len);
351 | #endif
352 | if (len == 0) {
353 | return TRUE;
354 | }
355 |
356 | if (JSON_NODE_HOLDS_OBJECT(json_array_get_element(pblite_array, len - 1))) {
357 | #ifdef DEBUG
358 | printf("ZOMG the last element is an object\n");
359 | #endif
360 | last_element_is_object = TRUE;
361 | len = len - 1;
362 | }
363 |
364 | for (i = offset; i < len; i++) {
365 | //stuff
366 | JsonNode *value = json_array_get_element(pblite_array, i);
367 | gboolean success = pblite_decode_element(message, i - offset + 1, value);
368 |
369 | g_return_val_if_fail(success, FALSE);
370 | }
371 |
372 | //Continue on the array with the objects
373 | if (last_element_is_object) {
374 | JsonObject *last_object = json_array_get_object_element(pblite_array, len);
375 | GList *members = json_object_get_members(last_object);
376 | GList *l;
377 | for (l = members; l != NULL; l = l->next) {
378 | const gchar *member_name = l->data;
379 | guint64 member = g_ascii_strtoull(member_name, NULL, 0);
380 | JsonNode *value = json_object_get_member(last_object, member_name);
381 |
382 | gboolean success = pblite_decode_element(message, member - offset, value);
383 |
384 | g_return_val_if_fail(success, FALSE);
385 | }
386 | g_list_free(members);
387 | }
388 |
389 | return TRUE;
390 | }
391 |
392 | JsonArray *
393 | pblite_encode(ProtobufCMessage *message)
394 | {
395 | //Maiku asked for more dinosaurs
396 | JsonArray *pblite = json_array_new();
397 | JsonObject *cheats_object = json_object_new();
398 | const ProtobufCMessageDescriptor *descriptor = message->descriptor;
399 | guint i;
400 | #ifdef DEBUG
401 | printf("pblite_encode of %s with length %d\n", descriptor->name, descriptor->n_fields);
402 | #endif
403 |
404 | for (i = 0; i < descriptor->n_fields; i++) {
405 | const ProtobufCFieldDescriptor *field_descriptor = descriptor->fields + i;
406 | void *field = STRUCT_MEMBER_P(message, field_descriptor->offset);
407 | JsonNode *encoded_value = NULL;
408 |
409 | #ifdef DEBUG
410 | printf("pblite_encode_element field %d (%d) ", i, field_descriptor->id);
411 | #endif
412 | #ifdef DEBUG
413 | printf("is %s\n", field_descriptor->name);
414 | #endif
415 |
416 | if (field_descriptor->label == PROTOBUF_C_LABEL_REPEATED) {
417 | guint j;
418 | size_t siz;
419 | size_t array_len;
420 | JsonArray *value_array;
421 |
422 | siz = sizeof_elt_in_repeated_array(field_descriptor->type);
423 | array_len = STRUCT_MEMBER(size_t, message, field_descriptor->quantifier_offset);
424 |
425 | #ifdef DEBUG
426 | printf(" which is an array of length %d\n", array_len);
427 | #endif
428 |
429 | value_array = json_array_new();
430 | for (j = 0; j < array_len; j++) {
431 | field = STRUCT_MEMBER(void *, message, field_descriptor->offset) + (siz * j);
432 | json_array_add_element(value_array, pblite_encode_field(field_descriptor, field));
433 | }
434 | encoded_value = json_node_new(JSON_NODE_ARRAY);
435 | json_node_take_array(encoded_value, value_array);
436 | } else {
437 | if (field_descriptor->label == PROTOBUF_C_LABEL_OPTIONAL) {
438 | if (field_descriptor->type == PROTOBUF_C_TYPE_MESSAGE ||
439 | field_descriptor->type == PROTOBUF_C_TYPE_STRING)
440 | {
441 | const void *ptr = *(const void * const *) field;
442 | if (ptr == NULL || ptr == field_descriptor->default_value)
443 | encoded_value = json_node_new(JSON_NODE_NULL);
444 | } else {
445 | const protobuf_c_boolean *val = STRUCT_MEMBER_P(message, field_descriptor->quantifier_offset);
446 | if (!*val)
447 | encoded_value = json_node_new(JSON_NODE_NULL);
448 | }
449 | }
450 | if (encoded_value == NULL) {
451 | encoded_value = pblite_encode_field(field_descriptor, field);
452 | }
453 | }
454 |
455 | if (json_array_get_length(pblite) + 1 == field_descriptor->id) {
456 | json_array_add_element(pblite, encoded_value);
457 | } else {
458 | //need to insert at point [field_descriptor->id - 1] of the array, somehow
459 | //... so dont, just cheat!
460 | if (!JSON_NODE_HOLDS_NULL(encoded_value)) {
461 | gchar *obj_id = g_strdup_printf("%u", field_descriptor->id);
462 | json_object_set_member(cheats_object, obj_id, encoded_value);
463 | g_free(obj_id);
464 | } else {
465 | json_node_free(encoded_value);
466 | }
467 | }
468 | }
469 | if (json_object_get_size(cheats_object)) {
470 | json_array_add_object_element(pblite, cheats_object);
471 | } else {
472 | json_object_unref(cheats_object);
473 | }
474 |
475 | return pblite;
476 | }
477 |
478 |
479 |
480 |
481 |
482 | static JsonObject *pblite_encode_for_json(ProtobufCMessage *message);
483 |
484 | static JsonNode *
485 | pblite_encode_field_for_json(const ProtobufCFieldDescriptor *field, gpointer value)
486 | {
487 | JsonNode *node = NULL;
488 |
489 | switch (field->type) {
490 | case PROTOBUF_C_TYPE_INT32:
491 | case PROTOBUF_C_TYPE_UINT32:
492 | case PROTOBUF_C_TYPE_SFIXED32:
493 | case PROTOBUF_C_TYPE_FIXED32:
494 | case PROTOBUF_C_TYPE_FLOAT: {
495 | uint32_t * member = value;
496 | node = json_node_new(JSON_NODE_VALUE);
497 | json_node_set_int(node, *member);
498 | break;
499 | }
500 | case PROTOBUF_C_TYPE_ENUM: {
501 | uint32_t * member = value;
502 | const ProtobufCEnumDescriptor *enum_descriptor = field->descriptor;
503 | const ProtobufCEnumValue *enum_value = protobuf_c_enum_descriptor_get_value(enum_descriptor, *member);
504 | node = json_node_new(JSON_NODE_VALUE);
505 | if (enum_value == NULL) {
506 | gchar *unknown_text = g_strdup_printf("UNKNOWN ENUM VALUE %u", *member);
507 | json_node_set_string(node, unknown_text);
508 | g_free(unknown_text);
509 | } else {
510 | json_node_set_string(node, enum_value->name);
511 | }
512 | break;
513 | }
514 |
515 | case PROTOBUF_C_TYPE_SINT32: {
516 | int32_t * member = value;
517 | node = json_node_new(JSON_NODE_VALUE);
518 | json_node_set_int(node, *member);
519 | break;
520 | }
521 |
522 | case PROTOBUF_C_TYPE_INT64:
523 | case PROTOBUF_C_TYPE_UINT64:
524 | case PROTOBUF_C_TYPE_SFIXED64:
525 | case PROTOBUF_C_TYPE_FIXED64:
526 | case PROTOBUF_C_TYPE_DOUBLE: {
527 | uint64_t * member = value;
528 | node = json_node_new(JSON_NODE_VALUE);
529 | json_node_set_int(node, *member);
530 | break;
531 | }
532 |
533 | case PROTOBUF_C_TYPE_SINT64: {
534 | int64_t * member = value;
535 | node = json_node_new(JSON_NODE_VALUE);
536 | json_node_set_int(node, *member);
537 | break;
538 | }
539 |
540 | case PROTOBUF_C_TYPE_BOOL: {
541 | protobuf_c_boolean * member = value;
542 | node = json_node_new(JSON_NODE_VALUE);
543 | json_node_set_boolean(node, *member);
544 | break;
545 | }
546 |
547 | case PROTOBUF_C_TYPE_STRING: {
548 | char **pstr = value;
549 | node = json_node_new(JSON_NODE_VALUE);
550 | json_node_set_string(node, *pstr);
551 | break;
552 | }
553 |
554 | case PROTOBUF_C_TYPE_BYTES: {
555 | ProtobufCBinaryData *bd = value;
556 | gchar *b64_data;
557 |
558 | b64_data = g_base64_encode(bd->data, bd->len);
559 | node = json_node_new(JSON_NODE_VALUE);
560 | json_node_set_string(node, b64_data);
561 | g_free(b64_data);
562 | break;
563 | }
564 |
565 | case PROTOBUF_C_TYPE_MESSAGE: {
566 | ProtobufCMessage **pmessage = value;
567 |
568 | node = json_node_new(JSON_NODE_OBJECT);
569 | if (pmessage != NULL) {
570 | json_node_take_object(node, pblite_encode_for_json(*pmessage));
571 | }
572 | break;
573 | }
574 | }
575 |
576 | return node;
577 | }
578 |
579 | static JsonObject *
580 | pblite_encode_for_json(ProtobufCMessage *message)
581 | {
582 | JsonObject *pblite = json_object_new();
583 | const ProtobufCMessageDescriptor *descriptor = message->descriptor;
584 | guint i;
585 |
586 | for (i = 0; i < descriptor->n_fields; i++) {
587 | const ProtobufCFieldDescriptor *field_descriptor = descriptor->fields + i;
588 | void *field = STRUCT_MEMBER_P(message, field_descriptor->offset);
589 | JsonNode *encoded_value = NULL;
590 |
591 | if (field_descriptor->label == PROTOBUF_C_LABEL_REPEATED) {
592 | guint j;
593 | size_t siz;
594 | size_t array_len;
595 | JsonArray *value_array;
596 |
597 | siz = sizeof_elt_in_repeated_array(field_descriptor->type);
598 | array_len = STRUCT_MEMBER(size_t, message, field_descriptor->quantifier_offset);
599 |
600 | value_array = json_array_new();
601 | for (j = 0; j < array_len; j++) {
602 | field = STRUCT_MEMBER(void *, message, field_descriptor->offset) + (siz * j);
603 | json_array_add_element(value_array, pblite_encode_field_for_json(field_descriptor, field));
604 | }
605 | encoded_value = json_node_new(JSON_NODE_ARRAY);
606 | json_node_take_array(encoded_value, value_array);
607 | } else {
608 | if (field_descriptor->label == PROTOBUF_C_LABEL_OPTIONAL) {
609 | if (field_descriptor->type == PROTOBUF_C_TYPE_MESSAGE ||
610 | field_descriptor->type == PROTOBUF_C_TYPE_STRING)
611 | {
612 | const void *ptr = *(const void * const *) field;
613 | if (ptr == NULL || ptr == field_descriptor->default_value)
614 | encoded_value = json_node_new(JSON_NODE_NULL);
615 | } else {
616 | const protobuf_c_boolean *val = STRUCT_MEMBER_P(message, field_descriptor->quantifier_offset);
617 | if (!*val)
618 | encoded_value = json_node_new(JSON_NODE_NULL);
619 | }
620 | }
621 | if (encoded_value == NULL) {
622 | encoded_value = pblite_encode_field_for_json(field_descriptor, field);
623 | }
624 | }
625 |
626 | json_object_set_member(pblite, field_descriptor->name, encoded_value);
627 | }
628 |
629 | return pblite;
630 | }
631 |
632 |
633 | gchar *
634 | pblite_dump_json(ProtobufCMessage *message)
635 | {
636 | JsonObject *pblite = pblite_encode_for_json(message);
637 | JsonNode *node = json_node_new(JSON_NODE_OBJECT);
638 | gchar *json;
639 |
640 | json_node_take_object(node, pblite);
641 | json = json_pretty_encode(node, NULL);
642 |
643 | json_node_free(node);
644 | return json;
645 | }
646 |
--------------------------------------------------------------------------------
/hangouts_pblite.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #ifndef _HANGOUTS_PBLITE_H_
21 | #define _HANGOUTS_PBLITE_H_
22 |
23 | #include
24 |
25 |
26 | #include "hangouts_json.h"
27 | #include "hangouts.pb-c.h"
28 |
29 | /**
30 | * Decode a JsonArray into a ProtobufCMessage.
31 | *
32 | * \param message
33 | * The message type to store as. Is also used as the output.
34 | * \param pblite_array
35 | * The JSON array of data to parse.
36 | * \param ignore_first_item
37 | * Whether to skip the first item in the array (sometimes it's garbage data to identify type).
38 | * \return
39 | * TRUE if successful, FALSE if there was an error.
40 | */
41 | gboolean pblite_decode(ProtobufCMessage *message, JsonArray *pblite_array, gboolean ignore_first_item);
42 |
43 | /**
44 | * Encodes a ProtobufCMessage into a JsonArray.
45 | *
46 | * \param message
47 | * The message to parse into the equivalent pblite array.
48 | * \return
49 | * The JSON array of data, ready to be sent.
50 | */
51 | JsonArray *pblite_encode(ProtobufCMessage *message);
52 |
53 |
54 | gchar *pblite_dump_json(ProtobufCMessage *message);
55 |
56 | #endif /* _HANGOUTS_PBLITE_H_ */
57 |
--------------------------------------------------------------------------------
/libhangouts.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 | #include "libhangouts.h"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include "accountopt.h"
27 | #include "cmds.h"
28 | #include "debug.h"
29 | #include "mediamanager.h"
30 | #include "plugins.h"
31 | #include "request.h"
32 | #include "version.h"
33 |
34 | #include "hangouts_auth.h"
35 | #include "hangouts_pblite.h"
36 | #include "hangouts_json.h"
37 | #include "hangouts_events.h"
38 | #include "hangouts_connection.h"
39 | #include "hangouts_conversation.h"
40 | #include "hangouts_media.h"
41 |
42 |
43 | /*****************************************************************************/
44 | //TODO move to nicer place
45 |
46 | gboolean
47 | hangouts_is_valid_id(const gchar *id)
48 | {
49 | gint i;
50 |
51 | for (i = strlen(id) - 1; i >= 0; i--) {
52 | if (!g_ascii_isdigit(id[i])) {
53 | return FALSE;
54 | }
55 | }
56 |
57 | return TRUE;
58 | }
59 |
60 | PurpleMediaCaps
61 | hangouts_get_media_caps(PurpleAccount *account, const char *who)
62 | {
63 | return PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_AUDIO_VIDEO | PURPLE_MEDIA_CAPS_MODIFY_SESSION;
64 | }
65 |
66 | void
67 | hangouts_set_idle(PurpleConnection *pc, int time)
68 | {
69 | HangoutsAccount *ha;
70 |
71 | ha = purple_connection_get_protocol_data(pc);
72 |
73 | if (time < HANGOUTS_ACTIVE_CLIENT_TIMEOUT) {
74 | hangouts_set_active_client(pc);
75 | }
76 | ha->idle_time = time;
77 | }
78 |
79 | static GList *
80 | hangouts_add_account_options(GList *account_options)
81 | {
82 | PurpleAccountOption *option;
83 |
84 | option = purple_account_option_bool_new(N_("Show call links in chat"), "show-call-links", !purple_media_manager_get());
85 | account_options = g_list_append(account_options, option);
86 |
87 | option = purple_account_option_bool_new(N_("Un-Googlify URLs"), "unravel_google_url", FALSE);
88 | account_options = g_list_append(account_options, option);
89 |
90 | option = purple_account_option_bool_new(N_("Treat invisible users as offline"), "treat_invisible_as_offline", FALSE);
91 | account_options = g_list_append(account_options, option);
92 |
93 | option = purple_account_option_bool_new(N_("Hide self from buddy list (requires reconnect)"), "hide_self", FALSE);
94 | account_options = g_list_append(account_options, option);
95 |
96 | option = purple_account_option_bool_new(N_("Fetch image history when opening group chats"), "fetch_image_history", TRUE);
97 | account_options = g_list_append(account_options, option);
98 |
99 | return account_options;
100 | }
101 |
102 | static void
103 | hangouts_blist_node_removed(PurpleBlistNode *node)
104 | {
105 | PurpleChat *chat = NULL;
106 | PurpleBuddy *buddy = NULL;
107 | PurpleAccount *account = NULL;
108 | PurpleConnection *pc;
109 | const gchar *conv_id;
110 | GHashTable *components;
111 |
112 | if (PURPLE_IS_CHAT(node)) {
113 | chat = PURPLE_CHAT(node);
114 | account = purple_chat_get_account(chat);
115 | } else if (PURPLE_IS_BUDDY(node)) {
116 | buddy = PURPLE_BUDDY(node);
117 | account = purple_buddy_get_account(buddy);
118 | }
119 |
120 | if (account == NULL) {
121 | return;
122 | }
123 |
124 | if (!purple_strequal(purple_account_get_protocol_id(account), HANGOUTS_PLUGIN_ID)) {
125 | return;
126 | }
127 |
128 | pc = purple_account_get_connection(account);
129 | if (pc == NULL) {
130 | return;
131 | }
132 |
133 | if (chat != NULL) {
134 | components = purple_chat_get_components(chat);
135 | conv_id = g_hash_table_lookup(components, "conv_id");
136 | if (conv_id == NULL) {
137 | conv_id = purple_chat_get_name_only(chat);
138 | }
139 |
140 | hangouts_chat_leave_by_conv_id(pc, conv_id, NULL);
141 | } else {
142 | HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
143 | const gchar *gaia_id = purple_buddy_get_name(buddy);
144 | conv_id = g_hash_table_lookup(ha->one_to_ones_rev, gaia_id);
145 |
146 | hangouts_archive_conversation(ha, conv_id);
147 |
148 | if (purple_strequal(gaia_id, ha->self_gaia_id)) {
149 | purple_account_set_bool(account, "hide_self", TRUE);
150 | }
151 | }
152 | }
153 |
154 | static void
155 | hangouts_blist_node_aliased(PurpleBlistNode *node, const char *old_alias)
156 | {
157 | PurpleChat *chat = NULL;
158 | PurpleAccount *account = NULL;
159 | PurpleConnection *pc;
160 | const gchar *conv_id;
161 | GHashTable *components;
162 | HangoutsAccount *ha;
163 | const gchar *new_alias;
164 |
165 | if (PURPLE_IS_CHAT(node)) {
166 | chat = PURPLE_CHAT(node);
167 | account = purple_chat_get_account(chat);
168 | }
169 |
170 | if (account == NULL) {
171 | return;
172 | }
173 |
174 | if (!purple_strequal(purple_account_get_protocol_id(account), HANGOUTS_PLUGIN_ID)) {
175 | return;
176 | }
177 |
178 | pc = purple_account_get_connection(account);
179 | if (pc == NULL) {
180 | return;
181 | }
182 | ha = purple_connection_get_protocol_data(pc);
183 |
184 | if (g_dataset_get_data(ha, "ignore_set_alias")) {
185 | return;
186 | }
187 |
188 | if (chat != NULL) {
189 | new_alias = purple_chat_get_alias(chat);
190 |
191 | // Don't send update to existing update
192 | if (!purple_strequal(old_alias, new_alias)) {
193 | components = purple_chat_get_components(chat);
194 | conv_id = g_hash_table_lookup(components, "conv_id");
195 | if (conv_id == NULL) {
196 | conv_id = purple_chat_get_name_only(chat);
197 | }
198 |
199 | hangouts_rename_conversation(ha, conv_id, new_alias);
200 | }
201 | }
202 | }
203 |
204 | static void
205 | hangouts_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
206 | {
207 | const gchar *conv_id;
208 | PurpleChatConversation *chatconv;
209 | HangoutsAccount *ha;
210 |
211 | ha = purple_connection_get_protocol_data(pc);
212 | chatconv = purple_conversations_find_chat(pc, id);
213 | conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
214 | if (conv_id == NULL) {
215 | // Fix for a race condition around the chat data and serv_got_joined_chat()
216 | conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
217 | }
218 |
219 | return hangouts_rename_conversation(ha, conv_id, topic);
220 | }
221 |
222 | static PurpleCmdRet
223 | hangouts_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
224 | {
225 | PurpleConnection *pc = NULL;
226 | int id = -1;
227 |
228 | pc = purple_conversation_get_connection(conv);
229 | id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
230 |
231 | if (pc == NULL || id == -1)
232 | return PURPLE_CMD_RET_FAILED;
233 |
234 | hangouts_chat_leave(pc, id);
235 |
236 | return PURPLE_CMD_RET_OK;
237 | }
238 |
239 | static PurpleCmdRet
240 | hangouts_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
241 | {
242 | PurpleConnection *pc = NULL;
243 | int id = -1;
244 |
245 | pc = purple_conversation_get_connection(conv);
246 | id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
247 |
248 | if (pc == NULL || id == -1)
249 | return PURPLE_CMD_RET_FAILED;
250 |
251 | hangouts_chat_kick(pc, id, args[0]);
252 |
253 | return PURPLE_CMD_RET_OK;
254 | }
255 |
256 | static GList *
257 | hangouts_node_menu(PurpleBlistNode *node)
258 | {
259 | GList *m = NULL;
260 | PurpleMenuAction *act;
261 |
262 | if(PURPLE_IS_BUDDY(node))
263 | {
264 | act = purple_menu_action_new(_("Initiate _Chat"),
265 | PURPLE_CALLBACK(hangouts_initiate_chat_from_node),
266 | NULL, NULL);
267 | m = g_list_append(m, act);
268 | } else if (PURPLE_IS_CHAT(node)) {
269 | act = purple_menu_action_new(_("_Leave Chat"),
270 | PURPLE_CALLBACK(hangouts_blist_node_removed), // A strange coinkidink
271 | NULL, NULL);
272 | m = g_list_append(m, act);
273 | }
274 |
275 | return m;
276 | }
277 |
278 | static void
279 | hangouts_join_chat_by_url_action(PurpleProtocolAction *action)
280 | {
281 | PurpleConnection *pc = purple_protocol_action_get_connection(action);
282 | HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
283 |
284 | purple_request_input(pc, _("Join chat..."),
285 | _("Join a Hangouts group chat from the invite URL..."),
286 | NULL,
287 | NULL, FALSE, FALSE, "https://hangouts.google.com/group/...",
288 | _("_Join"), G_CALLBACK(hangouts_join_chat_from_url),
289 | _("_Cancel"), NULL,
290 | purple_request_cpar_from_connection(pc),
291 | ha);
292 |
293 | }
294 |
295 | static GList *
296 | hangouts_actions(
297 | #if !PURPLE_VERSION_CHECK(3, 0, 0)
298 | PurplePlugin *plugin, gpointer context
299 | #else
300 | PurpleConnection *pc
301 | #endif
302 | )
303 | {
304 | GList *m = NULL;
305 | PurpleProtocolAction *act;
306 |
307 | act = purple_protocol_action_new(_("Search for friends..."), hangouts_search_users);
308 | m = g_list_append(m, act);
309 |
310 | act = purple_protocol_action_new(_("Join a group chat by URL..."), hangouts_join_chat_by_url_action);
311 | m = g_list_append(m, act);
312 |
313 | return m;
314 | }
315 |
316 | static void
317 | hangouts_authcode_input_cb(gpointer user_data, const gchar *auth_code)
318 | {
319 | HangoutsAccount *ha = user_data;
320 | PurpleConnection *pc = ha->pc;
321 |
322 | purple_connection_update_progress(pc, _("Authenticating"), 1, 3);
323 | hangouts_oauth_with_code(ha, auth_code);
324 | }
325 |
326 | static void
327 | hangouts_authcode_input_cancel_cb(gpointer user_data)
328 | {
329 | HangoutsAccount *ha = user_data;
330 | purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
331 | _("User cancelled authorization"));
332 | }
333 |
334 | static gulong chat_conversation_typing_signal = 0;
335 |
336 | #if !PURPLE_VERSION_CHECK(3, 0, 0)
337 | static gulong deleting_chat_buddy_signal = 0;
338 |
339 | // See workaround for purple_chat_conversation_find_user() in purplecompat.h
340 | static void
341 | hangouts_deleting_chat_buddy(PurpleConvChatBuddy *cb)
342 | {
343 | if (g_dataset_get_data(cb, "chat") != NULL) {
344 | g_dataset_destroy(cb);
345 | }
346 | }
347 | #endif
348 |
349 | static void
350 | hangouts_login(PurpleAccount *account)
351 | {
352 | PurpleConnection *pc;
353 | HangoutsAccount *ha; //hahaha
354 | const gchar *password;
355 | const gchar *self_gaia_id;
356 | PurpleConnectionFlags pc_flags;
357 |
358 | pc = purple_account_get_connection(account);
359 | password = purple_connection_get_password(pc);
360 |
361 | pc_flags = purple_connection_get_flags(pc);
362 | pc_flags |= PURPLE_CONNECTION_FLAG_HTML;
363 | pc_flags |= PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
364 | pc_flags |= PURPLE_CONNECTION_FLAG_NO_BGCOLOR;
365 | pc_flags &= ~PURPLE_CONNECTION_FLAG_NO_IMAGES;
366 | purple_connection_set_flags(pc, pc_flags);
367 |
368 | ha = g_new0(HangoutsAccount, 1);
369 | ha->account = account;
370 | ha->pc = pc;
371 | ha->cookie_jar = purple_http_cookie_jar_new();
372 | ha->channel_buffer = g_byte_array_sized_new(HANGOUTS_BUFFER_DEFAULT_SIZE);
373 | ha->channel_keepalive_pool = purple_http_keepalive_pool_new();
374 | ha->client6_keepalive_pool = purple_http_keepalive_pool_new();
375 | ha->sent_message_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
376 |
377 | ha->one_to_ones = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
378 | ha->one_to_ones_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
379 | ha->group_chats = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
380 | ha->google_voice_conversations = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
381 |
382 | self_gaia_id = purple_account_get_string(account, "self_gaia_id", NULL);
383 | if (self_gaia_id != NULL) {
384 | ha->self_gaia_id = g_strdup(self_gaia_id);
385 | purple_connection_set_display_name(pc, ha->self_gaia_id);
386 | }
387 |
388 | purple_connection_set_protocol_data(pc, ha);
389 |
390 | if (password && *password) {
391 | ha->refresh_token = g_strdup(password);
392 | purple_connection_update_progress(pc, _("Authenticating"), 1, 3);
393 | hangouts_oauth_refresh_token(ha);
394 | } else {
395 | //TODO get this code automatically
396 | purple_notify_uri(pc, "https://www.youtube.com/watch?v=hlDhp-eNLMU");
397 | purple_request_input(pc, _("Authorization Code"), "https://www.youtube.com/watch?v=hlDhp-eNLMU",
398 | _ ("Please follow the YouTube video to get the OAuth code"),
399 | _ ("and then paste the Google OAuth code here"), FALSE, FALSE, NULL,
400 | _("OK"), G_CALLBACK(hangouts_authcode_input_cb),
401 | _("Cancel"), G_CALLBACK(hangouts_authcode_input_cancel_cb),
402 | purple_request_cpar_from_connection(pc), ha);
403 | }
404 |
405 | purple_signal_connect(purple_blist_get_handle(), "blist-node-removed", account, PURPLE_CALLBACK(hangouts_blist_node_removed), NULL);
406 | purple_signal_connect(purple_blist_get_handle(), "blist-node-aliased", account, PURPLE_CALLBACK(hangouts_blist_node_aliased), NULL);
407 | purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", account, PURPLE_CALLBACK(hangouts_mark_conversation_seen), NULL);
408 | if (!chat_conversation_typing_signal) {
409 | chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(hangouts_conv_send_typing), NULL);
410 | }
411 |
412 | #if !PURPLE_VERSION_CHECK(3, 0, 0)
413 | if (!deleting_chat_buddy_signal) {
414 | deleting_chat_buddy_signal = purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy", purple_connection_get_protocol(pc), PURPLE_CALLBACK(hangouts_deleting_chat_buddy), NULL);
415 | }
416 | #endif
417 |
418 | ha->active_client_timeout = g_timeout_add_seconds(HANGOUTS_ACTIVE_CLIENT_TIMEOUT, ((GSourceFunc) hangouts_set_active_client), pc);
419 | }
420 |
421 | static void
422 | hangouts_close(PurpleConnection *pc)
423 | {
424 | HangoutsAccount *ha; //not so funny anymore
425 |
426 | ha = purple_connection_get_protocol_data(pc);
427 | purple_signals_disconnect_by_handle(ha->account);
428 |
429 | g_source_remove(ha->active_client_timeout);
430 | g_source_remove(ha->channel_watchdog);
431 | g_source_remove(ha->poll_buddy_status_timeout);
432 |
433 | purple_http_conn_cancel_all(pc);
434 |
435 | purple_http_keepalive_pool_unref(ha->channel_keepalive_pool);
436 | purple_http_keepalive_pool_unref(ha->client6_keepalive_pool);
437 | g_free(ha->self_gaia_id);
438 | g_free(ha->self_phone);
439 | g_free(ha->refresh_token);
440 | g_free(ha->access_token);
441 | g_free(ha->gsessionid_param);
442 | g_free(ha->sid_param);
443 | g_free(ha->client_id);
444 | purple_http_cookie_jar_unref(ha->cookie_jar);
445 | g_byte_array_free(ha->channel_buffer, TRUE);
446 |
447 | g_hash_table_remove_all(ha->sent_message_ids);
448 | g_hash_table_unref(ha->sent_message_ids);
449 | g_hash_table_remove_all(ha->one_to_ones);
450 | g_hash_table_unref(ha->one_to_ones);
451 | g_hash_table_remove_all(ha->one_to_ones_rev);
452 | g_hash_table_unref(ha->one_to_ones_rev);
453 | g_hash_table_remove_all(ha->group_chats);
454 | g_hash_table_unref(ha->group_chats);
455 | g_hash_table_remove_all(ha->google_voice_conversations);
456 | g_hash_table_unref(ha->google_voice_conversations);
457 |
458 | g_free(ha);
459 | }
460 |
461 |
462 | static const char *
463 | hangouts_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
464 | {
465 | return "hangouts";
466 | }
467 |
468 | static void
469 | hangouts_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
470 | {
471 | PurplePresence *presence;
472 | PurpleStatus *status;
473 | const gchar *message;
474 | HangoutsBuddy *hbuddy;
475 |
476 | g_return_if_fail(buddy != NULL);
477 |
478 | presence = purple_buddy_get_presence(buddy);
479 | status = purple_presence_get_active_status(presence);
480 | purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
481 |
482 | message = purple_status_get_attr_string(status, "message");
483 | if (message != NULL) {
484 | purple_notify_user_info_add_pair_html(user_info, _("Message"), message);
485 | }
486 |
487 | hbuddy = purple_buddy_get_protocol_data(buddy);
488 | if (hbuddy != NULL) {
489 | if (hbuddy->last_seen != 0) {
490 | gchar *seen = purple_str_seconds_to_string(time(NULL) - hbuddy->last_seen);
491 | purple_notify_user_info_add_pair_html(user_info, _("Last seen"), seen);
492 | g_free(seen);
493 | }
494 |
495 | if (hbuddy->in_call) {
496 | purple_notify_user_info_add_pair_html(user_info, _("In call"), NULL);
497 | }
498 |
499 | if (hbuddy->device_type) {
500 | purple_notify_user_info_add_pair_html(user_info, _("Device Type"),
501 | hbuddy->device_type & HANGOUTS_DEVICE_TYPE_DESKTOP ? _("Desktop") :
502 | hbuddy->device_type & HANGOUTS_DEVICE_TYPE_TABLET ? _("Tablet") :
503 | hbuddy->device_type & HANGOUTS_DEVICE_TYPE_MOBILE ? _("Mobile") :
504 | _("Unknown"));
505 | }
506 | }
507 | }
508 |
509 | GList *
510 | hangouts_status_types(PurpleAccount *account)
511 | {
512 | GList *types = NULL;
513 | PurpleStatusType *status;
514 |
515 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
516 | types = g_list_append(types, status);
517 |
518 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, NULL, NULL, FALSE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
519 | types = g_list_append(types, status);
520 |
521 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, NULL, NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
522 | types = g_list_append(types, status);
523 |
524 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, NULL, _("Do Not Disturb"), FALSE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
525 | types = g_list_append(types, status);
526 |
527 | status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, "mobile", _("Phone"), FALSE, FALSE, FALSE);
528 | types = g_list_append(types, status);
529 |
530 | status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
531 | types = g_list_append(types, status);
532 |
533 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, "gone", NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
534 | types = g_list_append(types, status);
535 |
536 | status = purple_status_type_new_with_attrs(PURPLE_STATUS_INVISIBLE, NULL, NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
537 | types = g_list_append(types, status);
538 |
539 | return types;
540 | }
541 |
542 | static gchar *
543 | hangouts_status_text(PurpleBuddy *buddy)
544 | {
545 | const gchar *message = purple_status_get_attr_string(purple_presence_get_active_status(purple_buddy_get_presence(buddy)), "message");
546 |
547 | if (message == NULL) {
548 | return NULL;
549 | }
550 |
551 | return g_markup_printf_escaped("%s", message);
552 | }
553 |
554 | static void
555 | hangouts_buddy_free(PurpleBuddy *buddy)
556 | {
557 | HangoutsBuddy *hbuddy = purple_buddy_get_protocol_data(buddy);
558 |
559 | g_return_if_fail(hbuddy != NULL);
560 |
561 | g_free(hbuddy);
562 | }
563 |
564 | static gboolean
565 | hangouts_offline_message(const PurpleBuddy *buddy)
566 | {
567 | return TRUE;
568 | }
569 |
570 |
571 | /*****************************************************************************/
572 |
573 | static gboolean
574 | plugin_load(PurplePlugin *plugin, GError **error)
575 | {
576 | purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
577 | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
578 | HANGOUTS_PLUGIN_ID, hangouts_cmd_leave,
579 | _("leave: Leave the group chat"), NULL);
580 |
581 | purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
582 | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
583 | HANGOUTS_PLUGIN_ID, hangouts_cmd_kick,
584 | _("kick : Kick a user from the room."), NULL);
585 |
586 | return TRUE;
587 | }
588 |
589 | static gboolean
590 | plugin_unload(PurplePlugin *plugin, GError **error)
591 | {
592 | purple_signals_disconnect_by_handle(plugin);
593 |
594 | return TRUE;
595 | }
596 |
597 | #if PURPLE_VERSION_CHECK(3, 0, 0)
598 |
599 | G_MODULE_EXPORT GType hangouts_protocol_get_type(void);
600 | #define HANGOUTS_TYPE_PROTOCOL (hangouts_protocol_get_type())
601 | #define HANGOUTS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocol))
602 | #define HANGOUTS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocolClass))
603 | #define HANGOUTS_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), HANGOUTS_TYPE_PROTOCOL))
604 | #define HANGOUTS_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), HANGOUTS_TYPE_PROTOCOL))
605 | #define HANGOUTS_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocolClass))
606 |
607 | typedef struct _HangoutsProtocol
608 | {
609 | PurpleProtocol parent;
610 | } HangoutsProtocol;
611 |
612 | typedef struct _HangoutsProtocolClass
613 | {
614 | PurpleProtocolClass parent_class;
615 | } HangoutsProtocolClass;
616 |
617 | static void
618 | hangouts_protocol_init(PurpleProtocol *prpl_info)
619 | {
620 | PurpleProtocol *plugin = prpl_info, *info = prpl_info;
621 |
622 | info->id = HANGOUTS_PLUGIN_ID;
623 | info->name = "Hangouts";
624 |
625 | prpl_info->options = OPT_PROTO_NO_PASSWORD | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_MAIL_CHECK;
626 | prpl_info->account_options = hangouts_add_account_options(prpl_info->account_options);
627 |
628 | purple_signal_register(plugin, "hangouts-received-stateupdate",
629 | purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
630 | PURPLE_TYPE_CONNECTION,
631 | G_TYPE_OBJECT);
632 |
633 | purple_signal_register(plugin, "hangouts-gmail-notification",
634 | purple_marshal_VOID__POINTER_POINTER_POINTER, G_TYPE_NONE, 3,
635 | PURPLE_TYPE_CONNECTION,
636 | G_TYPE_STRING,
637 | G_TYPE_OBJECT);
638 |
639 | hangouts_register_events(plugin);
640 | }
641 |
642 | static void
643 | hangouts_protocol_class_init(PurpleProtocolClass *prpl_info)
644 | {
645 | prpl_info->login = hangouts_login;
646 | prpl_info->close = hangouts_close;
647 | prpl_info->status_types = hangouts_status_types;
648 | prpl_info->list_icon = hangouts_list_icon;
649 | }
650 |
651 | static void
652 | hangouts_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
653 | {
654 | prpl_info->get_actions = hangouts_actions;
655 | prpl_info->blist_node_menu = hangouts_node_menu;
656 | prpl_info->status_text = hangouts_status_text;
657 | prpl_info->tooltip_text = hangouts_tooltip_text;
658 | prpl_info->buddy_free = hangouts_buddy_free;
659 | prpl_info->offline_message = hangouts_offline_message;
660 | }
661 |
662 | static void
663 | hangouts_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
664 | {
665 | prpl_info->get_info = hangouts_get_info;
666 | prpl_info->set_status = hangouts_set_status;
667 | prpl_info->set_idle = hangouts_set_idle;
668 | }
669 |
670 | static void
671 | hangouts_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info)
672 | {
673 | prpl_info->add_deny = hangouts_block_user;
674 | prpl_info->rem_deny = hangouts_unblock_user;
675 | }
676 |
677 | static void
678 | hangouts_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
679 | {
680 | prpl_info->send = hangouts_send_im;
681 | prpl_info->send_typing = hangouts_send_typing;
682 | }
683 |
684 | static void
685 | hangouts_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
686 | {
687 | prpl_info->send = hangouts_chat_send;
688 | prpl_info->info = hangouts_chat_info;
689 | prpl_info->info_defaults = hangouts_chat_info_defaults;
690 | prpl_info->join = hangouts_join_chat;
691 | prpl_info->get_name = hangouts_get_chat_name;
692 | prpl_info->invite = hangouts_chat_invite;
693 | prpl_info->set_topic = hangouts_chat_set_topic;
694 | }
695 |
696 | static void
697 | hangouts_protocol_media_iface_init(PurpleProtocolMediaIface *prpl_info)
698 | {
699 | prpl_info->get_caps = hangouts_get_media_caps;
700 | prpl_info->initiate_session = hangouts_initiate_media;
701 | }
702 |
703 | static void
704 | hangouts_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
705 | {
706 | prpl_info->get_list = hangouts_roomlist_get_list;
707 | }
708 |
709 | static PurpleProtocol *hangouts_protocol;
710 |
711 | PURPLE_DEFINE_TYPE_EXTENDED(
712 | HangoutsProtocol, hangouts_protocol, PURPLE_TYPE_PROTOCOL, 0,
713 |
714 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
715 | hangouts_protocol_im_iface_init)
716 |
717 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
718 | hangouts_protocol_chat_iface_init)
719 |
720 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
721 | hangouts_protocol_client_iface_init)
722 |
723 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
724 | hangouts_protocol_server_iface_init)
725 |
726 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
727 | hangouts_protocol_privacy_iface_init)
728 |
729 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_MEDIA_IFACE,
730 | hangouts_protocol_media_iface_init)
731 |
732 | PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
733 | hangouts_protocol_roomlist_iface_init)
734 | );
735 |
736 | static gboolean
737 | libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
738 | {
739 | hangouts_protocol_register_type(plugin);
740 | hangouts_protocol = purple_protocols_add(HANGOUTS_TYPE_PROTOCOL, error);
741 | if (!hangouts_protocol)
742 | return FALSE;
743 |
744 | return plugin_load(plugin, error);
745 | }
746 |
747 | static gboolean
748 | libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
749 | {
750 | if (!plugin_unload(plugin, error))
751 | return FALSE;
752 |
753 | if (!purple_protocols_remove(hangouts_protocol, error))
754 | return FALSE;
755 |
756 | return TRUE;
757 | }
758 |
759 | static PurplePluginInfo *
760 | plugin_query(GError **error)
761 | {
762 | return purple_plugin_info_new(
763 | "id", HANGOUTS_PLUGIN_ID,
764 | "name", "Hangouts",
765 | "version", HANGOUTS_PLUGIN_VERSION,
766 | "category", N_("Protocol"),
767 | "summary", N_("Hangouts Protocol Plugins."),
768 | "description", N_("Adds Hangouts protocol support to libpurple."),
769 | "website", "https://bitbucket.org/EionRobb/purple-hangouts/",
770 | "abi-version", PURPLE_ABI_VERSION,
771 | "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
772 | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
773 | NULL
774 | );
775 | }
776 |
777 | PURPLE_PLUGIN_INIT(hangouts, plugin_query,
778 | libpurple3_plugin_load, libpurple3_plugin_unload);
779 |
780 | #else
781 |
782 | // Normally set in core.c in purple3
783 | void _purple_socket_init(void);
784 | void _purple_socket_uninit(void);
785 |
786 |
787 | static gboolean
788 | libpurple2_plugin_load(PurplePlugin *plugin)
789 | {
790 | _purple_socket_init();
791 | purple_http_init();
792 |
793 | return plugin_load(plugin, NULL);
794 | }
795 |
796 | static gboolean
797 | libpurple2_plugin_unload(PurplePlugin *plugin)
798 | {
799 | _purple_socket_uninit();
800 | purple_http_uninit();
801 |
802 | return plugin_unload(plugin, NULL);
803 | }
804 |
805 | static PurplePluginInfo info =
806 | {
807 | PURPLE_PLUGIN_MAGIC,
808 | PURPLE_MAJOR_VERSION,
809 | PURPLE_MINOR_VERSION,
810 | PURPLE_PLUGIN_PROTOCOL, /**< type */
811 | NULL, /**< ui_requirement */
812 | 0, /**< flags */
813 | NULL, /**< dependencies */
814 | PURPLE_PRIORITY_DEFAULT, /**< priority */
815 |
816 | HANGOUTS_PLUGIN_ID, /**< id */
817 | N_("Hangouts"), /**< name */
818 | HANGOUTS_PLUGIN_VERSION, /**< version */
819 |
820 | N_("Hangouts Protocol Plugins."), /**< summary */
821 |
822 | N_("Adds Hangouts protocol support to libpurple."), /**< description */
823 | "Eion Robb ", /**< author */
824 | "https://bitbucket.org/EionRobb/purple-hangouts/", /**< homepage */
825 |
826 | libpurple2_plugin_load, /**< load */
827 | libpurple2_plugin_unload, /**< unload */
828 | NULL, /**< destroy */
829 |
830 | NULL, /**< ui_info */
831 | NULL, /**< extra_info */
832 | NULL, /**< prefs_info */
833 | NULL, /**< actions */
834 |
835 | /* padding */
836 | NULL,
837 | NULL,
838 | NULL,
839 | NULL
840 | };
841 |
842 | static void
843 | init_plugin(PurplePlugin *plugin)
844 | {
845 | PurplePluginInfo *info;
846 | PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
847 |
848 | info = plugin->info;
849 | if (info == NULL) {
850 | plugin->info = info = g_new0(PurplePluginInfo, 1);
851 | }
852 |
853 | prpl_info->options = OPT_PROTO_NO_PASSWORD | OPT_PROTO_IM_IMAGE | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_MAIL_CHECK;
854 | prpl_info->protocol_options = hangouts_add_account_options(prpl_info->protocol_options);
855 |
856 | purple_signal_register(plugin, "hangouts-received-stateupdate",
857 | purple_marshal_VOID__POINTER_POINTER, NULL, 2,
858 | PURPLE_TYPE_CONNECTION,
859 | purple_value_new(PURPLE_TYPE_OBJECT));
860 |
861 | purple_signal_register(plugin, "hangouts-gmail-notification",
862 | purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
863 | PURPLE_TYPE_CONNECTION,
864 | purple_value_new(PURPLE_TYPE_STRING),
865 | purple_value_new(PURPLE_TYPE_OBJECT));
866 |
867 | hangouts_register_events(plugin);
868 |
869 | prpl_info->login = hangouts_login;
870 | prpl_info->close = hangouts_close;
871 | prpl_info->status_types = hangouts_status_types;
872 | prpl_info->list_icon = hangouts_list_icon;
873 | prpl_info->status_text = hangouts_status_text;
874 | prpl_info->tooltip_text = hangouts_tooltip_text;
875 | prpl_info->buddy_free = hangouts_buddy_free;
876 | prpl_info->offline_message = hangouts_offline_message;
877 |
878 | prpl_info->get_info = hangouts_get_info;
879 | prpl_info->set_status = hangouts_set_status;
880 | prpl_info->set_idle = hangouts_set_idle;
881 |
882 | prpl_info->blist_node_menu = hangouts_node_menu;
883 |
884 | prpl_info->send_im = hangouts_send_im;
885 | prpl_info->send_typing = hangouts_send_typing;
886 | prpl_info->chat_send = hangouts_chat_send;
887 | prpl_info->chat_info = hangouts_chat_info;
888 | prpl_info->chat_info_defaults = hangouts_chat_info_defaults;
889 | prpl_info->join_chat = hangouts_join_chat;
890 | prpl_info->get_chat_name = hangouts_get_chat_name;
891 | prpl_info->chat_invite = hangouts_chat_invite;
892 | prpl_info->set_chat_topic = hangouts_chat_set_topic;
893 |
894 | prpl_info->get_media_caps = hangouts_get_media_caps;
895 | prpl_info->initiate_media = hangouts_initiate_media;
896 |
897 | prpl_info->add_deny = hangouts_block_user;
898 | prpl_info->rem_deny = hangouts_unblock_user;
899 |
900 | prpl_info->roomlist_get_list = hangouts_roomlist_get_list;
901 |
902 | info->extra_info = prpl_info;
903 | #if PURPLE_MINOR_VERSION >= 5
904 | prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
905 | #endif
906 | #if PURPLE_MINOR_VERSION >= 8
907 | //prpl_info->add_buddy_with_invite = skypeweb_add_buddy_with_invite;
908 | #endif
909 |
910 | info->actions = hangouts_actions;
911 | }
912 |
913 | PURPLE_INIT_PLUGIN(hangouts, init_plugin, info);
914 |
915 | #endif
916 |
--------------------------------------------------------------------------------
/libhangouts.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Hangouts Plugin for libpurple/Pidgin
3 | * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 |
21 |
22 | #ifndef _LIBHANGOUTS_H_
23 | #define _LIBHANGOUTS_H_
24 |
25 | #ifndef PURPLE_PLUGINS
26 | # define PURPLE_PLUGINS
27 | #endif
28 |
29 | #define PROTOBUF_C_UNPACK_ERROR(...) purple_debug_error("hangouts-protobuf", __VA_ARGS__)
30 |
31 | #include "purplecompat.h"
32 |
33 | #include "account.h"
34 | #include "connection.h"
35 | #include "http.h"
36 |
37 | #include "hangouts.pb-c.h"
38 |
39 | #define HANGOUTS_PLUGIN_ID "prpl-hangouts"
40 | #define HANGOUTS_PLUGIN_VERSION "0.1"
41 |
42 | #define HANGOUTS_BUFFER_DEFAULT_SIZE 4096
43 |
44 | #ifndef N_
45 | # define N_(a) (a)
46 | #endif
47 | #ifndef _
48 | # define _(a) (a)
49 | #endif
50 |
51 | #define HANGOUTS_API_OAUTH2_TOKEN_URL "https://www.googleapis.com/oauth2/v3/token"
52 |
53 | // #define GOOGLE_CLIENT_ID "1055179169992-mvb9smig5gflo8bq6m5pao05jqmov76h.apps.googleusercontent.com"
54 | // #define GOOGLE_CLIENT_SECRET "Hj5Cv38ZM__uO1bTQxOtWwkT"
55 |
56 | #define GOOGLE_CLIENT_ID "936475272427.apps.googleusercontent.com"
57 | #define GOOGLE_CLIENT_SECRET "KWsJlkaMn1jGLxQpWxMnOox-"
58 | #define GOOGLE_GPLUS_KEY "AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM"
59 |
60 | #define MINIFIED_OAUTH_URL "https://goo.gl/eJHvDX"
61 | #define HANGOUTS_API_OAUTH2_REDIRECT_URI "urn:ietf:wg:oauth:2.0:oob"
62 | // #define HANGOUTS_API_OAUTH2_AUTHORIZATION_CODE_URL MINIFIED_OAUTH_URL
63 |
64 | #define HANGOUTS_API_OAUTH2_AUTHORIZATION_CODE_URL "https://accounts.google.com/o/oauth2/auth?client_id=" GOOGLE_CLIENT_ID "&scope=https://www.google.com/accounts/OAuthLogin&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&device_name=purple-hangouts"
65 |
66 | #define HANGOUTS_IMAGE_UPLOAD_URL "https://docs.google.com/upload/photos/resumable?authuser=0"
67 |
68 | #define HANGOUTS_ACTIVE_CLIENT_TIMEOUT 120
69 |
70 | #define HANGOUTS_MAGIC_HALF_EIGHT_SLASH_ME_TYPE 4
71 |
72 | typedef struct {
73 | PurpleAccount *account;
74 | PurpleConnection *pc;
75 |
76 | PurpleHttpCookieJar *cookie_jar;
77 | gchar *refresh_token;
78 | gchar *access_token;
79 | gchar *gsessionid_param;
80 | gchar *sid_param;
81 | gchar *client_id;
82 | gchar *self_gaia_id;
83 | gchar *self_phone;
84 | ActiveClientState active_client_state;
85 | gint64 last_event_timestamp;
86 | PurpleConversation *last_conversation_focused;
87 | guint poll_buddy_status_timeout;
88 |
89 | GByteArray *channel_buffer;
90 | guint channel_watchdog;
91 | PurpleHttpConnection *channel_connection;
92 | PurpleHttpKeepalivePool *channel_keepalive_pool;
93 | PurpleHttpKeepalivePool *icons_keepalive_pool;
94 | PurpleHttpKeepalivePool *client6_keepalive_pool;
95 | gint idle_time;
96 | gint active_client_timeout;
97 | gint last_data_received; // A timestamp of when we last received data from the stream
98 |
99 | GHashTable *one_to_ones; // A store of known conv_id's->gaia_id's
100 | GHashTable *one_to_ones_rev; // A store of known gaia_id's->conv_id's
101 | GHashTable *group_chats; // A store of known conv_id's
102 | GHashTable *sent_message_ids;// A store of message id's that we generated from this instance
103 | GHashTable *google_voice_conversations; // A store of known SMS conv_id's
104 | } HangoutsAccount;
105 |
106 |
107 | typedef enum
108 | {
109 | HANGOUTS_DEVICE_TYPE_UNKNOWN = 0x0000,
110 | HANGOUTS_DEVICE_TYPE_MOBILE = 0x0001,
111 | HANGOUTS_DEVICE_TYPE_DESKTOP = 0x0002,
112 | HANGOUTS_DEVICE_TYPE_TABLET = 0x0004
113 | } HangoutsDeviceTypeFlags;
114 |
115 | typedef struct {
116 | PurpleBuddy *buddy;
117 | gboolean in_call;
118 | gint64 last_seen;
119 | HangoutsDeviceTypeFlags device_type;
120 | } HangoutsBuddy;
121 |
122 |
123 | gboolean hangouts_is_valid_id(const gchar *id);
124 |
125 | #endif /*_LIBHANGOUTS_H_*/
--------------------------------------------------------------------------------
/libprotobuf-c-1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EionRobb/purple-hangouts/55b9f01d040b240b794700f44d9c21a6cb51251e/libprotobuf-c-1.dll
--------------------------------------------------------------------------------
/purple-hangouts.nsi:
--------------------------------------------------------------------------------
1 | ; Script based on Off-the-Record Messaging NSI file
2 |
3 |
4 | SetCompress off
5 |
6 | ; todo: SetBrandingImage
7 | ; HM NIS Edit Wizard helper defines
8 | !ifndef PRODUCT_NAME
9 | !define PRODUCT_NAME "purple-hangouts"
10 | !endif
11 | !ifndef PRODUCT_VERSION
12 | !define PRODUCT_VERSION "v0.1"
13 | !endif
14 | !define PRODUCT_PUBLISHER "Eion Robb"
15 | !define PRODUCT_WEB_SITE "http://eion.robbmob.com/"
16 | !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
17 | !define PRODUCT_UNINST_ROOT_KEY "HKLM"
18 | !ifndef JSON_GLIB_DLL
19 | !define JSON_GLIB_DLL "libjson-glib-1.0.dll"
20 | !endif
21 | !ifndef PROTOBUF_C_DLL
22 | !define PROTOBUF_C_DLL "libprotobuf-c-1.dll"
23 | !endif
24 | !ifndef PIDGIN_VARIANT
25 | !define PIDGIN_VARIANT "Pidgin"
26 | !endif
27 | !ifndef INSTALLER_NAME
28 | !define INSTALLER_NAME ${PRODUCT_NAME}
29 | !endif
30 |
31 | ; MUI 1.67 compatible ------
32 | !include "MUI.nsh"
33 |
34 | ; MUI Settings
35 | !define MUI_ABORTWARNING
36 | !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
37 | !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
38 |
39 | ; Welcome page
40 | !insertmacro MUI_PAGE_WELCOME
41 | ; License page
42 | !insertmacro MUI_PAGE_LICENSE "gpl3.txt"
43 | ; Directory page
44 | ;!define MUI_PAGE_CUSTOMFUNCTION_PRE dir_pre
45 | ;!insertmacro MUI_PAGE_DIRECTORY
46 | ; Instfiles page
47 | !insertmacro MUI_PAGE_INSTFILES
48 | ; Finish page
49 | !define MUI_FINISHPAGE_RUN
50 | !define MUI_FINISHPAGE_RUN_TEXT "Run ${PIDGIN_VARIANT}"
51 | !define MUI_FINISHPAGE_RUN_FUNCTION "RunPidgin"
52 | !insertmacro MUI_PAGE_FINISH
53 |
54 | ; Uninstaller pages
55 | ;!insertmacro MUI_UNPAGE_INSTFILES
56 |
57 | ; Language files
58 | !insertmacro MUI_LANGUAGE "English"
59 |
60 | ; MUI end ------
61 |
62 | Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
63 | OutFile "${INSTALLER_NAME}.exe"
64 | InstallDir "$PROGRAMFILES\${PIDGIN_VARIANT}"
65 |
66 | Var "PidginDir"
67 |
68 | ShowInstDetails show
69 | ShowUnInstDetails show
70 |
71 | Section "MainSection" SEC01
72 |
73 | ;Check for pidgin installation
74 | Call GetPidginInstPath
75 |
76 | SetOverwrite ifnewer
77 |
78 | SetOutPath "$PidginDir"
79 | File "${JSON_GLIB_DLL}"
80 | File "${PROTOBUF_C_DLL}"
81 |
82 | SetOverwrite try
83 |
84 | SetOutPath "$PidginDir\pixmaps\pidgin"
85 | File "/oname=protocols\16\hangouts.png" "hangouts16.png"
86 | File "/oname=protocols\22\hangouts.png" "hangouts22.png"
87 | File "/oname=protocols\48\hangouts.png" "hangouts48.png"
88 |
89 | SetOverwrite try
90 |
91 | SetOutPath "$PidginDir\ca-certs"
92 | File "Google Internet Authority G2.pem"
93 | File "Google Internet Authority G3.pem"
94 | File "Google Trust Services - GlobalSign Root CA-R2.pem"
95 | File "GTS CA 1O1.pem"
96 |
97 | SetOverwrite try
98 |
99 | copy:
100 | ClearErrors
101 | Delete "$PidginDir\plugins\libhangouts.dll"
102 | IfErrors dllbusy
103 | SetOutPath "$PidginDir\plugins"
104 | File "libhangouts.dll"
105 | Goto after_copy
106 | dllbusy:
107 | Delete "$PidginDir\plugins\libhangouts.dllold"
108 | Rename "$PidginDir\plugins\libhangouts.dll" "$PidginDir\plugins\libhangouts.dllold"
109 | MessageBox MB_OK "Old version of plugin detected. You will need to restart ${PIDGIN_VARIANT} to complete installation"
110 | Goto copy
111 | after_copy:
112 |
113 | SectionEnd
114 |
115 | Function GetPidginInstPath
116 | Push $0
117 | ReadRegStr $0 HKLM "Software\${PIDGIN_VARIANT}" ""
118 | IfFileExists "$0\pidgin.exe" cont
119 | ReadRegStr $0 HKCU "Software\${PIDGIN_VARIANT}" ""
120 | IfFileExists "$0\pidgin.exe" cont
121 | MessageBox MB_OK|MB_ICONINFORMATION "Failed to find ${PIDGIN_VARIANT} installation."
122 | Abort "Failed to find ${PIDGIN_VARIANT} installation. Please install ${PIDGIN_VARIANT} first."
123 | cont:
124 | StrCpy $PidginDir $0
125 | FunctionEnd
126 |
127 | Function RunPidgin
128 | ExecShell "" "$PidginDir\pidgin.exe"
129 | FunctionEnd
130 |
131 |
--------------------------------------------------------------------------------
/purple-hangouts.spec:
--------------------------------------------------------------------------------
1 | %global plugin_name hangouts
2 |
3 | %global commit0 d6eb7fe69b1b3fd909b41c6b33566fc498c834b2
4 | %global shortcommit0 %(c=%{commit0}; echo ${c:0:7})
5 | %global archcommit0 %(c=%{commit0}; echo ${c:0:12})
6 | %global date 20160714
7 |
8 | Name: purple-%{plugin_name}
9 | Version: 0
10 | Release: 34.%{date}hg%{shortcommit0}%{?dist}
11 | Epoch: 1
12 | Summary: Hangouts plugin for libpurple
13 |
14 | License: GPLv3+
15 | URL: https://bitbucket.org/EionRobb/purple-hangouts/
16 | Source0: https://bitbucket.org/EionRobb/purple-hangouts/get/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz
17 |
18 | BuildRequires: pkgconfig(libprotobuf-c)
19 | BuildRequires: pkgconfig(json-glib-1.0)
20 | BuildRequires: pkgconfig(glib-2.0)
21 | BuildRequires: pkgconfig(purple)
22 | BuildRequires: pkgconfig(zlib)
23 | BuildRequires: gcc
24 |
25 | %package -n pidgin-%{plugin_name}
26 | Summary: Adds pixmaps, icons and smileys for Hangouts protocol
27 | BuildArch: noarch
28 | Requires: %{name} = %{?epoch:%{epoch}:}%{version}-%{release}
29 | Requires: pidgin
30 |
31 | %description
32 | Adds support for Hangouts to Pidgin, Adium, Finch and other libpurple
33 | based messengers.
34 |
35 | %description -n pidgin-%{plugin_name}
36 | Adds pixmaps, icons and smileys for Hangouts protocol implemented by
37 | hangouts-purple.
38 |
39 | %prep
40 | %autosetup -n EionRobb-purple-%{plugin_name}-%{archcommit0}
41 |
42 | # fix W: wrong-file-end-of-line-encoding
43 | sed -i -e "s,\r,," README.md
44 |
45 | %build
46 | export CFLAGS="%{optflags}"
47 | export LDFLAGS="%{__global_ldflags}"
48 | %make_build
49 |
50 | %install
51 | # Executing base install from makefile...
52 | %make_install
53 |
54 | # Setting correct chmod...
55 | chmod 755 %{buildroot}%{_libdir}/purple-2/lib%{plugin_name}.so
56 |
57 | %files
58 | %{_libdir}/purple-2/lib%{plugin_name}.so
59 | %license gpl3.txt
60 | %doc README.md
61 |
62 | %files -n pidgin-%{plugin_name}
63 | %{_datadir}/pixmaps/pidgin/protocols/*/%{plugin_name}.png
64 |
65 | %changelog
66 | * Tue Jul 19 2016 Vitaly Zaitsev - 1:0-34.20160714hgd6eb7fe
67 | - Updated to latest snapshot.
68 |
69 | * Tue Jul 12 2016 Vitaly Zaitsev - 1:0-33.20160712hg2c60a5e
70 | - Updated to latest snapshot.
71 |
72 | * Sun Jul 10 2016 Vitaly Zaitsev - 1:0-32.20160710hg5378549
73 | - Fixed Requires for pidgin subpackage. Replaced Perl to sed in prep section.
74 |
75 | * Sun Jul 10 2016 Vitaly Zaitsev - 1:0-31.20160710hg5378549
76 | - Fixed version. Updated SPEC file.
77 |
78 | * Sun Jul 10 2016 Vitaly Zaitsev - 0-30.20160710hg5378549
79 | - Updated to latest snapshot. Updated install section due upstream changes.
80 |
81 | * Tue Jun 21 2016 Vitaly Zaitsev - 0-29.20160621hg38f0731
82 | - Added missing LDFLAGS to build.
83 |
84 | * Tue Jun 21 2016 Vitaly Zaitsev - 0-28.20160621hg38f0731
85 | - Updated to latest Git snapshot.
86 |
87 | * Fri Jun 17 2016 Vitaly Zaitsev - 0-27.20160615hg2059439
88 | - Updated to latest snapshot. Removed patch.
89 |
90 | * Sun Jun 12 2016 Vitaly Zaitsev - 0-26.20160609hg90c515d
91 | - Updated to latest snapshot. Removed empty configure script.
92 |
93 | * Mon Jun 06 2016 Vitaly Zaitsev - 0-25.20160601hgf362605
94 | - Updated to latest snapshot.
95 |
96 | * Fri May 27 2016 Vitaly Zaitsev - 0-24.20160527hge643cc7
97 | - Updated to latest snapshot. Updated patch.
98 |
99 | * Thu May 26 2016 Vitaly Zaitsev - 0-23.20160526hg8cafb7a
100 | - Updated to latest snapshot. Updated patch.
101 |
102 | * Fri May 13 2016 Vitaly Zaitsev - 0-22.20160512hg7a23ed5
103 | - Updated to latest snapshot.
104 |
105 | * Tue May 10 2016 Vitaly Zaitsev - 0-21.20160510hg78a9c80
106 | - Updated to latest snapshot. Updated patch.
107 |
108 | * Sat May 07 2016 Vitaly Zaitsev - 0-20.20160507hg2095ac0
109 | - Updated to latest snapshot.
110 |
111 | * Thu May 05 2016 Vitaly Zaitsev - 0-19.20160505hg4cf1d50
112 | - Updated to latest snapshot.
113 |
114 | * Wed May 04 2016 Vitaly Zaitsev - 0-18.20160504hge8c30b6
115 | - Updated to latest snapshot.
116 |
117 | * Tue May 03 2016 Vitaly Zaitsev - 0-17.20160502hg2631ad2
118 | - Updated to latest snapshot. Added patch for Fedora.
119 |
120 | * Tue Apr 26 2016 Vitaly Zaitsev - 0-16.20160426hgac1741f
121 | - Updated to latest snapshot.
122 |
123 | * Thu Apr 21 2016 Vitaly Zaitsev - 0-15.20160421hg6f76943
124 | - Updated to latest snapshot.
125 |
126 | * Sun Apr 17 2016 Vitaly Zaitsev - 0-14.20160417hg635f50c
127 | - Updated to latest snapshot.
128 |
129 | * Fri Apr 15 2016 Vitaly Zaitsev - 0-13.20160413hg92bfbf1
130 | - Updated to latest snapshot.
131 |
132 | * Sun Apr 10 2016 Vitaly Zaitsev - 0-12.20160410hga5b0e60
133 | - Updated to latest snapshot.
134 |
135 | * Sat Apr 09 2016 Vitaly Zaitsev - 0-11.20160409hg7442ecd
136 | - Updated to latest snapshot.
137 |
138 | * Wed Apr 06 2016 Vitaly Zaitsev - 0-10.20160406hgab7c25a
139 | - Added license section. Updated to latest snapshot.
140 |
141 | * Tue Apr 05 2016 Vitaly Zaitsev - 0-9.20160405hgbd3b2be
142 | - Fixed SPEC. Updated to latest snapshot.
143 |
144 | * Sun Apr 03 2016 Vitaly Zaitsev - 0-8.20160403hg0f3cbbd
145 | - Updated to latest snapshot.
146 |
147 | * Fri Apr 01 2016 Vitaly Zaitsev - 0-7.20160401hg8b37dcc
148 | - Updated to latest snapshot.
149 |
150 | * Tue Mar 22 2016 Vitaly Zaitsev - 0-6.20160322hg735b7f8
151 | - Updated to latest snapshot.
152 |
153 | * Thu Mar 17 2016 Vitaly Zaitsev - 0-5.20160316hgfde2d8a
154 | - Updated to latest snapshot.
155 |
156 | * Tue Mar 15 2016 Vitaly Zaitsev - 0-4.20160315hg694bd41
157 | - Updated to latest snapshot.
158 |
159 | * Tue Mar 08 2016 Vitaly Zaitsev - 0-3.20160306hgb7ba81f
160 | - Updated to latest snapshot.
161 |
162 | * Fri Mar 04 2016 Vitaly Zaitsev - 0-2.20160303hg4789156
163 | - Updated to latest snapshot.
164 |
165 | * Mon Feb 29 2016 Vitaly Zaitsev - 0-1.20160227hga2c9af3
166 | - First SPEC version.
167 |
--------------------------------------------------------------------------------
/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_has_left purple_conv_chat_has_left
93 | #define purple_chat_conversation_remove_user purple_conv_chat_remove_user
94 |
95 | #define PurpleMessage PurpleConvMessage
96 | #define purple_message_set_time(msg, time) ((msg)->when = (time))
97 | #define purple_conversation_write_message(conv, msg) \
98 | purple_conversation_write(conv, msg->who, msg->what, msg->flags, msg->when); \
99 | purple_message_destroy(msg)
100 | static inline PurpleMessage *
101 | purple_message_new_outgoing(const gchar *who, const gchar *contents, PurpleMessageFlags flags)
102 | {
103 | PurpleMessage *message = g_new0(PurpleMessage, 1);
104 |
105 | message->who = g_strdup(who);
106 | message->what = g_strdup(contents);
107 | message->flags = flags;
108 | message->when = time(NULL);
109 |
110 | return message;
111 | }
112 | static inline void
113 | purple_message_destroy(PurpleMessage *message)
114 | {
115 | g_free(message->who);
116 | g_free(message->what);
117 | g_free(message);
118 | }
119 | #if !PURPLE_VERSION_CHECK(2, 12, 0)
120 | # define PURPLE_MESSAGE_REMOTE_SEND 0x10000
121 | #endif
122 |
123 | #define PurpleProtocolChatEntry struct proto_chat_entry
124 | #define PurpleChatUserFlags PurpleConvChatBuddyFlags
125 | #define PURPLE_CHAT_USER_NONE PURPLE_CBFLAGS_NONE
126 | #define PURPLE_CHAT_USER_OP PURPLE_CBFLAGS_OP
127 | #define PURPLE_CHAT_USER_FOUNDER PURPLE_CBFLAGS_FOUNDER
128 | #define PURPLE_CHAT_USER_TYPING PURPLE_CBFLAGS_TYPING
129 | #define PURPLE_CHAT_USER_AWAY PURPLE_CBFLAGS_AWAY
130 | #define PURPLE_CHAT_USER_HALFOP PURPLE_CBFLAGS_HALFOP
131 | #define PURPLE_CHAT_USER_VOICE PURPLE_CBFLAGS_VOICE
132 | #define PURPLE_CHAT_USER_TYPING PURPLE_CBFLAGS_TYPING
133 | #define PurpleChatUser PurpleConvChatBuddy
134 |
135 | static inline PurpleChatUser *
136 | purple_chat_conversation_find_user(PurpleChatConversation *chat, const char *name)
137 | {
138 | PurpleChatUser *cb = purple_conv_chat_cb_find(chat, name);
139 |
140 | if (cb != NULL) {
141 | g_dataset_set_data(cb, "chat", chat);
142 | }
143 |
144 | return cb;
145 | }
146 | #define purple_chat_user_get_flags(cb) purple_conv_chat_user_get_flags(g_dataset_get_data((cb), "chat"), (cb)->name)
147 | #define purple_chat_user_set_flags(cb, f) purple_conv_chat_user_set_flags(g_dataset_get_data((cb), "chat"), (cb)->name, (f))
148 | #define purple_chat_user_set_alias(cb, a) ((cb)->alias = (a))
149 |
150 | #define PurpleIMTypingState PurpleTypingState
151 | #define PURPLE_IM_NOT_TYPING PURPLE_NOT_TYPING
152 | #define PURPLE_IM_TYPING PURPLE_TYPING
153 | #define PURPLE_IM_TYPED PURPLE_TYPED
154 |
155 | #define purple_media_set_protocol_data purple_media_set_prpl_data
156 | #if PURPLE_VERSION_CHECK(2, 10, 12)
157 | // Handle ABI breakage
158 | # define PURPLE_MEDIA_NETWORK_PROTOCOL_TCP PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE
159 | #endif
160 |
161 | #undef purple_notify_error
162 | #define purple_notify_error(handle, title, primary, secondary, cpar) \
163 | purple_notify_message((handle), PURPLE_NOTIFY_MSG_ERROR, (title), \
164 | (primary), (secondary), NULL, NULL)
165 | #undef purple_notify_warning
166 | #define purple_notify_warning(handle, title, primary, secondary, cpar) \
167 | purple_notify_message((handle), PURPLE_NOTIFY_MSG_WARNING, (title), \
168 | (primary), (secondary), NULL, NULL)
169 | #define purple_notify_user_info_add_pair_html purple_notify_user_info_add_pair
170 |
171 | #define PurpleProtocolAction PurplePluginAction
172 | #define purple_protocol_action_get_connection(action) ((PurpleConnection *) (action)->context)
173 | #define purple_protocol_action_new purple_plugin_action_new
174 | #define purple_protocol_get_id purple_plugin_get_id
175 |
176 | #define purple_protocol_got_user_status purple_prpl_got_user_status
177 |
178 | #define purple_account_privacy_deny_add purple_privacy_deny_add
179 | #define purple_account_privacy_deny_remove purple_privacy_deny_remove
180 | #define purple_account_set_password(account, password, dummy1, dummy2) \
181 | purple_account_set_password(account, password);
182 | #define purple_account_set_private_alias purple_account_set_alias
183 | #define purple_account_get_private_alias purple_account_get_alias
184 |
185 | #define purple_proxy_info_get_proxy_type purple_proxy_info_get_type
186 |
187 | #define purple_serv_got_im serv_got_im
188 | #define purple_serv_got_typing serv_got_typing
189 | #define purple_serv_got_alias serv_got_alias
190 | #define purple_serv_got_chat_in serv_got_chat_in
191 | #define purple_serv_got_chat_left serv_got_chat_left
192 | #define purple_serv_got_joined_chat(pc, id, name) PURPLE_CONV_CHAT(serv_got_joined_chat(pc, id, name))
193 |
194 | #define purple_status_get_status_type purple_status_get_type
195 |
196 | #define g_timeout_add_seconds purple_timeout_add_seconds
197 | #define g_timeout_add purple_timeout_add
198 | #define g_source_remove purple_timeout_remove
199 |
200 | #define PurpleXmlNode xmlnode
201 | #define purple_xmlnode_new xmlnode_new
202 | #define purple_xmlnode_new_child xmlnode_new_child
203 | #define purple_xmlnode_from_str xmlnode_from_str
204 | #define purple_xmlnode_to_str xmlnode_to_str
205 | #define purple_xmlnode_get_child xmlnode_get_child
206 | #define purple_xmlnode_get_next_twin xmlnode_get_next_twin
207 | #define purple_xmlnode_get_data xmlnode_get_data
208 | #define purple_xmlnode_get_attrib xmlnode_get_attrib
209 | #define purple_xmlnode_set_attrib xmlnode_set_attrib
210 | #define purple_xmlnode_insert_data xmlnode_insert_data
211 | #define purple_xmlnode_free xmlnode_free
212 |
213 |
214 | #endif
215 |
216 | #endif /*_PURPLECOMPAT_H_*/
217 |
--------------------------------------------------------------------------------