Google Play Developer Publishing APIを使ってapkをGooglePlayにアップロードする


# 概要
スクリプトからapkをアップロードする実験のメモ。

# 何ができるようになる？
* CircleCIなどと連携すれば今まで手作業でやっていたGooglePlayへのapkのアッロードがPullRequestベースで自動でできるようになる。
* アプリの説明文、スクリーンショットがGitで管理できるようになりそう。
* α、βテストがやりやすくなるかも。
* Hubotに、android deployとか話しかけるとproductionにアプリを公開とかもできそう。

# Google APIs Client Libraryのインストール
色々と調べた結果、 PythonのGoogle APIs Client Libraryを使うのが一番簡単に実現できそうだったのでインストールする。インストールはpipで。

```bash
$ pip install google-api-python-client
```

# サービスアカウントの登録

OAuth認証にサービスアカウントが必要になるので作成する。

1.DeveloperAPIとリンク
Developer Consoleの左メニュー「設定→APIアクセス」でDeveloperAPIをオンにする
https://play.google.com/apps/publish/#ApiAccessPlace
![](https://qiita-image-store.s3.amazonaws.com/0/43438/6a7b2f69-c396-1ba7-2720-97f2893071eb.png "1.png")


2.サービスアカウントの作成
APIアクセスページの下の方の「サービスアカウントの作成」をクリックすると以下のような画面が表示される。
![](https://qiita-image-store.s3.amazonaws.com/0/43438/348f478d-346a-fcb1-dbac-b2ddf5d169af.png "2.png")


3.Developer Consoleに移動 
Developer ConsoleへのリンクからDeveloper Consoleへ移動し、「APIと認証」で「新しいクライアントIDの作成」をクリック。
![](https://qiita-image-store.s3.amazonaws.com/0/43438/617d3c3e-eac6-0c26-ab40-8325e96270ef.png "3.png")
すると以下の画面が表示されるので、サービスアカウントをチェックしクライアントIDを作成する。
![](https://qiita-image-store.s3.amazonaws.com/0/43438/5355eb24-50ef-78b1-d34e-afd9bb9fcf38.png "4.png")

作成が完了すると、以下の画面が表示され、Google Play Android Developer-1234abcd.p12というkeyファイルがダウンロードされる。
![](https://qiita-image-store.s3.amazonaws.com/0/43438/0e6d83a0-9182-0eb5-d34b-abf8eb051e36.png "5.png")


認証情報にサービスアカウントという項目(クライアントID、メールアドレス、証明書フィンガープリント)という項目が出来ていればOK。

# サンプルコードの実行

Google Playにapkをアップロードしたり、紹介文を更新する[サンプルコード](https://github.com/googlesamples/android-play-publisher-api/tree/master/v2/python)がGitHubになるのでそれをとりあえずcloneしてみる。

```bash
$ git clone git@github.com:googlesamples/android-play-publisher-api.git
```

READMEの[First request using OAuth2: Service accounts](https://github.com/googlesamples/android-play-publisher-api/tree/master/v2/python#first-request-using-oauth2-service-accounts)を参考に作業する。

1.basic_list_apks_service_account.pyを編集してサービスアカウントのメールアドレスを追加する  
[26行目](https://github.com/googlesamples/android-play-publisher-api/blob/master/v2/python/basic_list_apks_service_account.py#L26)の以下の部分を編集する。サービスアカウントのメールアドレスは、Developer Consoleの認証情報に表示されているもの。  

```python
SERVICE_ACCOUNT_EMAIL = (
    'ENTER_YOUR_SERVICE_ACCOUNT_EMAIL_HERE@developer.gserviceaccount.com')
```
2.keyファイルの読み込み  
[38行目](https://github.com/googlesamples/android-play-publisher-api/blob/master/v2/python/basic_list_apks_service_account.py#L38)でkeyファイルを読み込んでいるので、ファイル名をサービスアカウントを作成した時にダウンロードしたkeyファイルに変更し(keyファイルの名前を変更してしまっても良い)、keyファイルを同一ディレクトリにコピーしておく。

3.サンプルを実行する

```bash
$ python basic_list_apks_service_account.py com.myapp.package
```

エラーがでる。

```bash
$ python basic_list_apks_service_account.py jp.horie1024.myapp
Traceback (most recent call last):
  File "basic_list_apks_service_account.py", line 78, in <module>
    main()
  File "basic_list_apks_service_account.py", line 49, in main
    scope='https://www.googleapis.com/auth/androidpublisher')
  File "/usr/local/lib/python2.7/site-packages/oauth2client/util.py", line 132, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/oauth2client/client.py", line 1364, in __init__
    _RequireCryptoOrDie()
  File "/usr/local/lib/python2.7/site-packages/oauth2client/client.py", line 1318, in _RequireCryptoOrDie
    raise CryptoUnavailableError('No crypto library available')
oauth2client.client.CryptoUnavailableError: No crypto library available
```
[py-opensslが必要らしい](http://stackoverflow.com/questions/27305867/google-api-access-using-service-account-oauth2client-client-cryptounavailableerr)のでpipでインストール

```bash
$ pip install PyOpenSSL
```

もう一度実行。

```bash
$ python basic_list_apks_service_account.py jp.horie1024.myapp
versionCode: 1, binary.sha1: b650430a19joihfel2lasiwbf38188d8d
```
うまくいった。

# GooglePlayにスクリプトからapkをアップロードしてみる
サンプルコードには、サービスアカウントを使用してapkをGooglePlayへアップロードするスクリプトがなかったので幾つかのサンプルを参考に書いてみる。

cloneしてきたサンプルには、[サービスアカウントを使用しないでapkをアップロードするサンプル](https://github.com/googlesamples/android-play-publisher-api/blob/master/v2/python/basic_upload_apks.py)と[サービスアカウントを使用するサンプル](https://github.com/googlesamples/android-play-publisher-api/blob/master/v2/python/basic_list_apks_service_account.py)があるので二つを読みくらべてみる。二つのサンプルは、どちらもserviceオブジェクトを作成していて、認証部分の違いのみだった。なのでこれら二つのサンプルを組み合わてみる。

[サービスアカウントを使用するサンプル](https://github.com/googlesamples/android-play-publisher-api/blob/master/v2/python/basic_list_apks_service_account.py)をベースに以下のようにした。

```python
#!/usr/bin/python
#
# Copyright 2014 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse

from apiclient.discovery import build
import httplib2
from oauth2client import client

# aplha、beta、productionなどアップロード後の公開レベルの指定を追加
TRACK = 'alpha'

SERVICE_ACCOUNT_EMAIL = (
    'ENTER_YOUR_SERVICE_ACCOUNT_EMAIL_HERE@developer.gserviceaccount.com')

argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('package_name',
                       help='The package name. Example: com.android.sample')

# apk_fileへのパスを引数で受け取る処理追加
argparser.add_argument('apk_file',
                       nargs='?',
                       default='test.apk',
                       help='The path to the APK file to upload.')


def main():
  f = file('key.p12', 'rb')
  key = f.read()
  f.close()

  credentials = client.SignedJwtAssertionCredentials(
      SERVICE_ACCOUNT_EMAIL,
      key,
      scope='https://www.googleapis.com/auth/androidpublisher')
  http = httplib2.Http()
  http = credentials.authorize(http)

  service = build('androidpublisher', 'v2', http=http)

  flags = argparser.parse_args()

  package_name = flags.package_name
  apk_file = flags.apk_file

  # try以下はhttps://github.com/googlesamples/android-play-publisher-api/blob/master/v2/python/basic_upload_apks.pyのコードを使用
  try:
    edit_request = service.edits().insert(body={}, packageName=package_name)
    result = edit_request.execute()
    edit_id = result['id']

    apk_response = service.edits().apks().upload(
        editId=edit_id,
        packageName=package_name,
        media_body=apk_file).execute()

    print 'Version code %d has been uploaded' % apk_response['versionCode']

    track_response = service.edits().tracks().update(
        editId=edit_id,
        track=TRACK,
        packageName=package_name,
        body={u'versionCodes': [apk_response['versionCode']]}).execute()

    print 'Track %s is set for version code(s) %s' % (
        track_response['track'], str(track_response['versionCodes']))

    commit_request = service.edits().commit(
        editId=edit_id, packageName=package_name).execute()

    print 'Edit "%s" has been committed' % (commit_request['id'])

  except client.AccessTokenRefreshError:
    print ('The credentials have been revoked or expired, please re-run the '
           'application to re-authorize')

if __name__ == '__main__':
  main()
```

実行

```bash
$ python upload_apks_service_account.py jp.horie1024.myapp /PATH/TO/APK/app-release.apk
```
エラーがでる。

```bash
$ python upload_apks_service_account.py jp.horie1024.myapp /PATH/TO/APK/app-release.apk
Version code 1 has been uploaded
Track alpha is set for version code(s) [1]
Traceback (most recent call last):
  File "upload_apks_service_account.py", line 71, in <module>
    main()
  File "upload_apks_service_account.py", line 62, in main
    editId=edit_id, packageName=package_name).execute()
  File "/usr/local/lib/python2.7/site-packages/oauth2client/util.py", line 132, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/googleapiclient/http.py", line 723, in execute
    raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 401 when requesting https://www.googleapis.com/androidpublisher/v2/applications/jp.horie1024.myapp/edits/0455029384756893:commit?alt=json returned "The current user has insufficient permissions to perform the requested operation.">
```
サービスアカウントの権限が足りないので、DeveloperConsoleのユーザーアカウントと権限でサービスアカウントに権限を追加し、もう一度実行。

```bash
$ python upload_apks_service_account.py jp.horie1024.myapp /PATH/TO/APK/app-release.apk
Version code 1 has been uploaded
Track alpha is set for version code(s) [1]
Edit "128240713345872934" has been committed
```

うまくいった。

# 参考
* https://speakerdeck.com/gfx/androidapurifalseririsusitemasuka-number-potatotips
* http://www.slideshare.net/sakebook/google-play-developer-api
* https://developers.google.com/discovery/libraries
* http://stackoverflow.com/questions/27305867/google-api-access-using-service-account-oauth2client-client-cryptounavailableerr
* https://developers.google.com/android-publisher/api-ref/
* https://developers.google.com/android-publisher/api-ref/edits/insert
* https://developers.google.com/android-publisher/api-ref/edits#resource

# 追記
こちらのGradlePluginを使用するとより簡単にGooglePlayへのアップロードを自動化できます。

* https://github.com/Triple-T/gradle-play-publisher

このプラグインについて紹介したスライドは[こちら](https://speakerdeck.com/horie1024/googleplayhefalseatupurodozi-dong-hua-woshi-sitemita)です。