├── .travis.yml ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── demo ├── demo │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py └── requirements.txt ├── djgpa ├── __init__.py ├── admin.py ├── android-checkin │ ├── README │ ├── android-checkin.jar │ ├── commons-codec-1.6.jar │ ├── commons-logging-1.1.1.jar │ ├── fluent-hc-4.2.2.jar │ ├── httpclient-4.2.2.jar │ ├── httpclient-cache-4.2.2.jar │ ├── httpcore-4.2.2.jar │ ├── httpmime-4.2.2.jar │ └── protobuf-java-2.4.1.jar ├── api.py ├── configs.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── pb2.py ├── static │ └── djgpa │ │ └── admin │ │ └── js │ │ └── djgpa.js ├── test_cases.py ├── test_settings.py ├── urls.py └── views.py ├── requirements ├── package.txt └── tests.txt └── setup.py /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | before_install: 6 | - sudo apt-get update -qq 7 | - sudo apt-get install -qq python-lxml 8 | - export PIP_USE_MIRRORS=true 9 | - export DJANGO_SETTINGS_MODULE=djgpa.test_settings 10 | install: 11 | - pip install -e . --use-mirrors 12 | - pip install -r requirements/tests.txt Django==$DJANGO --use-mirrors 13 | before_script: 14 | - flake8 djgpa --exclude="migrations,pb2.*" 15 | script: 16 | - coverage run --branch --source=djgpa `which django-admin.py` test djgpa 17 | - coverage report --omit="djgpa/test*" 18 | env: 19 | - DJANGO=1.3.7 20 | - DJANGO=1.4.10 21 | - DJANGO=1.5.5 22 | - DJANGO=1.6 23 | branches: 24 | only: 25 | - master 26 | - development 27 | after_success: coveralls 28 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ruslan Askarov 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements/* 2 | recursive-exclude * .DS_Store 3 | recursive-include geoip *.py *.html *.txt *.po *.jar *.js 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | django-admin.py test --settings=djgpa.test_settings djgpa 3 | coverage: 4 | export DJANGO_SETTINGS_MODULE=djgpa.test_settings && \ 5 | coverage run --branch --source=djgpa `which django-admin.py` test djgpa && \ 6 | coverage report --omit="djgpa/test*,djgpa/migrations/*,djgpa/management/*" 7 | sphinx: 8 | cd docs && sphinx-build -b html -d .build/doctrees . .build/html 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django-GooglePlay-API 2 | ===================== 3 | 4 | .. image:: https://api.travis-ci.org/gotlium/django-googleplay-api.png?branch=master 5 | :alt: Build Status 6 | :target: https://travis-ci.org/gotlium/django-googleplay-api 7 | .. image:: https://coveralls.io/repos/gotlium/django-googleplay-api/badge.png?branch=master 8 | :target: https://coveralls.io/r/gotlium/django-googleplay-api?branch=master 9 | .. image:: https://pypip.in/v/django-googleplay-api/badge.png 10 | :alt: Current version on PyPi 11 | :target: https://crate.io/packages/django-googleplay-api/ 12 | .. image:: https://pypip.in/d/django-googleplay-api/badge.png 13 | :alt: Downloads from PyPi 14 | :target: https://crate.io/packages/django-googleplay-api/ 15 | 16 | With this package, you can configure device id and proxy params, 17 | for google play api. Also you can change google account, and link device 18 | with new or existing account. 19 | This reusable app give you capabilities to search, download and etc. 20 | 21 | 22 | Installation: 23 | ------------- 24 | 1. Package: 25 | 26 | .. code-block:: bash 27 | 28 | $ git clone https://github.com/gotlium/django-googleplay-api.git 29 | 30 | $ cd django-googleplay-api && sudo python setup.py install 31 | 32 | **OR** 33 | 34 | .. code-block:: bash 35 | 36 | $ sudo pip install django-googleplay-api 37 | 38 | 2. Add the ``djgpa`` and ``preferences`` applications to ``INSTALLED_APPS`` 39 | in your settings file (usually ``settings.py``) 40 | 3. Sync database (``./manage.py syncdb``) 41 | 42 | 43 | Usage example: 44 | -------------- 45 | 1. Setup Google Account, DeviceID, Proxy settings on admin panel 46 | 2. Try to use it from shell (``./manage.py shell``): 47 | 48 | >>> from djgpa.api import GooglePlay 49 | >>> 50 | >>> api = GooglePlay().auth() 51 | >>> # Search apps 52 | >>> for row in api.search('google'): 53 | ... print row.title 54 | >>> # App details 55 | >>> details = api.details('com.android.chrome') 56 | >>> print details.docV2.title, details.docV2.creator 57 | >>> # Download app 58 | >>> api.download('com.google.android.apps.docs', '~/Download/chrome.apk') 59 | 60 | 61 | Compatibility: 62 | ------------- 63 | * Python: 2.6, 2.7 64 | * Django: 1.3.x, 1.4.x, 1.5.x, 1.6 65 | 66 | 67 | .. image:: https://d2weczhvl823v0.cloudfront.net/gotlium/django-googleplay-api/trend.png 68 | :alt: Bitdeli badge 69 | :target: https://bitdeli.com/free 70 | -------------------------------------------------------------------------------- /demo/demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/demo/demo/__init__.py -------------------------------------------------------------------------------- /demo/demo/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for demo project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 15 | 'NAME': 'db.sqlite', # Or path to database file if using sqlite3. 16 | 'USER': '', # Not used with sqlite3. 17 | 'PASSWORD': '', # Not used with sqlite3. 18 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 19 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 20 | } 21 | } 22 | 23 | # Hosts/domain names that are valid for this site; required if DEBUG is False 24 | # See https://docs.djangoproject.com/en/1.4/ref/settings/#allowed-hosts 25 | ALLOWED_HOSTS = [] 26 | 27 | # Local time zone for this installation. Choices can be found here: 28 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 29 | # although not all choices may be available on all operating systems. 30 | # In a Windows environment this must be set to your system time zone. 31 | TIME_ZONE = 'America/Chicago' 32 | 33 | # Language code for this installation. All choices can be found here: 34 | # http://www.i18nguy.com/unicode/language-identifiers.html 35 | LANGUAGE_CODE = 'en-us' 36 | 37 | SITE_ID = 1 38 | 39 | # If you set this to False, Django will make some optimizations so as not 40 | # to load the internationalization machinery. 41 | USE_I18N = True 42 | 43 | # If you set this to False, Django will not format dates, numbers and 44 | # calendars according to the current locale. 45 | USE_L10N = True 46 | 47 | # If you set this to False, Django will not use timezone-aware datetimes. 48 | USE_TZ = True 49 | 50 | # Absolute filesystem path to the directory that will hold user-uploaded files. 51 | # Example: "/home/media/media.lawrence.com/media/" 52 | MEDIA_ROOT = '' 53 | 54 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 55 | # trailing slash. 56 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 57 | MEDIA_URL = '' 58 | 59 | # Absolute path to the directory static files should be collected to. 60 | # Don't put anything in this directory yourself; store your static files 61 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 62 | # Example: "/home/media/media.lawrence.com/static/" 63 | STATIC_ROOT = '' 64 | 65 | # URL prefix for static files. 66 | # Example: "http://media.lawrence.com/static/" 67 | STATIC_URL = '/static/' 68 | 69 | # Additional locations of static files 70 | STATICFILES_DIRS = ( 71 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 72 | # Always use forward slashes, even on Windows. 73 | # Don't forget to use absolute paths, not relative paths. 74 | ) 75 | 76 | # List of finder classes that know how to find static files in 77 | # various locations. 78 | STATICFILES_FINDERS = ( 79 | 'django.contrib.staticfiles.finders.FileSystemFinder', 80 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 81 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 82 | ) 83 | 84 | # Make this unique, and don't share it with anybody. 85 | SECRET_KEY = '^3$ol=q^mpw+p3ikjq=0_-l=@5qr0!3k6uewwgy$#%zpkgkt30' 86 | 87 | # List of callables that know how to import templates from various sources. 88 | TEMPLATE_LOADERS = ( 89 | 'django.template.loaders.filesystem.Loader', 90 | 'django.template.loaders.app_directories.Loader', 91 | # 'django.template.loaders.eggs.Loader', 92 | ) 93 | 94 | MIDDLEWARE_CLASSES = ( 95 | 'django.middleware.common.CommonMiddleware', 96 | 'django.contrib.sessions.middleware.SessionMiddleware', 97 | 'django.middleware.csrf.CsrfViewMiddleware', 98 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 99 | 'django.contrib.messages.middleware.MessageMiddleware', 100 | # Uncomment the next line for simple clickjacking protection: 101 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 102 | ) 103 | 104 | ROOT_URLCONF = 'demo.urls' 105 | 106 | # Python dotted path to the WSGI application used by Django's runserver. 107 | WSGI_APPLICATION = 'demo.wsgi.application' 108 | 109 | TEMPLATE_DIRS = ( 110 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 111 | # Always use forward slashes, even on Windows. 112 | # Don't forget to use absolute paths, not relative paths. 113 | ) 114 | 115 | INSTALLED_APPS = ( 116 | 'django.contrib.auth', 117 | 'django.contrib.contenttypes', 118 | 'django.contrib.sessions', 119 | 'django.contrib.sites', 120 | 'django.contrib.messages', 121 | 'django.contrib.staticfiles', 122 | # Uncomment the next line to enable the admin: 123 | 'grappelli', 124 | 'django.contrib.admin', 125 | # Uncomment the next line to enable admin documentation: 126 | # 'django.contrib.admindocs', 127 | 128 | 'django_extensions', 129 | 'preferences', 130 | 'south', 131 | 'djgpa', 132 | ) 133 | 134 | # A sample logging configuration. The only tangible logging 135 | # performed by this configuration is to send an email to 136 | # the site admins on every HTTP 500 error when DEBUG=False. 137 | # See http://docs.djangoproject.com/en/dev/topics/logging for 138 | # more details on how to customize your logging configuration. 139 | LOGGING = { 140 | 'version': 1, 141 | 'disable_existing_loggers': False, 142 | 'filters': { 143 | 'require_debug_false': { 144 | '()': 'django.utils.log.RequireDebugFalse' 145 | } 146 | }, 147 | 'handlers': { 148 | 'mail_admins': { 149 | 'level': 'ERROR', 150 | 'filters': ['require_debug_false'], 151 | 'class': 'django.utils.log.AdminEmailHandler' 152 | } 153 | }, 154 | 'loggers': { 155 | 'django.request': { 156 | 'handlers': ['mail_admins'], 157 | 'level': 'ERROR', 158 | 'propagate': True, 159 | }, 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /demo/demo/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.contrib import admin 3 | 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | '', 8 | url(r'^admin/', include(admin.site.urls)), 9 | url(r'^grappelli/', include('grappelli.urls')), 10 | url(r'', include('djgpa.urls')), 11 | ) 12 | -------------------------------------------------------------------------------- /demo/demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /demo/requirements.txt: -------------------------------------------------------------------------------- 1 | django==1.6 2 | django-grappelli==2.4.7 3 | South==0.8.2 4 | Werkzeug==0.9.4 5 | django-extensions==1.2.5 6 | ipython==1.1.0 7 | readline==6.2.4.1 8 | -------------------------------------------------------------------------------- /djgpa/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (1, 1, 2) 2 | -------------------------------------------------------------------------------- /djgpa/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.contrib import admin 4 | 5 | from preferences.admin import PreferencesAdmin 6 | 7 | from .models import GooglePlayPreferences 8 | 9 | 10 | class PreferencesAdmin(PreferencesAdmin): 11 | exclude = ('sites',) 12 | 13 | def add_view(self, *args, **kwargs): 14 | return self.changelist_view(*args, **kwargs) 15 | 16 | def has_add_permission(self, request): 17 | return False 18 | 19 | class Media: 20 | js = ( 21 | '/static/djgpa/admin/js/djgpa.js', 22 | ) 23 | 24 | 25 | admin.site.register(GooglePlayPreferences, PreferencesAdmin) 26 | -------------------------------------------------------------------------------- /djgpa/android-checkin/README: -------------------------------------------------------------------------------- 1 | Author: nviennot 2 | License: MIT 3 | URL: https://github.com/nviennot/android-checkin 4 | -------------------------------------------------------------------------------- /djgpa/android-checkin/android-checkin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/android-checkin.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/commons-codec-1.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/commons-codec-1.6.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/commons-logging-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/commons-logging-1.1.1.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/fluent-hc-4.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/fluent-hc-4.2.2.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/httpclient-4.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/httpclient-4.2.2.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/httpclient-cache-4.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/httpclient-cache-4.2.2.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/httpcore-4.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/httpcore-4.2.2.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/httpmime-4.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/httpmime-4.2.2.jar -------------------------------------------------------------------------------- /djgpa/android-checkin/protobuf-java-2.4.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/android-checkin/protobuf-java-2.4.1.jar -------------------------------------------------------------------------------- /djgpa/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | import urllib 5 | import os 6 | 7 | import grab 8 | import pb2 9 | 10 | from django.core.exceptions import ObjectDoesNotExist 11 | 12 | from .models import GooglePlayPreferences 13 | from .configs import ( 14 | AUTH_VALUES, REQUEST_HEADERS_TO_API, URL_LOGIN, 15 | TOKEN_FILE, TOKEN_TTL, REQUEST_URL, DOWNLOAD_AGENT) 16 | 17 | 18 | from google.protobuf.internal.containers import RepeatedCompositeFieldContainer 19 | from google.protobuf.message import Message 20 | from google.protobuf import descriptor 21 | 22 | 23 | class AccountWasNotInstalled(Exception): 24 | """ Google accounts was not installed """ 25 | 26 | 27 | class DeviceIDIsNotSet(Exception): 28 | """ Device ID is not set. """ 29 | 30 | 31 | class GooglePlay(object): 32 | def __init__(self, token=None): 33 | conf = GooglePlayPreferences.objects.all() 34 | 35 | if not conf.count(): 36 | raise ObjectDoesNotExist('Config not exists.') 37 | if not (conf[0].google_login != "" and conf[0].google_password != ""): 38 | raise AccountWasNotInstalled( 39 | 'Google accounts was not installed.') 40 | if not conf[0].android_id: 41 | raise DeviceIDIsNotSet('Device ID is not set.') 42 | 43 | conf = conf[0] 44 | 45 | if conf.proxy_host and conf.proxy_port: 46 | self.proxy = '%s:%s' % (conf.proxy_host, conf.proxy_port) 47 | if conf.proxy_login and conf.proxy_password: 48 | self.proxy_auth = '%s:%s' % (conf.proxy_login, conf.proxy_password) 49 | self.proxy_enabled = conf.proxy_enabled 50 | self.token = token 51 | 52 | AUTH_VALUES['Email'] = conf.google_login 53 | AUTH_VALUES['Passwd'] = conf.google_password 54 | AUTH_VALUES['androidId'] = conf.android_id 55 | REQUEST_HEADERS_TO_API['X-DFE-Device-Id'] = conf.android_id 56 | 57 | def toDict(self, protoObj): 58 | """Converts the (protobuf) result from an API call into a dict, for 59 | easier introspection.""" 60 | iterable = False 61 | if isinstance(protoObj, RepeatedCompositeFieldContainer): 62 | iterable = True 63 | else: 64 | protoObj = [protoObj] 65 | retlist = [] 66 | 67 | for po in protoObj: 68 | msg = dict() 69 | for fielddesc, value in po.ListFields(): 70 | if fielddesc.type == descriptor.FieldDescriptor.TYPE_GROUP or ( 71 | isinstance(value, RepeatedCompositeFieldContainer) 72 | ) or (isinstance(value, Message)): 73 | msg[fielddesc.name] = self.toDict(value) 74 | else: 75 | msg[fielddesc.name] = value 76 | retlist.append(msg) 77 | if not iterable: 78 | if len(retlist) > 0: 79 | return retlist[0] 80 | else: 81 | return None 82 | return retlist 83 | 84 | def _get(self, url, **kwargs): 85 | grabber = grab.Grab() 86 | grabber.reset() 87 | grabber.setup( 88 | connect_timeout=5, timeout=300, hammer_mode=True, 89 | hammer_timeouts=((300, 360), (360, 420), (420, 480)), 90 | ) 91 | if kwargs: 92 | grabber.setup(**kwargs) 93 | if self.proxy_enabled: 94 | if hasattr(self, 'proxy'): 95 | grabber.setup(proxy=self.proxy, proxy_type='http') 96 | if hasattr(self, 'proxy_auth'): 97 | grabber.setup(proxy_userpwd=self.proxy_auth) 98 | grabber.go(url) 99 | return grabber.response.body 100 | 101 | def _login_parse_response(self, data): 102 | params = {} 103 | for line in data: 104 | if not "=" in line: 105 | continue 106 | key, val = line.split("=") 107 | params[key.strip().lower()] = val.strip() 108 | return params 109 | 110 | def _login(self): 111 | response = self._get( 112 | URL_LOGIN, post=AUTH_VALUES, headers={"Accept-Encoding": ""}) 113 | params = self._login_parse_response(response.split()) 114 | if "auth" in params: 115 | return params["auth"] 116 | elif "error" in params: 117 | raise Exception("server says: " + params["error"]) 118 | 119 | def _save_token(self): 120 | with open(TOKEN_FILE, 'w') as file_obj: 121 | file_obj.write(self.token) 122 | 123 | def _remove_token(self): 124 | create_time = datetime.datetime.fromtimestamp( 125 | os.stat(TOKEN_FILE).st_ctime) 126 | now = datetime.datetime.now() 127 | if (now - create_time).days > TOKEN_TTL: 128 | os.remove(TOKEN_FILE) 129 | return True 130 | 131 | def _executeRequestApi2(self, path, **kwargs): 132 | headers = REQUEST_HEADERS_TO_API 133 | headers["Authorization"] = "GoogleLogin auth=%s" % self.token 134 | url = REQUEST_URL % path 135 | response = self._get(url, headers=headers, **kwargs) 136 | return pb2.ResponseWrapper.FromString(response) 137 | 138 | def auth(self): 139 | if self.token: 140 | pass 141 | elif os.path.exists(TOKEN_FILE): 142 | if self._remove_token(): 143 | self.auth() 144 | self.token = open(TOKEN_FILE).readline(1024) 145 | else: 146 | self.token = self._login() 147 | self._save_token() 148 | return self 149 | 150 | def search(self, query, nb_results=None, offset=None): 151 | path = {'q': query, 'c': 3} 152 | if nb_results is not None: 153 | path['n'] = int(nb_results) 154 | if offset is not None: 155 | path['o'] = int(offset) 156 | path = 'search?%s' % urllib.urlencode(path) 157 | results = self._executeRequestApi2(path).payload.searchResponse 158 | try: 159 | return results.doc[0].child 160 | except IndexError: 161 | return [] 162 | 163 | def details(self, package_name): 164 | path = "details?%s" % urllib.urlencode({'doc': package_name}) 165 | return self._executeRequestApi2(path).payload.detailsResponse 166 | 167 | def download(self, package_name, location, details=None): 168 | details = details and details or self.details(package_name) 169 | doc = details.docV2 170 | versionCode = doc.details.appDetails.versionCode 171 | offerType = doc.offer[0].offerType 172 | response = self._executeRequestApi2( 173 | "purchase", post={ 174 | 'ot': offerType, 175 | 'doc': package_name, 176 | 'vc': versionCode 177 | } 178 | ).payload.buyResponse.purchaseStatusResponse.appDeliveryData 179 | 180 | url = response.downloadUrl 181 | cookie = response.downloadAuthCookie[0] 182 | cookies = {str(cookie.name): str(cookie.value)} 183 | headers = {"User-Agent": DOWNLOAD_AGENT, "Accept-Encoding": ""} 184 | 185 | response = self._get(url, headers=headers, cookies=cookies) 186 | with open(location, 'wb') as out: 187 | out.write(response) 188 | return True 189 | 190 | def browse(self, cat=None, ctr=None): 191 | path = "browse?c=3" 192 | if cat is not None: 193 | path += "&cat=%s" % cat 194 | if ctr is not None: 195 | path += "&ctr=%s" % ctr 196 | return self._executeRequestApi2(path).payload.browseResponse 197 | 198 | def list(self, cat, ctr=None, nb_results=None, offset=None): 199 | path = "list?c=3&cat=%s" % cat 200 | if ctr is not None: 201 | path += "&ctr=%s" % ctr 202 | if nb_results is not None: 203 | path += "&n=%s" % nb_results 204 | if offset is not None: 205 | path += "&o=%s" % offset 206 | return self._executeRequestApi2(path).payload.listResponse 207 | -------------------------------------------------------------------------------- /djgpa/configs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from os import path 4 | 5 | from django.conf import settings 6 | 7 | 8 | def get_settings(key, default): 9 | return getattr(settings, key, default) 10 | 11 | 12 | def location(x): 13 | return path.join(path.dirname(path.realpath(__file__)), x) 14 | 15 | 16 | LANG = get_settings('LANG', "ru_RU") 17 | COUNTRY = get_settings('COUNTRY', 'ru') 18 | 19 | SERVICE = get_settings('SERVICE', "androidmarket") 20 | URL_LOGIN = get_settings( 21 | 'URL_LOGIN', "https://android.clients.google.com/auth") 22 | ACCOUNT_TYPE_GOOGLE = get_settings('ACCOUNT_TYPE_GOOGLE', "GOOGLE") 23 | ACCOUNT_TYPE_HOSTED = get_settings('ACCOUNT_TYPE_HOSTED', "HOSTED") 24 | ACCOUNT_TYPE_HOSTED_OR_GOOGLE = get_settings( 25 | 'ACCOUNT_TYPE_HOSTED_OR_GOOGLE', "HOSTED_OR_GOOGLE") 26 | 27 | DOWNLOAD_AGENT = get_settings( 28 | 'DOWNLOAD_AGENT', 29 | "AndroidDownloadManager/4.1.1 (Linux; U; Android 4.1.1; " 30 | "Nexus S Build/JRO03E)") 31 | 32 | AUTH_VALUES = get_settings('AUTH_VALUES', { 33 | "Email": None, 34 | "Passwd": None, 35 | "service": SERVICE, 36 | "accountType": ACCOUNT_TYPE_HOSTED_OR_GOOGLE, 37 | "has_permission": "1", 38 | "source": "android", 39 | "androidId": None, 40 | "app": "com.android.vending", 41 | "device_country": COUNTRY, 42 | "operatorCountry": COUNTRY, 43 | "lang": COUNTRY, 44 | "sdk_version": "16" 45 | }) 46 | 47 | REQUEST_URL = get_settings( 48 | 'REQUEST_URL', "https://android.clients.google.com/fdfe/%s" 49 | ) 50 | REQUEST_HEADERS_TO_API = get_settings('REQUEST_HEADERS_TO_API', { 51 | "Accept-Language": LANG, 52 | "Authorization": "", 53 | "X-DFE-Enabled-Experiments": "cl:billing.select_add_instrument_by_default", 54 | "X-DFE-Unsupported-Experiments": str( 55 | "nocache:billing.use_charging_poller,market_emails,buyer_currency," 56 | "prod_baseline,checkin.set_asset_paid_app_field,shekel_test," 57 | "content_ratings,buyer_currency_in_app,nocache:encrypted_apk," 58 | "recent_changes"), 59 | "X-DFE-Device-Id": "", 60 | "X-DFE-Client-Id": "am-android-google", 61 | "User-Agent": "Android-Finsky/3.7.13 (" 62 | "api=3,versionCode=8013013,sdk=16,device=crespo," 63 | "hardware=herring,product=soju)", 64 | "X-DFE-SmallestScreenWidthDp": "320", 65 | "X-DFE-Filter-Level": "3", 66 | "Accept-Encoding": "", 67 | "Host": "android.clients.google.com" 68 | }) 69 | 70 | REQUEST_POST_CONTENT_TYPE = get_settings( 71 | 'REQUEST_POST_CONTENT_TYPE', 72 | "application/x-www-form-urlencoded; charset=UTF-8") 73 | 74 | TOKEN_FILE = get_settings('TOKEN_FILE', "/tmp/google.token") 75 | TOKEN_TTL = get_settings('TOKEN_TTL', 3) 76 | 77 | AID_GENERATOR = location('android-checkin/android-checkin.jar') 78 | -------------------------------------------------------------------------------- /djgpa/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | pass 12 | 13 | def backwards(self, orm): 14 | pass 15 | 16 | models = { 17 | 18 | } 19 | 20 | complete_apps = ['djgpa'] -------------------------------------------------------------------------------- /djgpa/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotlium/django-googleplay-api/c1e73cece1587df56ef3ea37246d9f163f97df18/djgpa/migrations/__init__.py -------------------------------------------------------------------------------- /djgpa/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from django.db.models.signals import post_save 6 | from django.dispatch import receiver 7 | from django.db import models 8 | 9 | from preferences.models import Preferences 10 | 11 | from configs import TOKEN_FILE 12 | 13 | 14 | class GooglePlayPreferences(Preferences): 15 | __module__ = 'preferences.models' 16 | 17 | android_id = models.CharField('Android ID', max_length=50) 18 | google_login = models.CharField('Google email', max_length=50) 19 | google_password = models.CharField('Google password', max_length=75) 20 | proxy_enabled = models.BooleanField('Use proxy', default=False) 21 | proxy_host = models.CharField( 22 | 'Proxy host', max_length=50, blank=True, null=True) 23 | proxy_port = models.IntegerField( 24 | 'Proxy port', max_length=5, blank=True, null=True) 25 | proxy_login = models.CharField( 26 | 'Proxy login', max_length=50, blank=True, null=True) 27 | proxy_password = models.CharField( 28 | 'Proxy password', max_length=75, blank=True, null=True) 29 | 30 | class Meta: 31 | ordering = ['-id'] 32 | verbose_name = u'Google Play' 33 | verbose_name_plural = u'Google Play' 34 | 35 | 36 | @receiver(post_save, sender=GooglePlayPreferences) 37 | def remove_token(*args, **kwargs): 38 | if os.path.exists(TOKEN_FILE): 39 | os.unlink(TOKEN_FILE) 40 | -------------------------------------------------------------------------------- /djgpa/static/djgpa/admin/js/djgpa.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $(document).ready(function () { 3 | $(' generate').insertAfter( 4 | '#id_android_id'); 5 | 6 | $('#generate').click(function() { 7 | $.get('/get_new_android_id/', function(data) { 8 | if (data != 'ERROR') { 9 | $('#id_android_id').val(data); 10 | } else { 11 | alert('Can not get new android id!'); 12 | }; 13 | }); 14 | }); 15 | }); 16 | })(django.jQuery); 17 | -------------------------------------------------------------------------------- /djgpa/test_cases.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from django.core.exceptions import ObjectDoesNotExist 6 | from django.test import TestCase 7 | 8 | from djgpa.models import GooglePlayPreferences 9 | from djgpa.api import GooglePlay, AccountWasNotInstalled, DeviceIDIsNotSet 10 | 11 | 12 | class DjGPATestCase(TestCase): 13 | def setUp(self): 14 | self.aid = "387c87ca2103a990" 15 | self.email = "djgpa.tests@gmail.com" 16 | self.password = "djgpapassword" 17 | self.app_id = 'com.google.android.apps.unveil' 18 | self.app_name = 'Google Goggles' 19 | self.app_dst = '/tmp/app.apk' 20 | 21 | def _install_account(self): 22 | GooglePlayPreferences.objects.create( 23 | android_id=self.aid, 24 | google_login=self.email, 25 | google_password=self.password 26 | ) 27 | self.assertEqual(GooglePlayPreferences.objects.all().count(), 1) 28 | return GooglePlay().auth() 29 | 30 | def test_a_settings_is_set(self): 31 | self.assertRaises(ObjectDoesNotExist, GooglePlay) 32 | 33 | def test_b_settings_account_is_set(self): 34 | GooglePlayPreferences.objects.create( 35 | android_id=self.aid, google_login="", google_password="" 36 | ) 37 | self.assertRaises(AccountWasNotInstalled, GooglePlay) 38 | 39 | def test_c_settings_android_id_is_set(self): 40 | GooglePlayPreferences.objects.create( 41 | android_id="", 42 | google_login=self.email, 43 | google_password=self.password 44 | ) 45 | self.assertRaises(DeviceIDIsNotSet, GooglePlay) 46 | 47 | def test_d_check_details(self): 48 | api = self._install_account() 49 | details = api.details(self.app_id) 50 | self.assertEqual(details.docV2.title, self.app_name) 51 | 52 | def test_e_check_search(self): 53 | api = self._install_account() 54 | results = api.search(self.app_name) 55 | self.assertEqual(results[0].backendDocid, self.app_id) 56 | 57 | def test_f_check_download(self): 58 | api = self._install_account() 59 | self.assertEqual(api.download(self.app_id, self.app_dst), True) 60 | self.assertEqual(os.path.exists(self.app_dst), True) 61 | 62 | def test_f_check_download_from_details(self): 63 | api = self._install_account() 64 | details = api.details(self.app_id) 65 | self.assertEqual(api.download( 66 | self.app_id, self.app_dst, details), True) 67 | self.assertEqual(os.path.exists(self.app_dst), True) 68 | self.assertEqual( 69 | os.path.getsize(self.app_dst), 70 | details.docV2.details.appDetails.file[0].size 71 | ) 72 | 73 | def test_g_check_browse(self): 74 | api = self._install_account() 75 | self.assertTrue(len(api.toDict(api.browse()).get('category')) > 10) 76 | 77 | def test_h_check_browse(self): 78 | api = self._install_account() 79 | self.assertTrue(len(api.toDict(api.list('GAME')).get('doc')) > 0) 80 | -------------------------------------------------------------------------------- /djgpa/test_settings.py: -------------------------------------------------------------------------------- 1 | from django import VERSION 2 | 3 | DATABASE_ENGINE = 'sqlite3' 4 | 5 | SITE_ID = 1 6 | 7 | SECRET_KEY = '52b700e3a9dc3f83a410df37777fefbc' 8 | 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.sqlite3', 12 | 'NAME': ':memory:', 13 | } 14 | } 15 | 16 | INSTALLED_APPS = [ 17 | 'django.contrib.auth', 18 | 'django.contrib.contenttypes', 19 | 'django.contrib.sessions', 20 | 'django.contrib.sites', 21 | 'django.contrib.messages', 22 | 'django.contrib.staticfiles', 23 | 'django.contrib.admin', 24 | 25 | 'preferences', 26 | 'djgpa', 27 | ] 28 | 29 | TEMPLATE_LOADERS = ( 30 | 'django.template.loaders.filesystem.Loader', 31 | 'django.template.loaders.app_directories.Loader', 32 | ) 33 | 34 | if VERSION[:2] < (1, 6): 35 | TEST_RUNNER = 'discover_runner.DiscoverRunner' 36 | -------------------------------------------------------------------------------- /djgpa/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | 4 | urlpatterns = patterns( 5 | 'djgpa.views', 6 | url(r'^get_new_android_id/$', 'generate_aid', name='gpa-generate-aid'), 7 | ) 8 | -------------------------------------------------------------------------------- /djgpa/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from subprocess import Popen, STDOUT, PIPE 4 | 5 | from django.http import HttpResponse 6 | 7 | from .models import GooglePlayPreferences 8 | from .configs import AID_GENERATOR 9 | 10 | 11 | CMD = "java -jar %s '%s' '%s' 2>&1 | grep AndroidId | awk '{print $2}'" 12 | 13 | 14 | def generate_aid(request): 15 | objects = GooglePlayPreferences.objects.all()[0] 16 | login = objects.google_login 17 | password = objects.google_password 18 | response = 'ERROR' 19 | result = None 20 | 21 | if login and password: 22 | command = CMD % (AID_GENERATOR, login, password) 23 | 24 | p = Popen(command, shell=True, stdout=PIPE, stderr=STDOUT) 25 | result = p.communicate() 26 | 27 | if result and len(result): 28 | response = result[0].strip() 29 | return HttpResponse(response) 30 | -------------------------------------------------------------------------------- /requirements/package.txt: -------------------------------------------------------------------------------- 1 | django-preferences==0.0.6 2 | Django==1.6 3 | gdata==2.0.18 4 | grab==0.4.13 5 | lxml==3.2.4 6 | protobuf==2.5.0 7 | pycurl==7.19.0.2 8 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | flake8==2.1.0 2 | django-discover-runner==1.0 3 | coverage==3.7 4 | coveralls==0.3 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from djgpa import VERSION 3 | 4 | 5 | setup( 6 | name='django-googleplay-api', 7 | version=".".join(map(str, VERSION)), 8 | description='Google Play API, with configuration on Django.', 9 | keywords="django googleplay api", 10 | long_description=open('README.rst').read(), 11 | author="GoTLiuM InSPiRiT", 12 | author_email='gotlium@gmail.com', 13 | url='http://github.com/gotlium/django-googleplay-api', 14 | packages=find_packages(exclude=['demo']), 15 | package_data={'djgpa': [ 16 | 'android-checkin/*.jar', 17 | 'static/djgpa/admin/js/djgpa.js', 18 | ]}, 19 | include_package_data=True, 20 | install_requires=[ 21 | 'gdata', 22 | 'django-preferences', 23 | 'protobuf', 24 | 'pycurl', 25 | 'lxml', 26 | 'grab', 27 | ], 28 | zip_safe=False, 29 | classifiers=[ 30 | 'Development Status :: 5 - Production/Stable', 31 | 'Environment :: Web Environment', 32 | 'Framework :: Django', 33 | 'Intended Audience :: Developers', 34 | 'License :: OSI Approved :: GNU General Public License (GPL)', 35 | 'Operating System :: OS Independent', 36 | 'Programming Language :: Python', 37 | ], 38 | ) 39 | --------------------------------------------------------------------------------