├── .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 | ![logo](https://user-images.githubusercontent.com/1063865/87138135-18131780-c2f2-11ea-9579-3dfbb7d858fb.png) 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 | --------------------------------------------------------------------------------