├── .gitignore ├── LICENSE ├── README.md ├── config_example.py ├── googleplay_api ├── README.md ├── __init__.py ├── googleplay.proto ├── googleplay.py └── googleplay_pb2.py ├── googleplayupdater ├── __init__.py ├── __main__.py ├── asynchronousfilereader │ └── __init__.py ├── common.py └── gp_update.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore editor-files 2 | *.bak 3 | *.swp 4 | *.swo 5 | *~ 6 | 7 | #ignore libreoffice lock files 8 | .~lock.* 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *,cover 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | 663 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # googleplayupdater 2 | Bulk-updater for a folder with apk-files from googleplay 3 | 4 | ## Configure 5 | Copy the file `config_example.py` or create a new file named `config.py` and fill out your google-account information as well as your `ANDROID_ID`. Your android id can be obtained as descibed in the [googleplay-api](https://github.com/NeroBurner/googleplay-api#requirements) project by using the Gtalk Manager on your phone. Or use the java program [android-checkin](https://github.com/nviennot/android-checkin). 6 | 7 | ## Synopsis 8 | ``` 9 | usage: googleplayupdater [-h] [-c [CONFIG_FILE]] [-v] apk_folder_path 10 | 11 | Fetch updates for local apks from GooglePlayStore 12 | 13 | positional arguments: 14 | apk_folder_path absolute or relative path to folder containing the 15 | apks to update 16 | 17 | optional arguments: 18 | -h, --help show this help message and exit 19 | -c [CONFIG_FILE], --config_file [CONFIG_FILE] 20 | -v, --verbose be more verbose 21 | ``` 22 | 23 | ## Dependencies 24 | * [Python 2.7+ or Python 3.4+](http://www.python.org) 25 | * [Protocol Buffers](http://code.google.com/p/protobuf/) (for googleplay-api) 26 | * [android-sdk-build-tools](https://developer.android.com/tools/revisions/build-tools.html) 27 | (tested with version 23, for archlinux-users via [AUR](https://aur4.archlinux.org/packages/android-sdk-build-tools/)) 28 | 29 | ## Installation 30 | To install download/clone the repository, change into the downloaded directory and execute: 31 | ``` 32 | python setup.py install 33 | ``` 34 | 35 | ### Python 3 36 | googleplay-api depends on google's protobuf. Only versions greater than 3.0 have support for Python 3. Unfortunately these versions are still in alpha and must be installed manually. 37 | For Arch-linux the package [python-protobuf](https://aur4.archlinux.org/packages/python-protobuf/) is available in the AUR. Otherwise [protobuf-3.0.0a3](https://pypi.python.org/pypi/protobuf/3.0.0a3) can be installed directly via pip: 38 | ``` 39 | pip install https://pypi.python.org/packages/source/p/protobuf/protobuf-3.0.0a3.tar.gz#md5=6674fa7452ebf066b767075db96a7ee0 40 | ``` 41 | 42 | 43 | Otherwise an exception like the following will occur: 44 | ``` 45 | File "[...]/googleplayupdater/googleplayupdater/gp_update.py", line 33, in 46 | from googleplay_api.googleplay import GooglePlayAPI # GooglePlayAPI 47 | File "[...]/googleplayupdater/googleplay_api/googleplay.py", line 19, in 48 | from google.protobuf import text_format 49 | File "[...]/googleplayupdater/venv3/lib/python3.4/site-packages/google/protobuf/text_format.py", line 572 50 | except ValueError, e: 51 | ``` 52 | 53 | ## Included libraries 54 | 55 | ### googleplay-api 56 | Cloned from https://github.com/NeroBurner/googleplay-api which is a fork from https://github.com/timogilvie/googleplay-api 57 | 58 | ### AsynchronousFilereader 59 | Cloned from https://github.com/soxofaan/asynchronousfilereader 60 | 61 | Simple thread based asynchronous file reader for Python. 62 | 63 | ## Inspiration 64 | 65 | Inspired by [GooglePlayDownloader](http://codingteam.net/project/googleplaydownloader) and its 'Search updates for local APK(s)'-button 66 | 67 | ## Licence 68 | This project is released under the AGPLv3+ licence. 69 | 70 | 71 | -------------------------------------------------------------------------------- /config_example.py: -------------------------------------------------------------------------------- 1 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 2 | 3 | from __future__ import unicode_literals 4 | 5 | LANG = "en_US" # can be en_US, fr_FR, ... 6 | ANDROID_ID = None # "38c6523ac43ef9e1" 7 | GOOGLE_LOGIN = None # 'someone@gmail.com' 8 | GOOGLE_PASSWORD = None # 'yourpassword' 9 | AUTH_TOKEN = None # "yyyyyyyyy" 10 | 11 | # force the user to edit this file 12 | if ANDROID_ID == NONE 13 | or all([each == None for each in [GOOGLE_LOGIN, GOOGLE_PASSWORD, AUTH_TOKEN]]): 14 | raise Exception("config.py not updated") 15 | 16 | -------------------------------------------------------------------------------- /googleplay_api/README.md: -------------------------------------------------------------------------------- 1 | # Google Play Unofficial Python API 2 | 3 | An unofficial Python API that let you search, browse and download Android apps from Google Play (formerly Android Market). 4 | 5 | This library is inspired by those projects, working with the old version of the API: 6 | 7 | * [Android Market Python API](https://github.com/liato/android-market-api-py) 8 | * [Android Market Java API](http://code.google.com/p/android-market-api/) 9 | 10 | ## Disclaimer 11 | **This is not an official API. I am not afiliated with Google in any way, and am not responsible of any damage that could be done with it. Use it at your own risk.** 12 | 13 | ## Dependencies 14 | * [Python 2.7+ or Python 3.4+](http://www.python.org) 15 | * [Protocol Buffers](http://code.google.com/p/protobuf/) 16 | 17 | ## Requirements 18 | You must edit `config.py` before using the provided scripts (`search.py`, `download.py`, `apishell.py`, etc.). First, you need to provide your phone's `androidID`: 19 | 20 | ANDROID_ID = None # "xxxxxxxxxxxxxxxx" 21 | 22 | To get your `androidID`, use `*#*#8255#*#*` on your phone to start *Gtalk Monitor*. The hex string listed after `aid` is your `androidID`. 23 | 24 | In order to authenticate to Google Play, you also need to provide either your Google login and password, or a valid subAuthToken. 25 | 26 | ## Usage 27 | 28 | ### Searching 29 | 30 | $ python search.py 31 | Usage: search.py request [nb_results] [offset] 32 | Search for an app. 33 | If request contains a space, don't forget to surround it with "" 34 | 35 | $ python search.py earth 36 | Title;Package name;Creator;Super Dev;Price;Offer Type;Version Code;Size;Rating;Num Downloads 37 | Google Earth;com.google.earth;Google Inc.;1;Gratuit;1;53;8.6MB;4.46;10 000 000+ 38 | Terre HD Free Edition;ru.gonorovsky.kv.livewall.earthhd;Stanislav Gonorovsky;0;Gratuit;1;33;4.7MB;4.47;1 000 000+ 39 | Earth Live Wallpaper;com.seb.SLWP;unixseb;0;Gratuit;1;60;687.4KB;4.06;5 000 000+ 40 | Super Earth Wallpaper Free;com.mx.spacelwpfree;Mariux;0;Gratuit;1;2;1.8MB;4.41;100 000+ 41 | Earth And Legend;com.dvidearts.earthandlegend;DVide Arts Incorporated;0;5,99 €;1;6;6.8MB;4.82;50 000+ 42 | [...] 43 | 44 | Depending on the number of results you ask, you might get an error. My tests show that 100 search results are the maximum, but it may vary. 45 | 46 | By default, all scripts have CSV output. You can use Linux's `column` to prettify the output: 47 | 48 | $ alias pp="column -s ';' -t" 49 | $ python search.py earth | pp 50 | Title Package name Creator Super Dev Price Offer Type Version Code Size Rating Num Downloads 51 | Google Earth com.google.earth Google Inc. 1 Gratuit 1 53 8.6MB 4.46 10 000 000+ 52 | Terre HD Free Edition ru.gonorovsky.kv.livewall.earthhd Stanislav Gonorovsky 0 Gratuit 1 33 4.7MB 4.47 1 000 000+ 53 | Earth Live Wallpaper com.seb.SLWP unixseb 0 Gratuit 1 60 687.4KB 4.06 5 000 000+ 54 | Super Earth Wallpaper Free com.mx.spacelwpfree Mariux 0 Gratuit 1 2 1.8MB 4.41 100 000+ 55 | Earth And Legend com.dvidearts.earthandlegend DVide Arts Incorporated 0 5,99 € 1 6 6.8MB 4.82 50 000+ 56 | Earth 3D com.jmsys.earth3d Dokon Jang 0 Gratuit 1 12 3.4MB 4.05 500 000+ 57 | [...] 58 | 59 | ### Browse categories 60 | 61 | You can list all app categories this way: 62 | 63 | ID Name 64 | GAME Games 65 | BOOKS_AND_REFERENCE Books & Reference 66 | BUSINESS Business 67 | COMICS Comics 68 | COMMUNICATION Communication 69 | EDUCATION Education 70 | ENTERTAINMENT Entertainment 71 | FINANCE Finance 72 | [...] 73 | 74 | ### List subcategories and apps 75 | 76 | All categories have subcategories. You can list them with: 77 | 78 | $ python list.py 79 | Usage: list.py category [subcategory] [nb_results] [offset] 80 | List subcategories and apps within them. 81 | category: To obtain a list of supported catagories, use categories.py 82 | subcategory: You can get a list of all subcategories available, by supplying a valid category 83 | 84 | $ python list.py WEATHER | pp 85 | Subcategory ID Name 86 | apps_topselling_paid Top Selling 87 | apps_topselling_free Top Apps 88 | apps_topgrossing Top Grossing 89 | apps_topselling_new_paid Top Selling New 90 | apps_topselling_new_free Top New Apps 91 | apps_movers_shakers Trending 92 | 93 | And then list apps within them: 94 | 95 | $ python list.py WEATHER apps_topselling_free | pp 96 | Title Package name Creator Super Dev Price Offer Type Version Code Size Rating Num Downloads 97 | wetter.com com.wetter.androidclient wetter.com GmbH 0 Free 1 1514242001 10.3MB 4.07 10,000,000+ 98 | Weather Austria XL PRO com.exovoid.weather.app.at Exovoid Sàrl 0 Free 1 29 12.4MB 4.39 50,000+ 99 | MORECAST- Free Premium Weather com.morecast.weather UBIMET 0 Free 1 206 12.7MB 4.42 500,000+ 100 | [...] 101 | ### Viewing permissions 102 | 103 | You can use `permissions.py` to see what permissions are required by an app without downloading it: 104 | 105 | $ python search.py gmail 1 | pp 106 | Title Package name Creator Super Dev Price Offer Type Version Code Size Rating Num Downloads 107 | Gmail com.google.android.gm Google Inc. 1 Free 1 55008625 12.1MB 4.30 1,000,000,000+ 108 | 109 | $ python permissions.py com.google.android.gm 110 | android.permission.ACCESS_NETWORK_STATE 111 | android.permission.GET_ACCOUNTS 112 | android.permission.MANAGE_ACCOUNTS 113 | android.permission.INTERNET 114 | android.permission.READ_CONTACTS 115 | android.permission.WRITE_CONTACTS 116 | android.permission.READ_SYNC_SETTINGS 117 | android.permission.READ_SYNC_STATS 118 | android.permission.RECEIVE_BOOT_COMPLETED 119 | [...] 120 | 121 | You can specify multiple apps, using only one request. 122 | 123 | ### Downloading apps 124 | 125 | Downloading an app is really easy, just provide its package name. I only tested with free apps, but I guess it should work as well with non-free as soon as you have enough money on your Google account. 126 | 127 | $ python download.py com.google.android.gm 128 | Downloading 2.7MB... Done 129 | 130 | $ file com.google.android.gm.apk 131 | com.google.android.gm.apk: Zip archive data, at least v2.0 to extract 132 | 133 | ### Interactive shell 134 | An interactive shell can be started using the `apishell.py` script. It initializes the `api` object and logs you in. 135 | 136 | $ python apishell.py 137 | 138 | Google Play Unofficial API Interactive Shell 139 | Successfully logged in using your Google account. The variable 'api' holds the API object. 140 | Feel free to use help(api). 141 | 142 | >>> print(api.__doc__) 143 | Google Play Unofficial API Class 144 | Usual APIs methods are login(), search(), details(), download(), browse() and list(). 145 | toStr() can be used to pretty print the result (protobuf object) of the previous methods. 146 | toDict() converts the result into a dict, for easier introspection. 147 | 148 | >>> res = api.search("angry birds") 149 | >>> for i in res.doc[0].child: 150 | ... print(helpers.str_compat(i.title)) 151 | ... 152 | Angry Birds 153 | Angry Birds 2 154 | Angry Birds POP Bubble Shooter 155 | Angry Birds Rio 156 | Angry Birds Transformers 157 | Angry Birds Star Wars II Free 158 | Angry Birds Go! 159 | [...] 160 | 161 | All results returned by methods such as `search()`, `details()`, ..., are Protobuf objects. You can use `toStr` and `toDict` method from `GooglePlayAPI` to pretty-print them and make introspection easier if you're not familiar with Protobuf. 162 | 163 | >>> s = api.browse() 164 | >>> s 165 | 166 | >>> d = api.toDict(s) 167 | >>> d.keys() # on Python 2 168 | ['promoUrl', 'category', 'contentsUrl'] 169 | >>> d.keys() # on Python 3 170 | dict_keys(['promoUrl', 'contentsUrl', 'category']) 171 | >>> print(d['category']) 172 | [{'name': 'Games', 'dataUrl': 'browse?c=3&cat=GAME'}, {'name': 'Books & Reference', 'dataUrl': 'browse?c=3&cat=BOOKS_AND_REFERENCE'}, {'name': 'Business', 'dataUrl': 'browse?c=3&cat=BUSINESS'}, {'name': 'Comics', 'dataUrl': 'browse?c=3&cat=COMICS'}, 173 | [...] 174 | 175 | 176 | ### Using the API as a module in another project 177 | 178 | You only need `googleplay.py` and `googleplay_pb2.py`. All other scripts are just front-ends. 179 | 180 | >>> from googleplay import GooglePlayAPI 181 | >>> help(GooglePlayAPI) 182 | 183 | What else? 184 | 185 | ### To be continued 186 | 187 | Feel free to extend the API, add command-line options to scripts, fork the project, and port it to any language. 188 | You can generate Protobuf stubs from `googleplay.proto` file with Google's `protoc`: 189 | 190 | $ protoc -h 191 | Usage: protoc [OPTION] PROTO_FILES 192 | Parse PROTO_FILES and generate output based on the options given: 193 | [...] 194 | --cpp_out=OUT_DIR Generate C++ header and source. 195 | --java_out=OUT_DIR Generate Java source file. 196 | --python_out=OUT_DIR Generate Python source file. 197 | 198 | ## License 199 | 200 | This project is released under the BSD license. 201 | 202 | -------------------------------------------------------------------------------- /googleplay_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeroBurner/googleplayupdater/445dd66773bdb6474b3840044389a451272c0f06/googleplay_api/__init__.py -------------------------------------------------------------------------------- /googleplay_api/googleplay.proto: -------------------------------------------------------------------------------- 1 | message AckNotificationResponse { 2 | } 3 | message AndroidAppDeliveryData { 4 | optional int64 downloadSize = 1; 5 | optional string signature = 2; 6 | optional string downloadUrl = 3; 7 | repeated AppFileMetadata additionalFile = 4; 8 | repeated HttpCookie downloadAuthCookie = 5; 9 | optional bool forwardLocked = 6; 10 | optional int64 refundTimeout = 7; 11 | optional bool serverInitiated = 8; 12 | optional int64 postInstallRefundWindowMillis = 9; 13 | optional bool immediateStartNeeded = 10; 14 | optional AndroidAppPatchData patchData = 11; 15 | optional EncryptionParams encryptionParams = 12; 16 | } 17 | message AndroidAppPatchData { 18 | optional int32 baseVersionCode = 1; 19 | optional string baseSignature = 2; 20 | optional string downloadUrl = 3; 21 | optional int32 patchFormat = 4; 22 | optional int64 maxPatchSize = 5; 23 | } 24 | message AppFileMetadata { 25 | optional int32 fileType = 1; 26 | optional int32 versionCode = 2; 27 | optional int64 size = 3; 28 | optional string downloadUrl = 4; 29 | } 30 | message EncryptionParams { 31 | optional int32 version = 1; 32 | optional string encryptionKey = 2; 33 | optional string hmacKey = 3; 34 | } 35 | message HttpCookie { 36 | optional string name = 1; 37 | optional string value = 2; 38 | } 39 | message Address { 40 | optional string name = 1; 41 | optional string addressLine1 = 2; 42 | optional string addressLine2 = 3; 43 | optional string city = 4; 44 | optional string state = 5; 45 | optional string postalCode = 6; 46 | optional string postalCountry = 7; 47 | optional string dependentLocality = 8; 48 | optional string sortingCode = 9; 49 | optional string languageCode = 10; 50 | optional string phoneNumber = 11; 51 | optional bool isReduced = 12; 52 | optional string firstName = 13; 53 | optional string lastName = 14; 54 | optional string email = 15; 55 | } 56 | message BookAuthor { 57 | optional string name = 1; 58 | optional string deprecatedQuery = 2; 59 | optional Docid docid = 3; 60 | } 61 | message BookDetails { 62 | repeated BookSubject subject = 3; 63 | optional string publisher = 4; 64 | optional string publicationDate = 5; 65 | optional string isbn = 6; 66 | optional int32 numberOfPages = 7; 67 | optional string subtitle = 8; 68 | repeated BookAuthor author = 9; 69 | optional string readerUrl = 10; 70 | optional string downloadEpubUrl = 11; 71 | optional string downloadPdfUrl = 12; 72 | optional string acsEpubTokenUrl = 13; 73 | optional string acsPdfTokenUrl = 14; 74 | optional bool epubAvailable = 15; 75 | optional bool pdfAvailable = 16; 76 | optional string aboutTheAuthor = 17; 77 | repeated group Identifier = 18 { 78 | optional int32 type = 19; 79 | optional string identifier = 20; 80 | } 81 | } 82 | message BookSubject { 83 | optional string name = 1; 84 | optional string query = 2; 85 | optional string subjectId = 3; 86 | } 87 | message BrowseLink { 88 | optional string name = 1; 89 | optional string dataUrl = 3; 90 | } 91 | message BrowseResponse { 92 | optional string contentsUrl = 1; 93 | optional string promoUrl = 2; 94 | repeated BrowseLink category = 3; 95 | repeated BrowseLink breadcrumb = 4; 96 | } 97 | message AddressChallenge { 98 | optional string responseAddressParam = 1; 99 | optional string responseCheckboxesParam = 2; 100 | optional string title = 3; 101 | optional string descriptionHtml = 4; 102 | repeated FormCheckbox checkbox = 5; 103 | optional Address address = 6; 104 | repeated InputValidationError errorInputField = 7; 105 | optional string errorHtml = 8; 106 | repeated int32 requiredField = 9; 107 | } 108 | message AuthenticationChallenge { 109 | optional int32 authenticationType = 1; 110 | optional string responseAuthenticationTypeParam = 2; 111 | optional string responseRetryCountParam = 3; 112 | optional string pinHeaderText = 4; 113 | optional string pinDescriptionTextHtml = 5; 114 | optional string gaiaHeaderText = 6; 115 | optional string gaiaDescriptionTextHtml = 7; 116 | } 117 | message BuyResponse { 118 | optional PurchaseNotificationResponse purchaseResponse = 1; 119 | optional group CheckoutInfo = 2 { 120 | optional LineItem item = 3; 121 | repeated LineItem subItem = 4; 122 | repeated group CheckoutOption = 5 { 123 | optional string formOfPayment = 6; 124 | optional string encodedAdjustedCart = 7; 125 | optional string instrumentId = 15; 126 | repeated LineItem item = 16; 127 | repeated LineItem subItem = 17; 128 | optional LineItem total = 18; 129 | repeated string footerHtml = 19; 130 | optional int32 instrumentFamily = 29; 131 | repeated int32 deprecatedInstrumentInapplicableReason = 30; 132 | optional bool selectedInstrument = 32; 133 | optional LineItem summary = 33; 134 | repeated string footnoteHtml = 35; 135 | optional Instrument instrument = 43; 136 | optional string purchaseCookie = 45; 137 | repeated string disabledReason = 48; 138 | } 139 | optional string deprecatedCheckoutUrl = 10; 140 | optional string addInstrumentUrl = 11; 141 | repeated string footerHtml = 20; 142 | repeated int32 eligibleInstrumentFamily = 31; 143 | repeated string footnoteHtml = 36; 144 | repeated Instrument eligibleInstrument = 44; 145 | } 146 | optional string continueViaUrl = 8; 147 | optional string purchaseStatusUrl = 9; 148 | optional string checkoutServiceId = 12; 149 | optional bool checkoutTokenRequired = 13; 150 | optional string baseCheckoutUrl = 14; 151 | repeated string tosCheckboxHtml = 37; 152 | optional int32 iabPermissionError = 38; 153 | optional PurchaseStatusResponse purchaseStatusResponse = 39; 154 | optional string purchaseCookie = 46; 155 | optional Challenge challenge = 49; 156 | } 157 | message Challenge { 158 | optional AddressChallenge addressChallenge = 1; 159 | optional AuthenticationChallenge authenticationChallenge = 2; 160 | } 161 | message FormCheckbox { 162 | optional string description = 1; 163 | optional bool checked = 2; 164 | optional bool required = 3; 165 | } 166 | message LineItem { 167 | optional string name = 1; 168 | optional string description = 2; 169 | optional Offer offer = 3; 170 | optional Money amount = 4; 171 | } 172 | message Money { 173 | optional int64 micros = 1; 174 | optional string currencyCode = 2; 175 | optional string formattedAmount = 3; 176 | } 177 | message PurchaseNotificationResponse { 178 | optional int32 status = 1; 179 | optional DebugInfo debugInfo = 2; 180 | optional string localizedErrorMessage = 3; 181 | optional string purchaseId = 4; 182 | } 183 | message PurchaseStatusResponse { 184 | optional int32 status = 1; 185 | optional string statusMsg = 2; 186 | optional string statusTitle = 3; 187 | optional string briefMessage = 4; 188 | optional string infoUrl = 5; 189 | optional LibraryUpdate libraryUpdate = 6; 190 | optional Instrument rejectedInstrument = 7; 191 | optional AndroidAppDeliveryData appDeliveryData = 8; 192 | } 193 | message CheckInstrumentResponse { 194 | optional bool userHasValidInstrument = 1; 195 | optional bool checkoutTokenRequired = 2; 196 | repeated Instrument instrument = 4; 197 | repeated Instrument eligibleInstrument = 5; 198 | } 199 | message UpdateInstrumentRequest { 200 | optional Instrument instrument = 1; 201 | optional string checkoutToken = 2; 202 | } 203 | message UpdateInstrumentResponse { 204 | optional int32 result = 1; 205 | optional string instrumentId = 2; 206 | optional string userMessageHtml = 3; 207 | repeated InputValidationError errorInputField = 4; 208 | optional bool checkoutTokenRequired = 5; 209 | optional RedeemedPromoOffer redeemedOffer = 6; 210 | } 211 | message InitiateAssociationResponse { 212 | optional string userToken = 1; 213 | } 214 | message VerifyAssociationResponse { 215 | optional int32 status = 1; 216 | optional Address billingAddress = 2; 217 | optional CarrierTos carrierTos = 3; 218 | } 219 | message AddCreditCardPromoOffer { 220 | optional string headerText = 1; 221 | optional string descriptionHtml = 2; 222 | optional Image image = 3; 223 | optional string introductoryTextHtml = 4; 224 | optional string offerTitle = 5; 225 | optional string noActionDescription = 6; 226 | optional string termsAndConditionsHtml = 7; 227 | } 228 | message AvailablePromoOffer { 229 | optional AddCreditCardPromoOffer addCreditCardOffer = 1; 230 | } 231 | message CheckPromoOfferResponse { 232 | repeated AvailablePromoOffer availableOffer = 1; 233 | optional RedeemedPromoOffer redeemedOffer = 2; 234 | optional bool checkoutTokenRequired = 3; 235 | } 236 | message RedeemedPromoOffer { 237 | optional string headerText = 1; 238 | optional string descriptionHtml = 2; 239 | optional Image image = 3; 240 | } 241 | message Docid { 242 | optional string backendDocid = 1; 243 | optional int32 type = 2; 244 | optional int32 backend = 3; 245 | } 246 | message Install { 247 | optional fixed64 androidId = 1; 248 | optional int32 version = 2; 249 | optional bool bundled = 3; 250 | } 251 | message Offer { 252 | optional int64 micros = 1; 253 | optional string currencyCode = 2; 254 | optional string formattedAmount = 3; 255 | repeated Offer convertedPrice = 4; 256 | optional bool checkoutFlowRequired = 5; 257 | optional int64 fullPriceMicros = 6; 258 | optional string formattedFullAmount = 7; 259 | optional int32 offerType = 8; 260 | optional RentalTerms rentalTerms = 9; 261 | optional int64 onSaleDate = 10; 262 | repeated string promotionLabel = 11; 263 | optional SubscriptionTerms subscriptionTerms = 12; 264 | optional string formattedName = 13; 265 | optional string formattedDescription = 14; 266 | } 267 | message OwnershipInfo { 268 | optional int64 initiationTimestampMsec = 1; 269 | optional int64 validUntilTimestampMsec = 2; 270 | optional bool autoRenewing = 3; 271 | optional int64 refundTimeoutTimestampMsec = 4; 272 | optional int64 postDeliveryRefundWindowMsec = 5; 273 | } 274 | message RentalTerms { 275 | optional int32 grantPeriodSeconds = 1; 276 | optional int32 activatePeriodSeconds = 2; 277 | } 278 | message SubscriptionTerms { 279 | optional TimePeriod recurringPeriod = 1; 280 | optional TimePeriod trialPeriod = 2; 281 | } 282 | message TimePeriod { 283 | optional int32 unit = 1; 284 | optional int32 count = 2; 285 | } 286 | message BillingAddressSpec { 287 | optional int32 billingAddressType = 1; 288 | repeated int32 requiredField = 2; 289 | } 290 | message CarrierBillingCredentials { 291 | optional string value = 1; 292 | optional int64 expiration = 2; 293 | } 294 | message CarrierBillingInstrument { 295 | optional string instrumentKey = 1; 296 | optional string accountType = 2; 297 | optional string currencyCode = 3; 298 | optional int64 transactionLimit = 4; 299 | optional string subscriberIdentifier = 5; 300 | optional EncryptedSubscriberInfo encryptedSubscriberInfo = 6; 301 | optional CarrierBillingCredentials credentials = 7; 302 | optional CarrierTos acceptedCarrierTos = 8; 303 | } 304 | message CarrierBillingInstrumentStatus { 305 | optional CarrierTos carrierTos = 1; 306 | optional bool associationRequired = 2; 307 | optional bool passwordRequired = 3; 308 | optional PasswordPrompt carrierPasswordPrompt = 4; 309 | optional int32 apiVersion = 5; 310 | optional string name = 6; 311 | } 312 | message CarrierTos { 313 | optional CarrierTosEntry dcbTos = 1; 314 | optional CarrierTosEntry piiTos = 2; 315 | optional bool needsDcbTosAcceptance = 3; 316 | optional bool needsPiiTosAcceptance = 4; 317 | } 318 | message CarrierTosEntry { 319 | optional string url = 1; 320 | optional string version = 2; 321 | } 322 | message CreditCardInstrument { 323 | optional int32 type = 1; 324 | optional string escrowHandle = 2; 325 | optional string lastDigits = 3; 326 | optional int32 expirationMonth = 4; 327 | optional int32 expirationYear = 5; 328 | repeated EfeParam escrowEfeParam = 6; 329 | } 330 | message EfeParam { 331 | optional int32 key = 1; 332 | optional string value = 2; 333 | } 334 | message InputValidationError { 335 | optional int32 inputField = 1; 336 | optional string errorMessage = 2; 337 | } 338 | message Instrument { 339 | optional string instrumentId = 1; 340 | optional Address billingAddress = 2; 341 | optional CreditCardInstrument creditCard = 3; 342 | optional CarrierBillingInstrument carrierBilling = 4; 343 | optional BillingAddressSpec billingAddressSpec = 5; 344 | optional int32 instrumentFamily = 6; 345 | optional CarrierBillingInstrumentStatus carrierBillingStatus = 7; 346 | optional string displayTitle = 8; 347 | } 348 | message PasswordPrompt { 349 | optional string prompt = 1; 350 | optional string forgotPasswordUrl = 2; 351 | } 352 | message ContainerMetadata { 353 | optional string browseUrl = 1; 354 | optional string nextPageUrl = 2; 355 | optional double relevance = 3; 356 | optional int64 estimatedResults = 4; 357 | optional string analyticsCookie = 5; 358 | optional bool ordered = 6; 359 | } 360 | message FlagContentResponse { 361 | } 362 | message DebugInfo { 363 | repeated string message = 1; 364 | repeated group Timing = 2 { 365 | optional string name = 3; 366 | optional double timeInMs = 4; 367 | } 368 | } 369 | message DeliveryResponse { 370 | optional int32 status = 1; 371 | optional AndroidAppDeliveryData appDeliveryData = 2; 372 | } 373 | message BulkDetailsEntry { 374 | optional DocV2 doc = 1; 375 | } 376 | message BulkDetailsRequest { 377 | repeated string docid = 1; 378 | optional bool includeChildDocs = 2; 379 | } 380 | message BulkDetailsResponse { 381 | repeated BulkDetailsEntry entry = 1; 382 | } 383 | message DetailsResponse { 384 | optional DocV1 docV1 = 1; 385 | optional string analyticsCookie = 2; 386 | optional Review userReview = 3; 387 | optional DocV2 docV2 = 4; 388 | optional string footerHtml = 5; 389 | } 390 | message DeviceConfigurationProto { 391 | optional int32 touchScreen = 1; 392 | optional int32 keyboard = 2; 393 | optional int32 navigation = 3; 394 | optional int32 screenLayout = 4; 395 | optional bool hasHardKeyboard = 5; 396 | optional bool hasFiveWayNavigation = 6; 397 | optional int32 screenDensity = 7; 398 | optional int32 glEsVersion = 8; 399 | repeated string systemSharedLibrary = 9; 400 | repeated string systemAvailableFeature = 10; 401 | repeated string nativePlatform = 11; 402 | optional int32 screenWidth = 12; 403 | optional int32 screenHeight = 13; 404 | repeated string systemSupportedLocale = 14; 405 | repeated string glExtension = 15; 406 | optional int32 deviceClass = 16; 407 | optional int32 maxApkDownloadSizeMb = 17; 408 | } 409 | message Document { 410 | optional Docid docid = 1; 411 | optional Docid fetchDocid = 2; 412 | optional Docid sampleDocid = 3; 413 | optional string title = 4; 414 | optional string url = 5; 415 | repeated string snippet = 6; 416 | optional Offer priceDeprecated = 7; 417 | optional Availability availability = 9; 418 | repeated Image image = 10; 419 | repeated Document child = 11; 420 | optional AggregateRating aggregateRating = 13; 421 | repeated Offer offer = 14; 422 | repeated TranslatedText translatedSnippet = 15; 423 | repeated DocumentVariant documentVariant = 16; 424 | repeated string categoryId = 17; 425 | repeated Document decoration = 18; 426 | repeated Document parent = 19; 427 | optional string privacyPolicyUrl = 20; 428 | } 429 | message DocumentVariant { 430 | optional int32 variationType = 1; 431 | optional Rule rule = 2; 432 | optional string title = 3; 433 | repeated string snippet = 4; 434 | optional string recentChanges = 5; 435 | repeated TranslatedText autoTranslation = 6; 436 | repeated Offer offer = 7; 437 | optional int64 channelId = 9; 438 | repeated Document child = 10; 439 | repeated Document decoration = 11; 440 | } 441 | message Image { 442 | optional int32 imageType = 1; 443 | optional group Dimension = 2 { 444 | optional int32 width = 3; 445 | optional int32 height = 4; 446 | } 447 | optional string imageUrl = 5; 448 | optional string altTextLocalized = 6; 449 | optional string secureUrl = 7; 450 | optional int32 positionInSequence = 8; 451 | optional bool supportsFifeUrlOptions = 9; 452 | optional group Citation = 10 { 453 | optional string titleLocalized = 11; 454 | optional string url = 12; 455 | } 456 | } 457 | message TranslatedText { 458 | optional string text = 1; 459 | optional string sourceLocale = 2; 460 | optional string targetLocale = 3; 461 | } 462 | message Badge { 463 | optional string title = 1; 464 | repeated Image image = 2; 465 | optional string browseUrl = 3; 466 | } 467 | message ContainerWithBanner { 468 | optional string colorThemeArgb = 1; 469 | } 470 | message DealOfTheDay { 471 | optional string featuredHeader = 1; 472 | optional string colorThemeArgb = 2; 473 | } 474 | message EditorialSeriesContainer { 475 | optional string seriesTitle = 1; 476 | optional string seriesSubtitle = 2; 477 | optional string episodeTitle = 3; 478 | optional string episodeSubtitle = 4; 479 | optional string colorThemeArgb = 5; 480 | } 481 | message Link { 482 | optional string uri = 1; 483 | } 484 | message PlusOneData { 485 | optional bool setByUser = 1; 486 | optional int64 total = 2; 487 | optional int64 circlesTotal = 3; 488 | repeated PlusPerson circlesPeople = 4; 489 | } 490 | message PlusPerson { 491 | optional string displayName = 2; 492 | optional string profileImageUrl = 4; 493 | } 494 | message PromotedDoc { 495 | optional string title = 1; 496 | optional string subtitle = 2; 497 | repeated Image image = 3; 498 | optional string descriptionHtml = 4; 499 | optional string detailsUrl = 5; 500 | } 501 | message Reason { 502 | optional string briefReason = 1; 503 | optional string detailedReason = 2; 504 | optional string uniqueId = 3; 505 | } 506 | message SectionMetadata { 507 | optional string header = 1; 508 | optional string listUrl = 2; 509 | optional string browseUrl = 3; 510 | optional string descriptionHtml = 4; 511 | } 512 | message SeriesAntenna { 513 | optional string seriesTitle = 1; 514 | optional string seriesSubtitle = 2; 515 | optional string episodeTitle = 3; 516 | optional string episodeSubtitle = 4; 517 | optional string colorThemeArgb = 5; 518 | optional SectionMetadata sectionTracks = 6; 519 | optional SectionMetadata sectionAlbums = 7; 520 | } 521 | message Template { 522 | optional SeriesAntenna seriesAntenna = 1; 523 | optional TileTemplate tileGraphic2X1 = 2; 524 | optional TileTemplate tileGraphic4X2 = 3; 525 | optional TileTemplate tileGraphicColoredTitle2X1 = 4; 526 | optional TileTemplate tileGraphicUpperLeftTitle2X1 = 5; 527 | optional TileTemplate tileDetailsReflectedGraphic2X2 = 6; 528 | optional TileTemplate tileFourBlock4X2 = 7; 529 | optional ContainerWithBanner containerWithBanner = 8; 530 | optional DealOfTheDay dealOfTheDay = 9; 531 | optional TileTemplate tileGraphicColoredTitle4X2 = 10; 532 | optional EditorialSeriesContainer editorialSeriesContainer = 11; 533 | } 534 | message TileTemplate { 535 | optional string colorThemeArgb = 1; 536 | optional string colorTextArgb = 2; 537 | } 538 | message Warning { 539 | optional string localizedMessage = 1; 540 | } 541 | message AlbumDetails { 542 | optional string name = 1; 543 | optional MusicDetails details = 2; 544 | optional ArtistDetails displayArtist = 3; 545 | } 546 | message AppDetails { 547 | optional string developerName = 1; 548 | optional int32 majorVersionNumber = 2; 549 | optional int32 versionCode = 3; 550 | optional string versionString = 4; 551 | optional string title = 5; 552 | repeated string appCategory = 7; 553 | optional int32 contentRating = 8; 554 | optional int64 installationSize = 9; 555 | repeated string permission = 10; 556 | optional string developerEmail = 11; 557 | optional string developerWebsite = 12; 558 | optional string numDownloads = 13; 559 | optional string packageName = 14; 560 | optional string recentChangesHtml = 15; 561 | optional string uploadDate = 16; 562 | repeated FileMetadata file = 17; 563 | optional string appType = 18; 564 | } 565 | message ArtistDetails { 566 | optional string detailsUrl = 1; 567 | optional string name = 2; 568 | optional ArtistExternalLinks externalLinks = 3; 569 | } 570 | message ArtistExternalLinks { 571 | repeated string websiteUrl = 1; 572 | optional string googlePlusProfileUrl = 2; 573 | optional string youtubeChannelUrl = 3; 574 | } 575 | message DocumentDetails { 576 | optional AppDetails appDetails = 1; 577 | optional AlbumDetails albumDetails = 2; 578 | optional ArtistDetails artistDetails = 3; 579 | optional SongDetails songDetails = 4; 580 | optional BookDetails bookDetails = 5; 581 | optional VideoDetails videoDetails = 6; 582 | optional SubscriptionDetails subscriptionDetails = 7; 583 | optional MagazineDetails magazineDetails = 8; 584 | optional TvShowDetails tvShowDetails = 9; 585 | optional TvSeasonDetails tvSeasonDetails = 10; 586 | optional TvEpisodeDetails tvEpisodeDetails = 11; 587 | } 588 | message FileMetadata { 589 | optional int32 fileType = 1; 590 | optional int32 versionCode = 2; 591 | optional int64 size = 3; 592 | } 593 | message MagazineDetails { 594 | optional string parentDetailsUrl = 1; 595 | optional string deviceAvailabilityDescriptionHtml = 2; 596 | optional string psvDescription = 3; 597 | optional string deliveryFrequencyDescription = 4; 598 | } 599 | message MusicDetails { 600 | optional int32 censoring = 1; 601 | optional int32 durationSec = 2; 602 | optional string originalReleaseDate = 3; 603 | optional string label = 4; 604 | repeated ArtistDetails artist = 5; 605 | repeated string genre = 6; 606 | optional string releaseDate = 7; 607 | repeated int32 releaseType = 8; 608 | } 609 | message SongDetails { 610 | optional string name = 1; 611 | optional MusicDetails details = 2; 612 | optional string albumName = 3; 613 | optional int32 trackNumber = 4; 614 | optional string previewUrl = 5; 615 | optional ArtistDetails displayArtist = 6; 616 | } 617 | message SubscriptionDetails { 618 | optional int32 subscriptionPeriod = 1; 619 | } 620 | message Trailer { 621 | optional string trailerId = 1; 622 | optional string title = 2; 623 | optional string thumbnailUrl = 3; 624 | optional string watchUrl = 4; 625 | optional string duration = 5; 626 | } 627 | message TvEpisodeDetails { 628 | optional string parentDetailsUrl = 1; 629 | optional int32 episodeIndex = 2; 630 | optional string releaseDate = 3; 631 | } 632 | message TvSeasonDetails { 633 | optional string parentDetailsUrl = 1; 634 | optional int32 seasonIndex = 2; 635 | optional string releaseDate = 3; 636 | optional string broadcaster = 4; 637 | } 638 | message TvShowDetails { 639 | optional int32 seasonCount = 1; 640 | optional int32 startYear = 2; 641 | optional int32 endYear = 3; 642 | optional string broadcaster = 4; 643 | } 644 | message VideoCredit { 645 | optional int32 creditType = 1; 646 | optional string credit = 2; 647 | repeated string name = 3; 648 | } 649 | message VideoDetails { 650 | repeated VideoCredit credit = 1; 651 | optional string duration = 2; 652 | optional string releaseDate = 3; 653 | optional string contentRating = 4; 654 | optional int64 likes = 5; 655 | optional int64 dislikes = 6; 656 | repeated string genre = 7; 657 | repeated Trailer trailer = 8; 658 | repeated VideoRentalTerm rentalTerm = 9; 659 | } 660 | message VideoRentalTerm { 661 | optional int32 offerType = 1; 662 | optional string offerAbbreviation = 2; 663 | optional string rentalHeader = 3; 664 | repeated group Term = 4 { 665 | optional string header = 5; 666 | optional string body = 6; 667 | } 668 | } 669 | message Bucket { 670 | repeated DocV1 document = 1; 671 | optional bool multiCorpus = 2; 672 | optional string title = 3; 673 | optional string iconUrl = 4; 674 | optional string fullContentsUrl = 5; 675 | optional double relevance = 6; 676 | optional int64 estimatedResults = 7; 677 | optional string analyticsCookie = 8; 678 | optional string fullContentsListUrl = 9; 679 | optional string nextPageUrl = 10; 680 | optional bool ordered = 11; 681 | } 682 | message ListResponse { 683 | repeated Bucket bucket = 1; 684 | repeated DocV2 doc = 2; 685 | } 686 | message DocV1 { 687 | optional Document finskyDoc = 1; 688 | optional string docid = 2; 689 | optional string detailsUrl = 3; 690 | optional string reviewsUrl = 4; 691 | optional string relatedListUrl = 5; 692 | optional string moreByListUrl = 6; 693 | optional string shareUrl = 7; 694 | optional string creator = 8; 695 | optional DocumentDetails details = 9; 696 | optional string descriptionHtml = 10; 697 | optional string relatedBrowseUrl = 11; 698 | optional string moreByBrowseUrl = 12; 699 | optional string relatedHeader = 13; 700 | optional string moreByHeader = 14; 701 | optional string title = 15; 702 | optional PlusOneData plusOneData = 16; 703 | optional string warningMessage = 17; 704 | } 705 | message Annotations { 706 | optional SectionMetadata sectionRelated = 1; 707 | optional SectionMetadata sectionMoreBy = 2; 708 | optional PlusOneData plusOneData = 3; 709 | repeated Warning warning = 4; 710 | optional SectionMetadata sectionBodyOfWork = 5; 711 | optional SectionMetadata sectionCoreContent = 6; 712 | optional Template template = 7; 713 | repeated Badge badgeForCreator = 8; 714 | repeated Badge badgeForDoc = 9; 715 | optional Link link = 10; 716 | optional SectionMetadata sectionCrossSell = 11; 717 | optional SectionMetadata sectionRelatedDocType = 12; 718 | repeated PromotedDoc promotedDoc = 13; 719 | optional string offerNote = 14; 720 | repeated DocV2 subscription = 16; 721 | optional Reason reason = 17; 722 | optional string privacyPolicyUrl = 18; 723 | } 724 | message DocV2 { 725 | optional string docid = 1; 726 | optional string backendDocid = 2; 727 | optional int32 docType = 3; 728 | optional int32 backendId = 4; 729 | optional string title = 5; 730 | optional string creator = 6; 731 | optional string descriptionHtml = 7; 732 | repeated Offer offer = 8; 733 | optional Availability availability = 9; 734 | repeated Image image = 10; 735 | repeated DocV2 child = 11; 736 | optional ContainerMetadata containerMetadata = 12; 737 | optional DocumentDetails details = 13; 738 | optional AggregateRating aggregateRating = 14; 739 | optional Annotations annotations = 15; 740 | optional string detailsUrl = 16; 741 | optional string shareUrl = 17; 742 | optional string reviewsUrl = 18; 743 | optional string backendUrl = 19; 744 | optional string purchaseDetailsUrl = 20; 745 | optional bool detailsReusable = 21; 746 | optional string subtitle = 22; 747 | } 748 | message EncryptedSubscriberInfo { 749 | optional string data = 1; 750 | optional string encryptedKey = 2; 751 | optional string signature = 3; 752 | optional string initVector = 4; 753 | optional int32 googleKeyVersion = 5; 754 | optional int32 carrierKeyVersion = 6; 755 | } 756 | message Availability { 757 | optional int32 restriction = 5; 758 | optional int32 offerType = 6; 759 | optional Rule rule = 7; 760 | repeated group PerDeviceAvailabilityRestriction = 9 { 761 | optional fixed64 androidId = 10; 762 | optional int32 deviceRestriction = 11; 763 | optional int64 channelId = 12; 764 | optional FilterEvaluationInfo filterInfo = 15; 765 | } 766 | optional bool availableIfOwned = 13; 767 | repeated Install install = 14; 768 | optional FilterEvaluationInfo filterInfo = 16; 769 | optional OwnershipInfo ownershipInfo = 17; 770 | } 771 | message FilterEvaluationInfo { 772 | repeated RuleEvaluation ruleEvaluation = 1; 773 | } 774 | message Rule { 775 | optional bool negate = 1; 776 | optional int32 operator = 2; 777 | optional int32 key = 3; 778 | repeated string stringArg = 4; 779 | repeated int64 longArg = 5; 780 | repeated double doubleArg = 6; 781 | repeated Rule subrule = 7; 782 | optional int32 responseCode = 8; 783 | optional string comment = 9; 784 | repeated fixed64 stringArgHash = 10; 785 | repeated int32 constArg = 11; 786 | } 787 | message RuleEvaluation { 788 | optional Rule rule = 1; 789 | repeated string actualStringValue = 2; 790 | repeated int64 actualLongValue = 3; 791 | repeated bool actualBoolValue = 4; 792 | repeated double actualDoubleValue = 5; 793 | } 794 | message LibraryAppDetails { 795 | optional string certificateHash = 2; 796 | optional int64 refundTimeoutTimestampMsec = 3; 797 | optional int64 postDeliveryRefundWindowMsec = 4; 798 | } 799 | message LibraryMutation { 800 | optional Docid docid = 1; 801 | optional int32 offerType = 2; 802 | optional int64 documentHash = 3; 803 | optional bool deleted = 4; 804 | optional LibraryAppDetails appDetails = 5; 805 | optional LibrarySubscriptionDetails subscriptionDetails = 6; 806 | } 807 | message LibrarySubscriptionDetails { 808 | optional int64 initiationTimestampMsec = 1; 809 | optional int64 validUntilTimestampMsec = 2; 810 | optional bool autoRenewing = 3; 811 | optional int64 trialUntilTimestampMsec = 4; 812 | } 813 | message LibraryUpdate { 814 | optional int32 status = 1; 815 | optional int32 corpus = 2; 816 | optional bytes serverToken = 3; 817 | repeated LibraryMutation mutation = 4; 818 | optional bool hasMore = 5; 819 | optional string libraryId = 6; 820 | } 821 | message ClientLibraryState { 822 | optional int32 corpus = 1; 823 | optional bytes serverToken = 2; 824 | optional int64 hashCodeSum = 3; 825 | optional int32 librarySize = 4; 826 | } 827 | message LibraryReplicationRequest { 828 | repeated ClientLibraryState libraryState = 1; 829 | } 830 | message LibraryReplicationResponse { 831 | repeated LibraryUpdate update = 1; 832 | } 833 | message ClickLogEvent { 834 | optional int64 eventTime = 1; 835 | optional string url = 2; 836 | optional string listId = 3; 837 | optional string referrerUrl = 4; 838 | optional string referrerListId = 5; 839 | } 840 | message LogRequest { 841 | repeated ClickLogEvent clickEvent = 1; 842 | } 843 | message LogResponse { 844 | } 845 | message AndroidAppNotificationData { 846 | optional int32 versionCode = 1; 847 | optional string assetId = 2; 848 | } 849 | message InAppNotificationData { 850 | optional string checkoutOrderId = 1; 851 | optional string inAppNotificationId = 2; 852 | } 853 | message LibraryDirtyData { 854 | optional int32 backend = 1; 855 | } 856 | message Notification { 857 | optional int32 notificationType = 1; 858 | optional int64 timestamp = 3; 859 | optional Docid docid = 4; 860 | optional string docTitle = 5; 861 | optional string userEmail = 6; 862 | optional AndroidAppNotificationData appData = 7; 863 | optional AndroidAppDeliveryData appDeliveryData = 8; 864 | optional PurchaseRemovalData purchaseRemovalData = 9; 865 | optional UserNotificationData userNotificationData = 10; 866 | optional InAppNotificationData inAppNotificationData = 11; 867 | optional PurchaseDeclinedData purchaseDeclinedData = 12; 868 | optional string notificationId = 13; 869 | optional LibraryUpdate libraryUpdate = 14; 870 | optional LibraryDirtyData libraryDirtyData = 15; 871 | } 872 | message PurchaseDeclinedData { 873 | optional int32 reason = 1; 874 | optional bool showNotification = 2; 875 | } 876 | message PurchaseRemovalData { 877 | optional bool malicious = 1; 878 | } 879 | message UserNotificationData { 880 | optional string notificationTitle = 1; 881 | optional string notificationText = 2; 882 | optional string tickerText = 3; 883 | optional string dialogTitle = 4; 884 | optional string dialogText = 5; 885 | } 886 | message PlusOneResponse { 887 | } 888 | message RateSuggestedContentResponse { 889 | } 890 | message AggregateRating { 891 | optional int32 type = 1; 892 | optional float starRating = 2; 893 | optional uint64 ratingsCount = 3; 894 | optional uint64 oneStarRatings = 4; 895 | optional uint64 twoStarRatings = 5; 896 | optional uint64 threeStarRatings = 6; 897 | optional uint64 fourStarRatings = 7; 898 | optional uint64 fiveStarRatings = 8; 899 | optional uint64 thumbsUpCount = 9; 900 | optional uint64 thumbsDownCount = 10; 901 | optional uint64 commentCount = 11; 902 | optional double bayesianMeanRating = 12; 903 | } 904 | message DirectPurchase { 905 | optional string detailsUrl = 1; 906 | optional string purchaseDocid = 2; 907 | optional string parentDocid = 3; 908 | optional int32 offerType = 4; 909 | } 910 | message ResolveLinkResponse { 911 | optional string detailsUrl = 1; 912 | optional string browseUrl = 2; 913 | optional string searchUrl = 3; 914 | optional DirectPurchase directPurchase = 4; 915 | optional string homeUrl = 5; 916 | } 917 | message Payload { 918 | optional ListResponse listResponse = 1; 919 | optional DetailsResponse detailsResponse = 2; 920 | optional ReviewResponse reviewResponse = 3; 921 | optional BuyResponse buyResponse = 4; 922 | optional SearchResponse searchResponse = 5; 923 | optional TocResponse tocResponse = 6; 924 | optional BrowseResponse browseResponse = 7; 925 | optional PurchaseStatusResponse purchaseStatusResponse = 8; 926 | optional UpdateInstrumentResponse updateInstrumentResponse = 9; 927 | optional LogResponse logResponse = 10; 928 | optional CheckInstrumentResponse checkInstrumentResponse = 11; 929 | optional PlusOneResponse plusOneResponse = 12; 930 | optional FlagContentResponse flagContentResponse = 13; 931 | optional AckNotificationResponse ackNotificationResponse = 14; 932 | optional InitiateAssociationResponse initiateAssociationResponse = 15; 933 | optional VerifyAssociationResponse verifyAssociationResponse = 16; 934 | optional LibraryReplicationResponse libraryReplicationResponse = 17; 935 | optional RevokeResponse revokeResponse = 18; 936 | optional BulkDetailsResponse bulkDetailsResponse = 19; 937 | optional ResolveLinkResponse resolveLinkResponse = 20; 938 | optional DeliveryResponse deliveryResponse = 21; 939 | optional AcceptTosResponse acceptTosResponse = 22; 940 | optional RateSuggestedContentResponse rateSuggestedContentResponse = 23; 941 | optional CheckPromoOfferResponse checkPromoOfferResponse = 24; 942 | } 943 | message PreFetch { 944 | optional string url = 1; 945 | optional bytes response = 2; 946 | optional string etag = 3; 947 | optional int64 ttl = 4; 948 | optional int64 softTtl = 5; 949 | } 950 | message ResponseWrapper { 951 | optional Payload payload = 1; 952 | optional ServerCommands commands = 2; 953 | repeated PreFetch preFetch = 3; 954 | repeated Notification notification = 4; 955 | } 956 | message ServerCommands { 957 | optional bool clearCache = 1; 958 | optional string displayErrorMessage = 2; 959 | optional string logErrorStacktrace = 3; 960 | } 961 | message GetReviewsResponse { 962 | repeated Review review = 1; 963 | optional int64 matchingCount = 2; 964 | } 965 | message Review { 966 | optional string authorName = 1; 967 | optional string url = 2; 968 | optional string source = 3; 969 | optional string documentVersion = 4; 970 | optional int64 timestampMsec = 5; 971 | optional int32 starRating = 6; 972 | optional string title = 7; 973 | optional string comment = 8; 974 | optional string commentId = 9; 975 | optional string deviceName = 19; 976 | optional string replyText = 29; 977 | optional int64 replyTimestampMsec = 30; 978 | } 979 | message ReviewResponse { 980 | optional GetReviewsResponse getResponse = 1; 981 | optional string nextPageUrl = 2; 982 | } 983 | message RevokeResponse { 984 | optional LibraryUpdate libraryUpdate = 1; 985 | } 986 | message RelatedSearch { 987 | optional string searchUrl = 1; 988 | optional string header = 2; 989 | optional int32 backendId = 3; 990 | optional int32 docType = 4; 991 | optional bool current = 5; 992 | } 993 | message SearchResponse { 994 | optional string originalQuery = 1; 995 | optional string suggestedQuery = 2; 996 | optional bool aggregateQuery = 3; 997 | repeated Bucket bucket = 4; 998 | repeated DocV2 doc = 5; 999 | repeated RelatedSearch relatedSearch = 6; 1000 | } 1001 | message CorpusMetadata { 1002 | optional int32 backend = 1; 1003 | optional string name = 2; 1004 | optional string landingUrl = 3; 1005 | optional string libraryName = 4; 1006 | } 1007 | message Experiments { 1008 | repeated string experimentId = 1; 1009 | } 1010 | message TocResponse { 1011 | repeated CorpusMetadata corpus = 1; 1012 | optional int32 tosVersionDeprecated = 2; 1013 | optional string tosContent = 3; 1014 | optional string homeUrl = 4; 1015 | optional Experiments experiments = 5; 1016 | optional string tosCheckboxTextMarketingEmails = 6; 1017 | optional string tosToken = 7; 1018 | optional UserSettings userSettings = 8; 1019 | optional string iconOverrideUrl = 9; 1020 | } 1021 | message UserSettings { 1022 | optional bool tosCheckboxMarketingEmailsOptedIn = 1; 1023 | } 1024 | message AcceptTosResponse { 1025 | } 1026 | message AckNotificationsRequestProto { 1027 | repeated string notificationId = 1; 1028 | optional SignatureHashProto signatureHash = 2; 1029 | repeated string nackNotificationId = 3; 1030 | } 1031 | message AckNotificationsResponseProto { 1032 | } 1033 | message AddressProto { 1034 | optional string address1 = 1; 1035 | optional string address2 = 2; 1036 | optional string city = 3; 1037 | optional string state = 4; 1038 | optional string postalCode = 5; 1039 | optional string country = 6; 1040 | optional string name = 7; 1041 | optional string type = 8; 1042 | optional string phone = 9; 1043 | } 1044 | message AppDataProto { 1045 | optional string key = 1; 1046 | optional string value = 2; 1047 | } 1048 | message AppSuggestionProto { 1049 | optional ExternalAssetProto assetInfo = 1; 1050 | } 1051 | message AssetIdentifierProto { 1052 | optional string packageName = 1; 1053 | optional int32 versionCode = 2; 1054 | optional string assetId = 3; 1055 | } 1056 | message AssetsRequestProto { 1057 | optional int32 assetType = 1; 1058 | optional string query = 2; 1059 | optional string categoryId = 3; 1060 | repeated string assetId = 4; 1061 | optional bool retrieveVendingHistory = 5; 1062 | optional bool retrieveExtendedInfo = 6; 1063 | optional int32 sortOrder = 7; 1064 | optional int64 startIndex = 8; 1065 | optional int64 numEntries = 9; 1066 | optional int32 viewFilter = 10; 1067 | optional string rankingType = 11; 1068 | optional bool retrieveCarrierChannel = 12; 1069 | repeated string pendingDownloadAssetId = 13; 1070 | optional bool reconstructVendingHistory = 14; 1071 | optional bool unfilteredResults = 15; 1072 | repeated string badgeId = 16; 1073 | } 1074 | message AssetsResponseProto { 1075 | repeated ExternalAssetProto asset = 1; 1076 | optional int64 numTotalEntries = 2; 1077 | optional string correctedQuery = 3; 1078 | repeated ExternalAssetProto altAsset = 4; 1079 | optional int64 numCorrectedEntries = 5; 1080 | optional string header = 6; 1081 | optional int32 listType = 7; 1082 | } 1083 | message BillingEventRequestProto { 1084 | optional int32 eventType = 1; 1085 | optional string billingParametersId = 2; 1086 | optional bool resultSuccess = 3; 1087 | optional string clientMessage = 4; 1088 | optional ExternalCarrierBillingInstrumentProto carrierInstrument = 5; 1089 | } 1090 | message BillingEventResponseProto { 1091 | } 1092 | message BillingParameterProto { 1093 | optional string id = 1; 1094 | optional string name = 2; 1095 | repeated string mncMcc = 3; 1096 | repeated string backendUrl = 4; 1097 | optional string iconId = 5; 1098 | optional int32 billingInstrumentType = 6; 1099 | optional string applicationId = 7; 1100 | optional string tosUrl = 8; 1101 | optional bool instrumentTosRequired = 9; 1102 | optional int32 apiVersion = 10; 1103 | optional bool perTransactionCredentialsRequired = 11; 1104 | optional bool sendSubscriberIdWithCarrierBillingRequests = 12; 1105 | optional int32 deviceAssociationMethod = 13; 1106 | optional string userTokenRequestMessage = 14; 1107 | optional string userTokenRequestAddress = 15; 1108 | optional bool passphraseRequired = 16; 1109 | } 1110 | message CarrierBillingCredentialsProto { 1111 | optional string credentials = 1; 1112 | optional int64 credentialsTimeout = 2; 1113 | } 1114 | message CategoryProto { 1115 | optional int32 assetType = 2; 1116 | optional string categoryId = 3; 1117 | optional string categoryDisplay = 4; 1118 | optional string categorySubtitle = 5; 1119 | repeated string promotedAssetsNew = 6; 1120 | repeated string promotedAssetsHome = 7; 1121 | repeated CategoryProto subCategories = 8; 1122 | repeated string promotedAssetsPaid = 9; 1123 | repeated string promotedAssetsFree = 10; 1124 | } 1125 | message CheckForNotificationsRequestProto { 1126 | optional int64 alarmDuration = 1; 1127 | } 1128 | message CheckForNotificationsResponseProto { 1129 | } 1130 | message CheckLicenseRequestProto { 1131 | optional string packageName = 1; 1132 | optional int32 versionCode = 2; 1133 | optional int64 nonce = 3; 1134 | } 1135 | message CheckLicenseResponseProto { 1136 | optional int32 responseCode = 1; 1137 | optional string signedData = 2; 1138 | optional string signature = 3; 1139 | } 1140 | message CommentsRequestProto { 1141 | optional string assetId = 1; 1142 | optional int64 startIndex = 2; 1143 | optional int64 numEntries = 3; 1144 | optional bool shouldReturnSelfComment = 4; 1145 | optional string assetReferrer = 5; 1146 | } 1147 | message CommentsResponseProto { 1148 | repeated ExternalCommentProto comment = 1; 1149 | optional int64 numTotalEntries = 2; 1150 | optional ExternalCommentProto selfComment = 3; 1151 | } 1152 | message ContentSyncRequestProto { 1153 | optional bool incremental = 1; 1154 | repeated group AssetInstallState = 2 { 1155 | optional string assetId = 3; 1156 | optional int32 assetState = 4; 1157 | optional int64 installTime = 5; 1158 | optional int64 uninstallTime = 6; 1159 | optional string packageName = 7; 1160 | optional int32 versionCode = 8; 1161 | optional string assetReferrer = 9; 1162 | } 1163 | repeated group SystemApp = 10 { 1164 | optional string packageName = 11; 1165 | optional int32 versionCode = 12; 1166 | repeated string certificateHash = 13; 1167 | } 1168 | optional int32 sideloadedAppCount = 14; 1169 | } 1170 | message ContentSyncResponseProto { 1171 | optional int32 numUpdatesAvailable = 1; 1172 | } 1173 | message DataMessageProto { 1174 | optional string category = 1; 1175 | repeated AppDataProto appData = 3; 1176 | } 1177 | message DownloadInfoProto { 1178 | optional int64 apkSize = 1; 1179 | repeated FileMetadataProto additionalFile = 2; 1180 | } 1181 | message ExternalAssetProto { 1182 | optional string id = 1; 1183 | optional string title = 2; 1184 | optional int32 assetType = 3; 1185 | optional string owner = 4; 1186 | optional string version = 5; 1187 | optional string price = 6; 1188 | optional string averageRating = 7; 1189 | optional int64 numRatings = 8; 1190 | optional group PurchaseInformation = 9 { 1191 | optional int64 purchaseTime = 10; 1192 | optional int64 refundTimeoutTime = 11; 1193 | optional int32 refundStartPolicy = 45; 1194 | optional int64 refundWindowDuration = 46; 1195 | } 1196 | optional group ExtendedInfo = 12 { 1197 | optional string description = 13; 1198 | optional int64 downloadCount = 14; 1199 | repeated string applicationPermissionId = 15; 1200 | optional int64 requiredInstallationSize = 16; 1201 | optional string packageName = 17; 1202 | optional string category = 18; 1203 | optional bool forwardLocked = 19; 1204 | optional string contactEmail = 20; 1205 | optional bool everInstalledByUser = 21; 1206 | optional string downloadCountString = 23; 1207 | optional string contactPhone = 26; 1208 | optional string contactWebsite = 27; 1209 | optional bool nextPurchaseRefundable = 28; 1210 | optional int32 numScreenshots = 30; 1211 | optional string promotionalDescription = 31; 1212 | optional int32 serverAssetState = 34; 1213 | optional int32 contentRatingLevel = 36; 1214 | optional string contentRatingString = 37; 1215 | optional string recentChanges = 38; 1216 | repeated group PackageDependency = 39 { 1217 | optional string packageName = 41; 1218 | optional bool skipPermissions = 42; 1219 | } 1220 | optional string videoLink = 43; 1221 | optional DownloadInfoProto downloadInfo = 49; 1222 | } 1223 | optional string ownerId = 22; 1224 | optional string packageName = 24; 1225 | optional int32 versionCode = 25; 1226 | optional bool bundledAsset = 29; 1227 | optional string priceCurrency = 32; 1228 | optional int64 priceMicros = 33; 1229 | optional string filterReason = 35; 1230 | optional string actualSellerPrice = 40; 1231 | repeated ExternalBadgeProto appBadge = 47; 1232 | repeated ExternalBadgeProto ownerBadge = 48; 1233 | } 1234 | message ExternalBadgeImageProto { 1235 | optional int32 usage = 1; 1236 | optional string url = 2; 1237 | } 1238 | message ExternalBadgeProto { 1239 | optional string localizedTitle = 1; 1240 | optional string localizedDescription = 2; 1241 | repeated ExternalBadgeImageProto badgeImage = 3; 1242 | optional string searchId = 4; 1243 | } 1244 | message ExternalCarrierBillingInstrumentProto { 1245 | optional string instrumentKey = 1; 1246 | optional string subscriberIdentifier = 2; 1247 | optional string accountType = 3; 1248 | optional string subscriberCurrency = 4; 1249 | optional uint64 transactionLimit = 5; 1250 | optional string subscriberName = 6; 1251 | optional string address1 = 7; 1252 | optional string address2 = 8; 1253 | optional string city = 9; 1254 | optional string state = 10; 1255 | optional string postalCode = 11; 1256 | optional string country = 12; 1257 | optional EncryptedSubscriberInfo encryptedSubscriberInfo = 13; 1258 | } 1259 | message ExternalCommentProto { 1260 | optional string body = 1; 1261 | optional int32 rating = 2; 1262 | optional string creatorName = 3; 1263 | optional int64 creationTime = 4; 1264 | optional string creatorId = 5; 1265 | } 1266 | message ExternalCreditCard { 1267 | optional string type = 1; 1268 | optional string lastDigits = 2; 1269 | optional int32 expYear = 3; 1270 | optional int32 expMonth = 4; 1271 | optional string personName = 5; 1272 | optional string countryCode = 6; 1273 | optional string postalCode = 7; 1274 | optional bool makeDefault = 8; 1275 | optional string address1 = 9; 1276 | optional string address2 = 10; 1277 | optional string city = 11; 1278 | optional string state = 12; 1279 | optional string phone = 13; 1280 | } 1281 | message ExternalPaypalInstrumentProto { 1282 | optional string instrumentKey = 1; 1283 | optional string preapprovalKey = 2; 1284 | optional string paypalEmail = 3; 1285 | optional AddressProto paypalAddress = 4; 1286 | optional bool multiplePaypalInstrumentsSupported = 5; 1287 | } 1288 | message FileMetadataProto { 1289 | optional int32 fileType = 1; 1290 | optional int32 versionCode = 2; 1291 | optional int64 size = 3; 1292 | optional string downloadUrl = 4; 1293 | } 1294 | message GetAddressSnippetRequestProto { 1295 | optional EncryptedSubscriberInfo encryptedSubscriberInfo = 1; 1296 | } 1297 | message GetAddressSnippetResponseProto { 1298 | optional string addressSnippet = 1; 1299 | } 1300 | message GetAssetRequestProto { 1301 | optional string assetId = 1; 1302 | optional string directDownloadKey = 2; 1303 | } 1304 | message GetAssetResponseProto { 1305 | optional group InstallAsset = 1 { 1306 | optional string assetId = 2; 1307 | optional string assetName = 3; 1308 | optional string assetType = 4; 1309 | optional string assetPackage = 5; 1310 | optional string blobUrl = 6; 1311 | optional string assetSignature = 7; 1312 | optional int64 assetSize = 8; 1313 | optional int64 refundTimeoutMillis = 9; 1314 | optional bool forwardLocked = 10; 1315 | optional bool secured = 11; 1316 | optional int32 versionCode = 12; 1317 | optional string downloadAuthCookieName = 13; 1318 | optional string downloadAuthCookieValue = 14; 1319 | optional int64 postInstallRefundWindowMillis = 16; 1320 | } 1321 | repeated FileMetadataProto additionalFile = 15; 1322 | } 1323 | message GetCarrierInfoRequestProto { 1324 | } 1325 | message GetCarrierInfoResponseProto { 1326 | optional bool carrierChannelEnabled = 1; 1327 | optional bytes carrierLogoIcon = 2; 1328 | optional bytes carrierBanner = 3; 1329 | optional string carrierSubtitle = 4; 1330 | optional string carrierTitle = 5; 1331 | optional int32 carrierImageDensity = 6; 1332 | } 1333 | message GetCategoriesRequestProto { 1334 | optional bool prefetchPromoData = 1; 1335 | } 1336 | message GetCategoriesResponseProto { 1337 | repeated CategoryProto categories = 1; 1338 | } 1339 | message GetImageRequestProto { 1340 | optional string assetId = 1; 1341 | optional int32 imageUsage = 3; 1342 | optional string imageId = 4; 1343 | optional int32 screenPropertyWidth = 5; 1344 | optional int32 screenPropertyHeight = 6; 1345 | optional int32 screenPropertyDensity = 7; 1346 | optional int32 productType = 8; 1347 | } 1348 | message GetImageResponseProto { 1349 | optional bytes imageData = 1; 1350 | optional int32 imageDensity = 2; 1351 | } 1352 | message GetMarketMetadataRequestProto { 1353 | optional int64 lastRequestTime = 1; 1354 | optional DeviceConfigurationProto deviceConfiguration = 2; 1355 | optional bool deviceRoaming = 3; 1356 | repeated string marketSignatureHash = 4; 1357 | optional int32 contentRating = 5; 1358 | optional string deviceModelName = 6; 1359 | optional string deviceManufacturerName = 7; 1360 | } 1361 | message GetMarketMetadataResponseProto { 1362 | optional int32 latestClientVersionCode = 1; 1363 | optional string latestClientUrl = 2; 1364 | optional bool paidAppsEnabled = 3; 1365 | repeated BillingParameterProto billingParameter = 4; 1366 | optional bool commentPostEnabled = 5; 1367 | optional bool billingEventsEnabled = 6; 1368 | optional string warningMessage = 7; 1369 | optional bool inAppBillingEnabled = 8; 1370 | optional int32 inAppBillingMaxApiVersion = 9; 1371 | } 1372 | message GetSubCategoriesRequestProto { 1373 | optional int32 assetType = 1; 1374 | } 1375 | message GetSubCategoriesResponseProto { 1376 | repeated group SubCategory = 1 { 1377 | optional string subCategoryDisplay = 2; 1378 | optional string subCategoryId = 3; 1379 | } 1380 | } 1381 | message InAppPurchaseInformationRequestProto { 1382 | optional SignatureHashProto signatureHash = 1; 1383 | optional int64 nonce = 2; 1384 | repeated string notificationId = 3; 1385 | optional string signatureAlgorithm = 4; 1386 | optional int32 billingApiVersion = 5; 1387 | } 1388 | message InAppPurchaseInformationResponseProto { 1389 | optional SignedDataProto signedResponse = 1; 1390 | repeated StatusBarNotificationProto statusBarNotification = 2; 1391 | optional PurchaseResultProto purchaseResult = 3; 1392 | } 1393 | message InAppRestoreTransactionsRequestProto { 1394 | optional SignatureHashProto signatureHash = 1; 1395 | optional int64 nonce = 2; 1396 | optional string signatureAlgorithm = 3; 1397 | optional int32 billingApiVersion = 4; 1398 | } 1399 | message InAppRestoreTransactionsResponseProto { 1400 | optional SignedDataProto signedResponse = 1; 1401 | optional PurchaseResultProto purchaseResult = 2; 1402 | } 1403 | /* 1404 | message InputValidationError { 1405 | optional int32 inputField = 1; 1406 | optional string errorMessage = 2; 1407 | } 1408 | */ 1409 | message ModifyCommentRequestProto { 1410 | optional string assetId = 1; 1411 | optional ExternalCommentProto comment = 2; 1412 | optional bool deleteComment = 3; 1413 | optional bool flagAsset = 4; 1414 | optional int32 flagType = 5; 1415 | optional string flagMessage = 6; 1416 | optional bool nonFlagFlow = 7; 1417 | } 1418 | message ModifyCommentResponseProto { 1419 | } 1420 | message PaypalCountryInfoProto { 1421 | optional bool birthDateRequired = 1; 1422 | optional string tosText = 2; 1423 | optional string billingAgreementText = 3; 1424 | optional string preTosText = 4; 1425 | } 1426 | message PaypalCreateAccountRequestProto { 1427 | optional string firstName = 1; 1428 | optional string lastName = 2; 1429 | optional AddressProto address = 3; 1430 | optional string birthDate = 4; 1431 | } 1432 | message PaypalCreateAccountResponseProto { 1433 | optional string createAccountKey = 1; 1434 | } 1435 | message PaypalCredentialsProto { 1436 | optional string preapprovalKey = 1; 1437 | optional string paypalEmail = 2; 1438 | } 1439 | message PaypalMassageAddressRequestProto { 1440 | optional AddressProto address = 1; 1441 | } 1442 | message PaypalMassageAddressResponseProto { 1443 | optional AddressProto address = 1; 1444 | } 1445 | message PaypalPreapprovalCredentialsRequestProto { 1446 | optional string gaiaAuthToken = 1; 1447 | optional string billingInstrumentId = 2; 1448 | } 1449 | message PaypalPreapprovalCredentialsResponseProto { 1450 | optional int32 resultCode = 1; 1451 | optional string paypalAccountKey = 2; 1452 | optional string paypalEmail = 3; 1453 | } 1454 | message PaypalPreapprovalDetailsRequestProto { 1455 | optional bool getAddress = 1; 1456 | optional string preapprovalKey = 2; 1457 | } 1458 | message PaypalPreapprovalDetailsResponseProto { 1459 | optional string paypalEmail = 1; 1460 | optional AddressProto address = 2; 1461 | } 1462 | message PaypalPreapprovalRequestProto { 1463 | } 1464 | message PaypalPreapprovalResponseProto { 1465 | optional string preapprovalKey = 1; 1466 | } 1467 | message PendingNotificationsProto { 1468 | repeated DataMessageProto notification = 1; 1469 | optional int64 nextCheckMillis = 2; 1470 | } 1471 | message PrefetchedBundleProto { 1472 | optional SingleRequestProto request = 1; 1473 | optional SingleResponseProto response = 2; 1474 | } 1475 | message PurchaseCartInfoProto { 1476 | optional string itemPrice = 1; 1477 | optional string taxInclusive = 2; 1478 | optional string taxExclusive = 3; 1479 | optional string total = 4; 1480 | optional string taxMessage = 5; 1481 | optional string footerMessage = 6; 1482 | optional string priceCurrency = 7; 1483 | optional int64 priceMicros = 8; 1484 | } 1485 | message PurchaseInfoProto { 1486 | optional string transactionId = 1; 1487 | optional PurchaseCartInfoProto cartInfo = 2; 1488 | optional group BillingInstruments = 3 { 1489 | repeated group BillingInstrument = 4 { 1490 | optional string id = 5; 1491 | optional string name = 6; 1492 | optional bool isInvalid = 7; 1493 | optional int32 instrumentType = 11; 1494 | optional int32 instrumentStatus = 14; 1495 | } 1496 | optional string defaultBillingInstrumentId = 8; 1497 | } 1498 | repeated int32 errorInputFields = 9; 1499 | optional string refundPolicy = 10; 1500 | optional bool userCanAddGdd = 12; 1501 | repeated int32 eligibleInstrumentTypes = 13; 1502 | optional string orderId = 15; 1503 | } 1504 | message PurchaseMetadataRequestProto { 1505 | optional bool deprecatedRetrieveBillingCountries = 1; 1506 | optional int32 billingInstrumentType = 2; 1507 | } 1508 | message PurchaseMetadataResponseProto { 1509 | optional group Countries = 1 { 1510 | repeated group Country = 2 { 1511 | optional string countryCode = 3; 1512 | optional string countryName = 4; 1513 | optional PaypalCountryInfoProto paypalCountryInfo = 5; 1514 | optional bool allowsReducedBillingAddress = 6; 1515 | repeated group InstrumentAddressSpec = 7 { 1516 | optional int32 instrumentFamily = 8; 1517 | optional BillingAddressSpec billingAddressSpec = 9; 1518 | } 1519 | } 1520 | } 1521 | } 1522 | message PurchaseOrderRequestProto { 1523 | optional string gaiaAuthToken = 1; 1524 | optional string assetId = 2; 1525 | optional string transactionId = 3; 1526 | optional string billingInstrumentId = 4; 1527 | optional bool tosAccepted = 5; 1528 | optional CarrierBillingCredentialsProto carrierBillingCredentials = 6; 1529 | optional string existingOrderId = 7; 1530 | optional int32 billingInstrumentType = 8; 1531 | optional string billingParametersId = 9; 1532 | optional PaypalCredentialsProto paypalCredentials = 10; 1533 | optional RiskHeaderInfoProto riskHeaderInfo = 11; 1534 | optional int32 productType = 12; 1535 | optional SignatureHashProto signatureHash = 13; 1536 | optional string developerPayload = 14; 1537 | } 1538 | message PurchaseOrderResponseProto { 1539 | optional int32 deprecatedResultCode = 1; 1540 | optional PurchaseInfoProto purchaseInfo = 2; 1541 | optional ExternalAssetProto asset = 3; 1542 | optional PurchaseResultProto purchaseResult = 4; 1543 | } 1544 | message PurchasePostRequestProto { 1545 | optional string gaiaAuthToken = 1; 1546 | optional string assetId = 2; 1547 | optional string transactionId = 3; 1548 | optional group BillingInstrumentInfo = 4 { 1549 | optional string billingInstrumentId = 5; 1550 | optional ExternalCreditCard creditCard = 6; 1551 | optional ExternalCarrierBillingInstrumentProto carrierInstrument = 9; 1552 | optional ExternalPaypalInstrumentProto paypalInstrument = 10; 1553 | } 1554 | optional bool tosAccepted = 7; 1555 | optional string cbInstrumentKey = 8; 1556 | optional bool paypalAuthConfirmed = 11; 1557 | optional int32 productType = 12; 1558 | optional SignatureHashProto signatureHash = 13; 1559 | } 1560 | message PurchasePostResponseProto { 1561 | optional int32 deprecatedResultCode = 1; 1562 | optional PurchaseInfoProto purchaseInfo = 2; 1563 | optional string termsOfServiceUrl = 3; 1564 | optional string termsOfServiceText = 4; 1565 | optional string termsOfServiceName = 5; 1566 | optional string termsOfServiceCheckboxText = 6; 1567 | optional string termsOfServiceHeaderText = 7; 1568 | optional PurchaseResultProto purchaseResult = 8; 1569 | } 1570 | message PurchaseProductRequestProto { 1571 | optional int32 productType = 1; 1572 | optional string productId = 2; 1573 | optional SignatureHashProto signatureHash = 3; 1574 | } 1575 | message PurchaseProductResponseProto { 1576 | optional string title = 1; 1577 | optional string itemTitle = 2; 1578 | optional string itemDescription = 3; 1579 | optional string merchantField = 4; 1580 | } 1581 | message PurchaseResultProto { 1582 | optional int32 resultCode = 1; 1583 | optional string resultCodeMessage = 2; 1584 | } 1585 | message QuerySuggestionProto { 1586 | optional string query = 1; 1587 | optional int32 estimatedNumResults = 2; 1588 | optional int32 queryWeight = 3; 1589 | } 1590 | message QuerySuggestionRequestProto { 1591 | optional string query = 1; 1592 | optional int32 requestType = 2; 1593 | } 1594 | message QuerySuggestionResponseProto { 1595 | repeated group Suggestion = 1 { 1596 | optional AppSuggestionProto appSuggestion = 2; 1597 | optional QuerySuggestionProto querySuggestion = 3; 1598 | } 1599 | optional int32 estimatedNumAppSuggestions = 4; 1600 | optional int32 estimatedNumQuerySuggestions = 5; 1601 | } 1602 | message RateCommentRequestProto { 1603 | optional string assetId = 1; 1604 | optional string creatorId = 2; 1605 | optional int32 commentRating = 3; 1606 | } 1607 | message RateCommentResponseProto { 1608 | } 1609 | message ReconstructDatabaseRequestProto { 1610 | optional bool retrieveFullHistory = 1; 1611 | } 1612 | message ReconstructDatabaseResponseProto { 1613 | repeated AssetIdentifierProto asset = 1; 1614 | } 1615 | message RefundRequestProto { 1616 | optional string assetId = 1; 1617 | } 1618 | message RefundResponseProto { 1619 | optional int32 result = 1; 1620 | optional ExternalAssetProto asset = 2; 1621 | optional string resultDetail = 3; 1622 | } 1623 | message RemoveAssetRequestProto { 1624 | optional string assetId = 1; 1625 | } 1626 | message RequestPropertiesProto { 1627 | optional string userAuthToken = 1; 1628 | optional bool userAuthTokenSecure = 2; 1629 | optional int32 softwareVersion = 3; 1630 | optional string aid = 4; 1631 | optional string productNameAndVersion = 5; 1632 | optional string userLanguage = 6; 1633 | optional string userCountry = 7; 1634 | optional string operatorName = 8; 1635 | optional string simOperatorName = 9; 1636 | optional string operatorNumericName = 10; 1637 | optional string simOperatorNumericName = 11; 1638 | optional string clientId = 12; 1639 | optional string loggingId = 13; 1640 | } 1641 | message RequestProto { 1642 | optional RequestPropertiesProto requestProperties = 1; 1643 | repeated group Request = 2 { 1644 | optional RequestSpecificPropertiesProto requestSpecificProperties = 3; 1645 | optional AssetsRequestProto assetRequest = 4; 1646 | optional CommentsRequestProto commentsRequest = 5; 1647 | optional ModifyCommentRequestProto modifyCommentRequest = 6; 1648 | optional PurchasePostRequestProto purchasePostRequest = 7; 1649 | optional PurchaseOrderRequestProto purchaseOrderRequest = 8; 1650 | optional ContentSyncRequestProto contentSyncRequest = 9; 1651 | optional GetAssetRequestProto getAssetRequest = 10; 1652 | optional GetImageRequestProto getImageRequest = 11; 1653 | optional RefundRequestProto refundRequest = 12; 1654 | optional PurchaseMetadataRequestProto purchaseMetadataRequest = 13; 1655 | optional GetSubCategoriesRequestProto subCategoriesRequest = 14; 1656 | optional UninstallReasonRequestProto uninstallReasonRequest = 16; 1657 | optional RateCommentRequestProto rateCommentRequest = 17; 1658 | optional CheckLicenseRequestProto checkLicenseRequest = 18; 1659 | optional GetMarketMetadataRequestProto getMarketMetadataRequest = 19; 1660 | optional GetCategoriesRequestProto getCategoriesRequest = 21; 1661 | optional GetCarrierInfoRequestProto getCarrierInfoRequest = 22; 1662 | optional RemoveAssetRequestProto removeAssetRequest = 23; 1663 | optional RestoreApplicationsRequestProto restoreApplicationsRequest = 24; 1664 | optional QuerySuggestionRequestProto querySuggestionRequest = 25; 1665 | optional BillingEventRequestProto billingEventRequest = 26; 1666 | optional PaypalPreapprovalRequestProto paypalPreapprovalRequest = 27; 1667 | optional PaypalPreapprovalDetailsRequestProto paypalPreapprovalDetailsRequest = 28; 1668 | optional PaypalCreateAccountRequestProto paypalCreateAccountRequest = 29; 1669 | optional PaypalPreapprovalCredentialsRequestProto paypalPreapprovalCredentialsRequest = 30; 1670 | optional InAppRestoreTransactionsRequestProto inAppRestoreTransactionsRequest = 31; 1671 | optional InAppPurchaseInformationRequestProto inAppPurchaseInformationRequest = 32; 1672 | optional CheckForNotificationsRequestProto checkForNotificationsRequest = 33; 1673 | optional AckNotificationsRequestProto ackNotificationsRequest = 34; 1674 | optional PurchaseProductRequestProto purchaseProductRequest = 35; 1675 | optional ReconstructDatabaseRequestProto reconstructDatabaseRequest = 36; 1676 | optional PaypalMassageAddressRequestProto paypalMassageAddressRequest = 37; 1677 | optional GetAddressSnippetRequestProto getAddressSnippetRequest = 38; 1678 | } 1679 | } 1680 | message RequestSpecificPropertiesProto { 1681 | optional string ifNoneMatch = 1; 1682 | } 1683 | message ResponsePropertiesProto { 1684 | optional int32 result = 1; 1685 | optional int32 maxAge = 2; 1686 | optional string etag = 3; 1687 | optional int32 serverVersion = 4; 1688 | optional int32 maxAgeConsumable = 6; 1689 | optional string errorMessage = 7; 1690 | repeated InputValidationError errorInputField = 8; 1691 | } 1692 | message ResponseProto { 1693 | repeated group Response = 1 { 1694 | optional ResponsePropertiesProto responseProperties = 2; 1695 | optional AssetsResponseProto assetsResponse = 3; 1696 | optional CommentsResponseProto commentsResponse = 4; 1697 | optional ModifyCommentResponseProto modifyCommentResponse = 5; 1698 | optional PurchasePostResponseProto purchasePostResponse = 6; 1699 | optional PurchaseOrderResponseProto purchaseOrderResponse = 7; 1700 | optional ContentSyncResponseProto contentSyncResponse = 8; 1701 | optional GetAssetResponseProto getAssetResponse = 9; 1702 | optional GetImageResponseProto getImageResponse = 10; 1703 | optional RefundResponseProto refundResponse = 11; 1704 | optional PurchaseMetadataResponseProto purchaseMetadataResponse = 12; 1705 | optional GetSubCategoriesResponseProto subCategoriesResponse = 13; 1706 | optional UninstallReasonResponseProto uninstallReasonResponse = 15; 1707 | optional RateCommentResponseProto rateCommentResponse = 16; 1708 | optional CheckLicenseResponseProto checkLicenseResponse = 17; 1709 | optional GetMarketMetadataResponseProto getMarketMetadataResponse = 18; 1710 | repeated PrefetchedBundleProto prefetchedBundle = 19; 1711 | optional GetCategoriesResponseProto getCategoriesResponse = 20; 1712 | optional GetCarrierInfoResponseProto getCarrierInfoResponse = 21; 1713 | optional RestoreApplicationsResponseProto restoreApplicationResponse = 23; 1714 | optional QuerySuggestionResponseProto querySuggestionResponse = 24; 1715 | optional BillingEventResponseProto billingEventResponse = 25; 1716 | optional PaypalPreapprovalResponseProto paypalPreapprovalResponse = 26; 1717 | optional PaypalPreapprovalDetailsResponseProto paypalPreapprovalDetailsResponse = 27; 1718 | optional PaypalCreateAccountResponseProto paypalCreateAccountResponse = 28; 1719 | optional PaypalPreapprovalCredentialsResponseProto paypalPreapprovalCredentialsResponse = 29; 1720 | optional InAppRestoreTransactionsResponseProto inAppRestoreTransactionsResponse = 30; 1721 | optional InAppPurchaseInformationResponseProto inAppPurchaseInformationResponse = 31; 1722 | optional CheckForNotificationsResponseProto checkForNotificationsResponse = 32; 1723 | optional AckNotificationsResponseProto ackNotificationsResponse = 33; 1724 | optional PurchaseProductResponseProto purchaseProductResponse = 34; 1725 | optional ReconstructDatabaseResponseProto reconstructDatabaseResponse = 35; 1726 | optional PaypalMassageAddressResponseProto paypalMassageAddressResponse = 36; 1727 | optional GetAddressSnippetResponseProto getAddressSnippetResponse = 37; 1728 | } 1729 | optional PendingNotificationsProto pendingNotifications = 38; 1730 | } 1731 | message RestoreApplicationsRequestProto { 1732 | optional string backupAndroidId = 1; 1733 | optional string tosVersion = 2; 1734 | optional DeviceConfigurationProto deviceConfiguration = 3; 1735 | } 1736 | message RestoreApplicationsResponseProto { 1737 | repeated GetAssetResponseProto asset = 1; 1738 | } 1739 | message RiskHeaderInfoProto { 1740 | optional string hashedDeviceInfo = 1; 1741 | } 1742 | message SignatureHashProto { 1743 | optional string packageName = 1; 1744 | optional int32 versionCode = 2; 1745 | optional bytes hash = 3; 1746 | } 1747 | message SignedDataProto { 1748 | optional string signedData = 1; 1749 | optional string signature = 2; 1750 | } 1751 | message SingleRequestProto { 1752 | optional RequestSpecificPropertiesProto requestSpecificProperties = 3; 1753 | optional AssetsRequestProto assetRequest = 4; 1754 | optional CommentsRequestProto commentsRequest = 5; 1755 | optional ModifyCommentRequestProto modifyCommentRequest = 6; 1756 | optional PurchasePostRequestProto purchasePostRequest = 7; 1757 | optional PurchaseOrderRequestProto purchaseOrderRequest = 8; 1758 | optional ContentSyncRequestProto contentSyncRequest = 9; 1759 | optional GetAssetRequestProto getAssetRequest = 10; 1760 | optional GetImageRequestProto getImageRequest = 11; 1761 | optional RefundRequestProto refundRequest = 12; 1762 | optional PurchaseMetadataRequestProto purchaseMetadataRequest = 13; 1763 | optional GetSubCategoriesRequestProto subCategoriesRequest = 14; 1764 | optional UninstallReasonRequestProto uninstallReasonRequest = 16; 1765 | optional RateCommentRequestProto rateCommentRequest = 17; 1766 | optional CheckLicenseRequestProto checkLicenseRequest = 18; 1767 | optional GetMarketMetadataRequestProto getMarketMetadataRequest = 19; 1768 | optional GetCategoriesRequestProto getCategoriesRequest = 21; 1769 | optional GetCarrierInfoRequestProto getCarrierInfoRequest = 22; 1770 | optional RemoveAssetRequestProto removeAssetRequest = 23; 1771 | optional RestoreApplicationsRequestProto restoreApplicationsRequest = 24; 1772 | optional QuerySuggestionRequestProto querySuggestionRequest = 25; 1773 | optional BillingEventRequestProto billingEventRequest = 26; 1774 | optional PaypalPreapprovalRequestProto paypalPreapprovalRequest = 27; 1775 | optional PaypalPreapprovalDetailsRequestProto paypalPreapprovalDetailsRequest = 28; 1776 | optional PaypalCreateAccountRequestProto paypalCreateAccountRequest = 29; 1777 | optional PaypalPreapprovalCredentialsRequestProto paypalPreapprovalCredentialsRequest = 30; 1778 | optional InAppRestoreTransactionsRequestProto inAppRestoreTransactionsRequest = 31; 1779 | optional InAppPurchaseInformationRequestProto getInAppPurchaseInformationRequest = 32; 1780 | optional CheckForNotificationsRequestProto checkForNotificationsRequest = 33; 1781 | optional AckNotificationsRequestProto ackNotificationsRequest = 34; 1782 | optional PurchaseProductRequestProto purchaseProductRequest = 35; 1783 | optional ReconstructDatabaseRequestProto reconstructDatabaseRequest = 36; 1784 | optional PaypalMassageAddressRequestProto paypalMassageAddressRequest = 37; 1785 | optional GetAddressSnippetRequestProto getAddressSnippetRequest = 38; 1786 | } 1787 | message SingleResponseProto { 1788 | optional ResponsePropertiesProto responseProperties = 2; 1789 | optional AssetsResponseProto assetsResponse = 3; 1790 | optional CommentsResponseProto commentsResponse = 4; 1791 | optional ModifyCommentResponseProto modifyCommentResponse = 5; 1792 | optional PurchasePostResponseProto purchasePostResponse = 6; 1793 | optional PurchaseOrderResponseProto purchaseOrderResponse = 7; 1794 | optional ContentSyncResponseProto contentSyncResponse = 8; 1795 | optional GetAssetResponseProto getAssetResponse = 9; 1796 | optional GetImageResponseProto getImageResponse = 10; 1797 | optional RefundResponseProto refundResponse = 11; 1798 | optional PurchaseMetadataResponseProto purchaseMetadataResponse = 12; 1799 | optional GetSubCategoriesResponseProto subCategoriesResponse = 13; 1800 | optional UninstallReasonResponseProto uninstallReasonResponse = 15; 1801 | optional RateCommentResponseProto rateCommentResponse = 16; 1802 | optional CheckLicenseResponseProto checkLicenseResponse = 17; 1803 | optional GetMarketMetadataResponseProto getMarketMetadataResponse = 18; 1804 | optional GetCategoriesResponseProto getCategoriesResponse = 20; 1805 | optional GetCarrierInfoResponseProto getCarrierInfoResponse = 21; 1806 | optional RestoreApplicationsResponseProto restoreApplicationResponse = 23; 1807 | optional QuerySuggestionResponseProto querySuggestionResponse = 24; 1808 | optional BillingEventResponseProto billingEventResponse = 25; 1809 | optional PaypalPreapprovalResponseProto paypalPreapprovalResponse = 26; 1810 | optional PaypalPreapprovalDetailsResponseProto paypalPreapprovalDetailsResponse = 27; 1811 | optional PaypalCreateAccountResponseProto paypalCreateAccountResponse = 28; 1812 | optional PaypalPreapprovalCredentialsResponseProto paypalPreapprovalCredentialsResponse = 29; 1813 | optional InAppRestoreTransactionsResponseProto inAppRestoreTransactionsResponse = 30; 1814 | optional InAppPurchaseInformationResponseProto getInAppPurchaseInformationResponse = 31; 1815 | optional CheckForNotificationsResponseProto checkForNotificationsResponse = 32; 1816 | optional AckNotificationsResponseProto ackNotificationsResponse = 33; 1817 | optional PurchaseProductResponseProto purchaseProductResponse = 34; 1818 | optional ReconstructDatabaseResponseProto reconstructDatabaseResponse = 35; 1819 | optional PaypalMassageAddressResponseProto paypalMassageAddressResponse = 36; 1820 | optional GetAddressSnippetResponseProto getAddressSnippetResponse = 37; 1821 | } 1822 | message StatusBarNotificationProto { 1823 | optional string tickerText = 1; 1824 | optional string contentTitle = 2; 1825 | optional string contentText = 3; 1826 | } 1827 | message UninstallReasonRequestProto { 1828 | optional string assetId = 1; 1829 | optional int32 reason = 2; 1830 | } 1831 | message UninstallReasonResponseProto { 1832 | } 1833 | -------------------------------------------------------------------------------- /googleplay_api/googleplay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 4 | 5 | from __future__ import absolute_import 6 | from __future__ import division 7 | from __future__ import print_function 8 | from __future__ import unicode_literals 9 | 10 | import os 11 | import io 12 | import logging 13 | import base64 14 | import gzip 15 | import requests 16 | 17 | from google.protobuf import descriptor 18 | from google.protobuf.internal.containers import RepeatedCompositeFieldContainer 19 | from google.protobuf import text_format 20 | from google.protobuf.message import Message, DecodeError 21 | 22 | from googleplay_api import googleplay_pb2 23 | 24 | class LoginError(Exception): 25 | def __init__(self, value): 26 | self.value = value 27 | def __str__(self): 28 | return repr(self.value) 29 | 30 | class RequestError(Exception): 31 | def __init__(self, value): 32 | self.value = value 33 | def __str__(self): 34 | return repr(self.value) 35 | 36 | config = None 37 | 38 | class GooglePlayAPI(object): 39 | """Google Play Unofficial API Class 40 | 41 | Usual APIs methods are login(), search(), details(), bulkDetails(), 42 | download(), browse(), reviews() and list(). 43 | 44 | toStr() can be used to pretty print the result (protobuf object) of the 45 | previous methods. 46 | 47 | toDict() converts the result into a dict, for easier introspection.""" 48 | 49 | SERVICE = "androidmarket" 50 | URL_LOGIN = "https://android.clients.google.com/auth" # "https://www.google.com/accounts/ClientLogin" 51 | ACCOUNT_TYPE_GOOGLE = "GOOGLE" 52 | ACCOUNT_TYPE_HOSTED = "HOSTED" 53 | ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE" 54 | authSubToken = None 55 | # HTTP_PROXY = "http://81.137.100.158" 56 | 57 | 58 | def __init__(self, androidId=None, lang=None, debug=False): # you must use a device-associated androidId value 59 | self.preFetch = {} 60 | #if androidId == None: 61 | # androidId = config.ANDROID_ID 62 | #if lang == None: 63 | # lang = config.LANG 64 | self.androidId = androidId 65 | self.lang = lang 66 | self.debug = debug 67 | # self.proxy_dict = { 68 | # "http" : "http://81.137.100.158:8080", 69 | # "https" : "http://81.137.100.158:8080", 70 | # "ftp" : "http://81.137.100.158:8080" 71 | # } 72 | 73 | @staticmethod 74 | def read_config(config_file='config.py'): 75 | """ 76 | Read the repository config 77 | 78 | The config is read from config_file, which is in the current directory. 79 | """ 80 | global config 81 | 82 | if config is not None: 83 | return config 84 | if not os.path.isfile(config_file): 85 | logging.critical("Missing config file.") 86 | sys.exit(2) 87 | 88 | config = dict() 89 | 90 | logging.debug("Reading %s" % config_file) 91 | with io.open("config.py", "rb") as f: 92 | code = compile(f.read(), "config.py", 'exec') 93 | exec(code, None, config) 94 | 95 | return config 96 | 97 | def toDict(self, protoObj): 98 | """Converts the (protobuf) result from an API call into a dict, for 99 | easier introspection.""" 100 | iterable = False 101 | if isinstance(protoObj, RepeatedCompositeFieldContainer): 102 | iterable = True 103 | else: 104 | protoObj = [protoObj] 105 | retlist = [] 106 | 107 | for po in protoObj: 108 | msg = dict() 109 | for fielddesc, value in po.ListFields(): 110 | #print value, type(value), getattr(value, "__iter__", False) 111 | if fielddesc.type == descriptor.FieldDescriptor.TYPE_GROUP or isinstance(value, RepeatedCompositeFieldContainer) or isinstance(value, Message): 112 | msg[fielddesc.name] = self.toDict(value) 113 | else: 114 | msg[fielddesc.name] = value 115 | retlist.append(msg) 116 | if not iterable: 117 | if len(retlist) > 0: 118 | return retlist[0] 119 | else: 120 | return None 121 | return retlist 122 | 123 | def toStr(self, protoObj): 124 | """Used for pretty printing a result from the API.""" 125 | return text_format.MessageToString(protoObj) 126 | 127 | def _try_register_preFetch(self, protoObj): 128 | fields = [i.name for (i,_) in protoObj.ListFields()] 129 | if ("preFetch" in fields): 130 | for p in protoObj.preFetch: 131 | self.preFetch[p.url] = p.response 132 | 133 | def setAuthSubToken(self, authSubToken): 134 | self.authSubToken = authSubToken 135 | 136 | # put your auth token in config.py to avoid multiple logins 137 | if self.debug: 138 | print("authSubToken: %s" % authSubToken) 139 | 140 | def login(self, email=None, password=None, authSubToken=None, proxy=None): 141 | """Login to your Google Account. You must provide either: 142 | - an email and password 143 | - a valid Google authSubToken""" 144 | if (authSubToken is not None): 145 | self.setAuthSubToken(authSubToken) 146 | self.proxy_dict = proxy 147 | # TODO: not really implemented yet 148 | else: 149 | if (email is None or password is None): 150 | raise Exception("You should provide at least authSubToken or (email and password)") 151 | params = {"Email": email, 152 | "Passwd": password, 153 | "service": self.SERVICE, 154 | "accountType": self.ACCOUNT_TYPE_HOSTED_OR_GOOGLE, 155 | "has_permission": "1", 156 | "source": "android", 157 | "androidId": self.androidId, 158 | "app": "com.android.vending", 159 | #"client_sig": self.client_sig, 160 | "device_country": "us", 161 | "operatorCountry": "us", 162 | "lang": "us", 163 | "sdk_version": "22"} 164 | headers = { 165 | "Accept-Encoding": "", 166 | } 167 | self.proxy_dict = proxy 168 | response = requests.post(self.URL_LOGIN, data=params, headers=headers, proxies=proxy, verify=True) 169 | data = response.text.split() 170 | params = {} 171 | for d in data: 172 | if not "=" in d: continue 173 | k, v = d.split("=") 174 | params[k.strip().lower()] = v.strip() 175 | if "auth" in params: 176 | #print("Auth-Token found: %s" % params["auth"]) 177 | self.setAuthSubToken(params["auth"]) 178 | elif "error" in params: 179 | raise LoginError("server says: " + params["error"]) 180 | else: 181 | raise LoginError("Auth token not found.") 182 | 183 | def executeRequestApi2(self, path, datapost=None, post_content_type="application/x-www-form-urlencoded; charset=UTF-8"): 184 | if (datapost is None and path in self.preFetch): 185 | data = self.preFetch[path] 186 | else: 187 | headers = { "Accept-Language": self.lang, 188 | "Authorization": "GoogleLogin auth=%s" % self.authSubToken, 189 | "X-DFE-Enabled-Experiments": "cl:billing.select_add_instrument_by_default", 190 | "X-DFE-Unsupported-Experiments": "nocache:billing.use_charging_poller,market_emails,buyer_currency,prod_baseline,checkin.set_asset_paid_app_field,shekel_test,content_ratings,buyer_currency_in_app,nocache:encrypted_apk,recent_changes", 191 | "X-DFE-Device-Id": self.androidId, 192 | "X-DFE-Client-Id": "am-android-google", 193 | #"X-DFE-Logging-Id": self.loggingId2, # Deprecated? 194 | # "User-Agent": "Android-Finsky/4.4.3 (api=3,versionCode=8016014,sdk=22,device=GT-I9300,hardware=aries,product=GT-I9300)", 195 | "User-Agent": "Android-Finsky/4.4.3 (api=3,versionCode=8016014,sdk=22,device=hammerhead,hardware=hammerhead,product=hammerhead)", 196 | # "User-Agent": "Android-Finsky/3.7.13 (api=3,versionCode=8013013,sdk=22,device=crespo,hardware=herring,product=soju)", 197 | "X-DFE-SmallestScreenWidthDp": "335", 198 | "X-DFE-Filter-Level": "3", 199 | "Accept-Encoding": "", 200 | "Host": "android.clients.google.com"} 201 | 202 | if datapost is not None: 203 | headers["Content-Type"] = post_content_type 204 | 205 | url = "https://android.clients.google.com/fdfe/%s" % path 206 | if datapost is not None: 207 | response = requests.post(url, data=datapost, headers=headers, proxies=self.proxy_dict, verify=True) 208 | else: 209 | response = requests.get(url, headers=headers, proxies=self.proxy_dict, verify=True) 210 | data = response.content 211 | #print(data) 212 | ''' 213 | data = StringIO.StringIO(data) 214 | gzipper = gzip.GzipFile(fileobj=data) 215 | data = gzipper.read() 216 | ''' 217 | message = googleplay_pb2.ResponseWrapper.FromString(data) 218 | self._try_register_preFetch(message) 219 | 220 | # Debug 221 | #print text_format.MessageToString(message) 222 | return message 223 | 224 | ##################################### 225 | # Google Play API Methods 226 | ##################################### 227 | 228 | def search(self, query, nb_results=None, offset=None): 229 | """Search for apps.""" 230 | path = "search?c=3&q=%s" % requests.utils.quote(query) # TODO handle categories 231 | if (nb_results is not None): 232 | path += "&n=%d" % int(nb_results) 233 | if (offset is not None): 234 | path += "&o=%d" % int(offset) 235 | 236 | message = self.executeRequestApi2(path) 237 | return message.payload.searchResponse 238 | 239 | def details(self, packageName): 240 | """Get app details from a package name. 241 | packageName is the app unique ID (usually starting with 'com.').""" 242 | path = "details?doc=%s" % requests.utils.quote(packageName) 243 | message = self.executeRequestApi2(path) 244 | return message.payload.detailsResponse 245 | 246 | def bulkDetails(self, packageNames): 247 | """Get several apps details from a list of package names. 248 | 249 | This is much more efficient than calling N times details() since it 250 | requires only one request. 251 | 252 | packageNames is a list of app ID (usually starting with 'com.').""" 253 | path = "bulkDetails" 254 | req = googleplay_pb2.BulkDetailsRequest() 255 | req.docid.extend(packageNames) 256 | data = req.SerializeToString() 257 | message = self.executeRequestApi2(path, data, "application/x-protobuf") 258 | return message.payload.bulkDetailsResponse 259 | 260 | def browse(self, cat=None, ctr=None): 261 | """Browse categories. 262 | cat (category ID) and ctr (subcategory ID) are used as filters.""" 263 | path = "browse?c=3" 264 | if (cat != None): 265 | path += "&cat=%s" % requests.utils.quote(cat) 266 | if (ctr != None): 267 | path += "&ctr=%s" % requests.utils.quote(ctr) 268 | message = self.executeRequestApi2(path) 269 | return message.payload.browseResponse 270 | 271 | def list(self, cat, ctr=None, nb_results=None, offset=None): 272 | """List apps. 273 | 274 | If ctr (subcategory ID) is None, returns a list of valid subcategories. 275 | 276 | If ctr is provided, list apps within this subcategory.""" 277 | path = "list?c=3&cat=%s" % requests.utils.quote(cat) 278 | if (ctr != None): 279 | path += "&ctr=%s" % requests.utils.quote(ctr) 280 | if (nb_results != None): 281 | path += "&n=%s" % requests.utils.quote(nb_results) 282 | if (offset != None): 283 | path += "&o=%s" % requests.utils.quote(offset) 284 | message = self.executeRequestApi2(path) 285 | return message.payload.listResponse 286 | 287 | def reviews(self, packageName, filterByDevice=False, sort=2, nb_results=None, offset=None): 288 | """Browse reviews. 289 | packageName is the app unique ID. 290 | If filterByDevice is True, return only reviews for your device.""" 291 | path = "rev?doc=%s&sort=%d" % (requests.utils.quote(packageName), sort) 292 | if (nb_results is not None): 293 | path += "&n=%d" % int(nb_results) 294 | if (offset is not None): 295 | path += "&o=%d" % int(offset) 296 | if(filterByDevice): 297 | path += "&dfil=1" 298 | message = self.executeRequestApi2(path) 299 | return message.payload.reviewResponse 300 | 301 | def download(self, packageName, versionCode, offerType=1): 302 | """Download an app and return its raw data (APK file). 303 | 304 | packageName is the app unique ID (usually starting with 'com.'). 305 | 306 | versionCode can be grabbed by using the details() method on the given 307 | app.""" 308 | path = "purchase" 309 | data = "ot=%d&doc=%s&vc=%d" % (offerType, packageName, versionCode) 310 | message = self.executeRequestApi2(path, data) 311 | 312 | url = message.payload.buyResponse.purchaseStatusResponse.appDeliveryData.downloadUrl 313 | #print(message) 314 | #print(message.payload) 315 | cookie = message.payload.buyResponse.purchaseStatusResponse.appDeliveryData.downloadAuthCookie[0] 316 | 317 | cookies = { 318 | str(cookie.name): str(cookie.value) # python-requests #459 fixes this 319 | } 320 | 321 | headers = { 322 | "User-Agent" : "AndroidDownloadManager/4.1.1 (Linux; U; Android 4.1.1; Nexus S Build/JRO03E)", 323 | "Accept-Encoding": "", 324 | } 325 | 326 | response = requests.get(url, headers=headers, cookies=cookies, proxies=self.proxy_dict, verify=True) 327 | return response.content 328 | 329 | -------------------------------------------------------------------------------- /googleplayupdater/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeroBurner/googleplayupdater/445dd66773bdb6474b3840044389a451272c0f06/googleplayupdater/__init__.py -------------------------------------------------------------------------------- /googleplayupdater/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2015 Neroburner 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 20 | 21 | from googleplayupdater.gp_update import main 22 | main() 23 | -------------------------------------------------------------------------------- /googleplayupdater/asynchronousfilereader/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AsynchronousFileReader 3 | ====================== 4 | 5 | Simple thread based asynchronous file reader for Python. 6 | 7 | see https://github.com/soxofaan/asynchronousfilereader 8 | 9 | MIT License 10 | Copyright (c) 2014 Stefaan Lippens 11 | """ 12 | 13 | __version__ = '0.2.1' 14 | 15 | import threading 16 | try: 17 | # Python 2 18 | from Queue import Queue 19 | except ImportError: 20 | # Python 3 21 | from queue import Queue 22 | 23 | 24 | class AsynchronousFileReader(threading.Thread): 25 | """ 26 | Helper class to implement asynchronous reading of a file 27 | in a separate thread. Pushes read lines on a queue to 28 | be consumed in another thread. 29 | """ 30 | 31 | def __init__(self, fd, queue=None, autostart=True): 32 | self._fd = fd 33 | if queue is None: 34 | queue = Queue() 35 | self.queue = queue 36 | 37 | threading.Thread.__init__(self) 38 | 39 | if autostart: 40 | self.start() 41 | 42 | def run(self): 43 | """ 44 | The body of the tread: read lines and put them on the queue. 45 | """ 46 | while True: 47 | line = self._fd.readline() 48 | if not line: 49 | break 50 | self.queue.put(line) 51 | 52 | def eof(self): 53 | """ 54 | Check whether there is no more content to expect. 55 | """ 56 | return not self.is_alive() and self.queue.empty() 57 | 58 | def readlines(self): 59 | """ 60 | Get currently available lines. 61 | """ 62 | while not self.queue.empty(): 63 | yield self.queue.get() 64 | 65 | -------------------------------------------------------------------------------- /googleplayupdater/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | # common.py - part of the FDroid server tools 5 | # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com 6 | # Copyright (C) 2013-2014 Daniel Martí 7 | # Copyright (C) 2015 Neroburner 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU Affero General Public License as published by 11 | # the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU Affero General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Affero General Public License 20 | # along with this program. If not, see . 21 | 22 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 23 | 24 | from __future__ import absolute_import 25 | from __future__ import division 26 | from __future__ import print_function 27 | from __future__ import unicode_literals 28 | 29 | import io 30 | import os 31 | import sys 32 | import re 33 | import subprocess 34 | import logging 35 | 36 | from googleplayupdater.asynchronousfilereader import AsynchronousFileReader 37 | 38 | config = None 39 | options = None 40 | env = None 41 | 42 | 43 | def str_compat(text): 44 | if sys.version_info[0] >= 3: # python 3 45 | return text 46 | else: # Python 2 47 | return text.encode('utf8', 'replace') 48 | 49 | 50 | # from fdroidserver 51 | def read_config(opts, config_file='config.py'): 52 | """Read the repository config 53 | 54 | The config is read from config_file, which is in the current directory when 55 | any of the repo management commands are used. 56 | """ 57 | global config, options, env 58 | 59 | if config is not None: 60 | return config 61 | if not os.path.isfile(config_file): 62 | logging.critical("Missing config file - is this a repo directory?") 63 | sys.exit(2) 64 | 65 | options = opts 66 | 67 | config = dict() 68 | 69 | logging.debug("Reading %s" % config_file) 70 | with io.open(config_file, "rb") as f: 71 | code = compile(f.read(), config_file, 'exec') 72 | exec(code, None, config) 73 | 74 | # don't overwrite already set configs 75 | default_configs = [('sdk_path','/opt/android-sdk/'), ('build_tools','23')] 76 | for (key,value) in default_configs: 77 | if not (key in config): 78 | config[key] = value 79 | 80 | # There is no standard, so just set up the most common environment 81 | # variables 82 | env = os.environ 83 | orig_path = env['PATH'] 84 | for n in ['ANDROID_HOME', 'ANDROID_SDK']: 85 | env[n] = config['sdk_path'] 86 | 87 | return config 88 | 89 | 90 | class PopenResult: 91 | returncode = None 92 | output = '' 93 | 94 | 95 | def find_sdk_tools_cmd(cmd): 96 | '''find a working path to a tool from the Android SDK''' 97 | 98 | tooldirs = [] 99 | if config is not None and 'sdk_path' in config and os.path.exists(config['sdk_path']): 100 | # try to find a working path to this command, in all the recent possible paths 101 | if 'build_tools' in config: 102 | build_tools = os.path.join(config['sdk_path'], 'build-tools') 103 | # if 'build_tools' was manually set and exists, check only that one 104 | configed_build_tools = os.path.join(build_tools, config['build_tools']) 105 | if os.path.exists(configed_build_tools): 106 | tooldirs.append(configed_build_tools) 107 | else: 108 | # no configed version, so hunt known paths for it 109 | for f in sorted(os.listdir(build_tools), reverse=True): 110 | if os.path.isdir(os.path.join(build_tools, f)): 111 | tooldirs.append(os.path.join(build_tools, f)) 112 | tooldirs.append(build_tools) 113 | sdk_tools = os.path.join(config['sdk_path'], 'tools') 114 | if os.path.exists(sdk_tools): 115 | tooldirs.append(sdk_tools) 116 | tooldirs.append('/usr/bin') 117 | for d in tooldirs: 118 | if os.path.isfile(os.path.join(d, cmd)): 119 | return os.path.join(d, cmd) 120 | # did not find the command, exit with error message 121 | ensure_build_tools_exists(config) 122 | 123 | 124 | def test_sdk_exists(thisconfig): 125 | if 'sdk_path' not in thisconfig: 126 | if 'aapt' in thisconfig and os.path.isfile(thisconfig['aapt']): 127 | return True 128 | else: 129 | logging.error("'sdk_path' not set in config.py!") 130 | return False 131 | if not os.path.exists(thisconfig['sdk_path']): 132 | logging.critical('Android SDK path "' + thisconfig['sdk_path'] + '" does not exist!') 133 | return False 134 | if not os.path.isdir(thisconfig['sdk_path']): 135 | logging.critical('Android SDK path "' + thisconfig['sdk_path'] + '" is not a directory!') 136 | return False 137 | for d in ['build-tools']: 138 | if not os.path.isdir(os.path.join(thisconfig['sdk_path'], d)): 139 | logging.critical('Android SDK path "%s" does not contain "%s/"!' % ( 140 | thisconfig['sdk_path'], d)) 141 | return False 142 | return True 143 | 144 | 145 | def ensure_build_tools_exists(thisconfig): 146 | if not test_sdk_exists(thisconfig): 147 | sys.exit(3) 148 | build_tools = os.path.join(thisconfig['sdk_path'], 'build-tools') 149 | versioned_build_tools = os.path.join(build_tools, thisconfig['build_tools']) 150 | if not os.path.isdir(versioned_build_tools): 151 | logging.critical('Android Build Tools path "' 152 | + versioned_build_tools + '" does not exist!') 153 | sys.exit(3) 154 | 155 | 156 | def SdkToolsPopen(commands, cwd=None, output=True): 157 | cmd = commands[0] 158 | if cmd not in config: 159 | config[cmd] = find_sdk_tools_cmd(commands[0]) 160 | return FDroidPopen([config[cmd]] + commands[1:], 161 | cwd=cwd, output=output) 162 | 163 | 164 | def FDroidPopen(commands, cwd=None, output=True): 165 | """ 166 | Run a command and capture the possibly huge output. 167 | 168 | :param commands: command and argument list like in subprocess.Popen 169 | :param cwd: optionally specifies a working directory 170 | :returns: A PopenResult. 171 | """ 172 | 173 | global env 174 | 175 | if cwd: 176 | cwd = os.path.normpath(cwd) 177 | logging.debug("Directory: %s" % cwd) 178 | logging.debug("> %s" % ' '.join(commands)) 179 | 180 | result = PopenResult() 181 | p = None 182 | try: 183 | p = subprocess.Popen(commands, cwd=cwd, shell=False, env=env, 184 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 185 | except OSError as e: 186 | raise BuildException("OSError while trying to execute " + 187 | ' '.join(commands) + ': ' + str(e)) 188 | 189 | reader = AsynchronousFileReader(p.stdout) 190 | while not reader.eof(): 191 | for line in reader.readlines(): 192 | result.output += line.decode('utf-8') 193 | 194 | reader.join() 195 | 196 | # TODO: why do we need that? 197 | result.returncode = p.wait() 198 | return result 199 | 200 | 201 | # from NeroBurner 202 | def getApkInfo(apkfile): 203 | ''' 204 | Parse information from a given apk-file. 205 | 206 | :param apkfile: path to the apk-file to get the info from 207 | :returns: dict with id, versioncode and version 208 | ''' 209 | thisinfo = dict() 210 | thisinfo['id'] = 'packagename' 211 | thisinfo['versioncode'] = 0 212 | thisinfo['version'] = '0.0.0' 213 | 214 | name_pat = re.compile(".*name='([a-zA-Z0-9._]*)'.*") 215 | vercode_pat = re.compile(".*versionCode='([0-9]*)'.*") 216 | vername_pat = re.compile(".*versionName='([^']*)'.*") 217 | 218 | p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False) 219 | if p.returncode != 0: 220 | # error while executing aapt 221 | logging.error("Failed to get apk information, skipping " + apkfile) 222 | return thisinfo 223 | for line in p.output.splitlines(): 224 | if line.startswith("package:"): 225 | try: 226 | thisinfo['id'] = re.match(name_pat, line).group(1) 227 | thisinfo['versioncode'] = int(re.match(vercode_pat, line).group(1)) 228 | thisinfo['version'] = re.match(vername_pat, line).group(1) 229 | except Exception as e: 230 | logging.error("Package matching failed: " + str(e)) 231 | logging.info("Line was: " + line) 232 | sys.exit(1) 233 | 234 | return thisinfo 235 | -------------------------------------------------------------------------------- /googleplayupdater/gp_update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2015 Neroburner 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 20 | 21 | from __future__ import absolute_import 22 | from __future__ import division 23 | from __future__ import print_function 24 | from __future__ import unicode_literals 25 | 26 | import os 27 | import sys 28 | import logging 29 | import argparse 30 | 31 | from googleplayupdater import common 32 | from googleplay_api.googleplay import GooglePlayAPI # GooglePlayAPI 33 | from googleplay_api.googleplay import LoginError 34 | 35 | 36 | def connect(): 37 | """ 38 | Connect to GooglePlayStore using the googleplay-api 39 | """ 40 | global config 41 | api = GooglePlayAPI(androidId=config['ANDROID_ID'], lang=config['LANG']) 42 | try: 43 | api.login(config['GOOGLE_LOGIN'], config['GOOGLE_PASSWORD'], config['AUTH_TOKEN']) 44 | except LoginError as exc: 45 | logging.error("Connection to PlayStore failed: %s" % exc) 46 | return None 47 | 48 | logging.info("Connection to GooglePlayStore established") 49 | return api 50 | 51 | 52 | def update(playstore_api, apk_folder_path): 53 | """ 54 | Search for updates in the given folder 55 | 56 | :param playstore_api: connected api used to search and download apks 57 | :param apk_folder_path: directory containing apks to update 58 | """ 59 | # search for apks in given folder 60 | list_of_apks = [filename for filename in os.listdir(apk_folder_path) if os.path.splitext(filename)[1] == ".apk"] 61 | if len(list_of_apks) <= 0: 62 | print("No apks found in folder %s" % apk_folder_path) 63 | sys.exit(0) 64 | 65 | # create a list of apks, just keep the newest 66 | apks_to_update = dict() 67 | for filename in list_of_apks: 68 | filepath = os.path.join(apk_folder_path, filename) 69 | 70 | # get packagename and versioncode using aant 71 | apk_info = common.getApkInfo(filepath) 72 | packagename = apk_info['id'] 73 | apk_version_code = apk_info['versioncode'] 74 | 75 | # get packagename and versioncode using androguard 76 | # a = androguard_apk.APK(filepath) 77 | # apk_version_code = int(a.get_androidversion_code()) 78 | # packagename = a.get_package() 79 | 80 | logging.info("Found apk %s : %s : %d" % (filepath, packagename, apk_version_code)) 81 | 82 | if packagename in apks_to_update: 83 | if apks_to_update[packagename] < apk_version_code: 84 | logging.info("Found newer local version %s : %d -> %d" % (packagename, apks_to_update[packagename], apk_version_code)) 85 | apks_to_update[packagename] = apk_version_code 86 | else: 87 | logging.info("Set new local apk %s : %d" % (packagename, apk_version_code)) 88 | apks_to_update[packagename] = apk_version_code 89 | 90 | # are there still apks to check? If not something went wrong 91 | if len(apks_to_update) <= 0: 92 | logging.error("No apks to update after non-empty apk-list. Something went wrong!") 93 | sys.exit(1) 94 | 95 | # search for the apks on googleplaystore 96 | for packagename, version_code in apks_to_update.items(): 97 | local_version_code = int(version_code) 98 | logging.info("Checking apk %s : %d" % (packagename, local_version_code)) 99 | 100 | # get infos of the store-version 101 | m = playstore_api.details(packagename) 102 | doc = m.docV2 103 | store_version_code = int(doc.details.appDetails.versionCode) 104 | 105 | if store_version_code == 0: 106 | logging.warning("Got store_version_code == 0 for package %s : %d" % (packagename, local_version_code)) 107 | continue 108 | 109 | # check if there is an update 110 | if store_version_code > local_version_code: 111 | # download apk from store 112 | print("Updating apk %s : %d -> %d" % (packagename, local_version_code, store_version_code)) 113 | try: 114 | data = playstore_api.download(packagename, store_version_code) 115 | except Exception as exc: 116 | logging.error("failed to download %s : %s" % (packagename, exc)) 117 | continue 118 | else: 119 | # save downloaded apk under '_.apk' 120 | filename = "%s_%d.apk" % (packagename, store_version_code) 121 | filepath = os.path.join(apk_folder_path, filename) 122 | 123 | try: 124 | open(filepath, "wb").write(data) 125 | except IOError as exc: 126 | logging.error("cannot write to disk %s : %s" % (packagename, exc)) 127 | continue 128 | logging.info("Downloaded apk %s : %d to file %s" % (packagename, store_version_code, filename)) 129 | else: 130 | logging.info("No newer apk found.") 131 | 132 | config = None 133 | options = None 134 | 135 | 136 | def main(): 137 | global config, options 138 | 139 | # Parse command line... 140 | parser = argparse.ArgumentParser(description='Fetch updates for local apks from GooglePlayStore') 141 | parser.add_argument('apk_folder_path', 142 | help='absolute or relative path to folder containing the apks to update') 143 | parser.add_argument("-c", "--config_file", nargs='?', default="config.py", 144 | help='configfile to read configs from, default="config.py"') 145 | parser.add_argument("-v", "--verbose", action="store_true", default=False, 146 | help="be more verbose") 147 | 148 | args = parser.parse_args() 149 | 150 | if args.verbose: 151 | logging.basicConfig(level=logging.INFO) 152 | 153 | config = common.read_config(args, args.config_file) 154 | 155 | # get apk_folder_path 156 | if not os.path.isdir(args.apk_folder_path): 157 | logging.error("given is not a directory: %s" % args.apk_folder_path) 158 | sys.exit(1) 159 | 160 | # connect to Google Play Store 161 | playstore_api = connect() 162 | if playstore_api is None: 163 | logging.error("Connection to PlayStore failed. Check provided credencials in config.py") 164 | sys.exit(1) 165 | 166 | # update local apks 167 | update(playstore_api, args.apk_folder_path) 168 | 169 | if __name__ == '__main__': 170 | main() 171 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Always prefer setuptools over distutils 2 | from setuptools import setup 3 | 4 | setup( 5 | name='googleplayupdater', 6 | 7 | # Versions should comply with PEP440. For a discussion on single-sourcing 8 | # the version across setup.py and the project code, see 9 | # https://packaging.python.org/en/latest/single_source_version.html 10 | version='0.1.2', 11 | 12 | description='Bulk-updater for a folder with apk-files from googleplay', 13 | #long_description=long_description, 14 | 15 | # The project's main homepage. 16 | url='https://github.com/NeroBurner/googleplay-api', 17 | 18 | # Author details 19 | author='NeroBurner', 20 | author_email='pyro4hell at gmail dot com', 21 | 22 | # Choose your license 23 | license='AGPLv3+', 24 | 25 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 26 | classifiers=[ 27 | # How mature is this project? Common values are 28 | # 3 - Alpha 29 | # 4 - Beta 30 | # 5 - Production/Stable 31 | 'Development Status :: 3 - Alpha', 32 | 33 | # Indicate who your project is intended for 34 | 'Intended Audience :: Developers', 35 | 36 | # Pick your license as you wish (should match "license" above) 37 | 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)' 38 | 39 | # Specify the Python versions you support here. In particular, ensure 40 | # that you indicate whether you support Python 2, Python 3 or both. 41 | 'Programming Language :: Python :: 2', 42 | 'Programming Language :: Python :: 2.7', 43 | 'Programming Language :: Python :: 3', 44 | 'Programming Language :: Python :: 3.4', 45 | ], 46 | 47 | # What does your project relate to? 48 | keywords='GooglePlayStore apk', 49 | 50 | # You can just specify the packages manually here if your project is 51 | # simple. Or you can use find_packages(). 52 | packages=['googleplayupdater', 'googleplayupdater.asynchronousfilereader', 'googleplay_api'], 53 | 54 | # List run-time dependencies here. These will be installed by pip when 55 | # your project is installed. For an analysis of "install_requires" vs pip's 56 | # requirements files see: 57 | # https://packaging.python.org/en/latest/requirements.html 58 | install_requires=['requests', 'protobuf'], 59 | 60 | # List additional groups of dependencies here (e.g. development 61 | # dependencies). You can install these using the following syntax, 62 | # for example: 63 | # $ pip install -e .[dev,test] 64 | #extras_require={ 65 | # 'dev': ['check-manifest'], 66 | # 'test': ['coverage'], 67 | #}, 68 | 69 | # If there are data files included in your packages that need to be 70 | # installed, specify them here. If using Python 2.6 or less, then these 71 | # have to be included in MANIFEST.in as well. 72 | #package_data={ 73 | # 'sample': ['package_data.dat'], 74 | #}, 75 | 76 | # Although 'package_data' is the preferred approach, in some case you may 77 | # need to place data files outside of your packages. See: 78 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 79 | # In this case, 'data_file' will be installed into '/my_data' 80 | #data_files=[('my_data', ['data/data_file'])], 81 | 82 | # To provide executable scripts, use entry points in preference to the 83 | # "scripts" keyword. Entry points provide cross-platform support and allow 84 | # pip to create the appropriate form of executable for the target platform. 85 | entry_points={ 86 | 'console_scripts': [ 87 | 'googleplayupdater=googleplayupdater.gp_update:main', 88 | ], 89 | }, 90 | ) 91 | 92 | --------------------------------------------------------------------------------