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