Python のコード例: ゲーム オプションおよびトレーラーを含むアプリの申請
この記事では、次のタスクで Microsoft Store 申請 API を使用する方法を示す Python コード例を提供します。
- Microsoft Store 申請 API で使用する Azure AD アクセス トークンを取得します。
- アプリの申請の作成
- ゲームとトレーラーの高度な登録情報のオプションを含む、アプリの申請用のストア登録情報データを構成します。
- アプリの申請用のパッケージ、登録情報の画像、トレーラー ファイルが含まれた ZIP ファイルをアップロードします。
- アプリの申請をコミットします。
このコードでは、他のサンプル クラスと関数を呼び出して、Microsoft Store 申請 API を使ってゲーム オプションとトレーラーを含むアプリの申請を作成し、コミットします。 このコードを採用するには、次の手順を実行してください。
tenant
変数をアプリのテナント ID に割り当てて、client
変数とsecret
変数をアプリのクライアント ID とキーに割り当てます。 詳しくは、「Azure AD アプリケーションをパートナー センター アカウントに関連付ける方法」をご覧ください。application_id
変数を、申請の作成対象とするアプリの Store ID に割り当てます。
import time
from devcenterclient import DevCenterClient, DevCenterAccessTokenClient
import submissiondatasamples as samples
# Add your tenant ID, client ID, and client secret here.
tenant = ""
client = ""
secret = ""
acc_token_client = DevCenterAccessTokenClient(tenant, client, secret)
acc_token = acc_token_client.get_access_token("https://manage.devcenter.microsoft.com")
dev_center = DevCenterClient("manage.devcenter.microsoft.com", acc_token)
# The application ID is taken from your app dashboard page's URI in Dev Center,
# e.g. https://developer.microsoft.com/en-us/dashboard/apps/{application_id}/
application_id = ""
# Get the application object, and cancel any in progress submissions.
is_ok, app = dev_center.get_application(application_id)
assert is_ok
if "pendingApplicationSubmission" in app:
in_progress_submission_id = app["pendingApplicationSubmission"]["id"]
is_ok = dev_center.cancel_in_progress_submission(application_id, in_progress_submission_id)
assert is_ok
# Create a new submission, based on the last published submission.
is_ok, submission = dev_center.create_submission(application_id)
assert is_ok
submission_id = submission["id"]
# The following fields are required:
submission["applicationCategory"] = "Games_Fighting"
submission["listings"] = samples.get_listings_object()
submission["Pricing"] = samples.get_pricing_object()
submission["packages"] = [samples.get_package_object()]
submission["allowTargetFutureDeviceFamilies"] = samples.get_device_families_object()
# The app must have the hasAdvancedListingPermission set to True in order for gaming options
# and trailers to be applied. If that's not the case, you can still update the app and
# its submissions through the API, but gaming options and trailers won't be saved.
if not "hasAdvancedListingPermission" in app or not app["hasAdvancedListingPermission"]:
print("This application does not support gaming options or trailers.")
else:
submission["gamingOptions"] = [samples.get_gaming_options_object()]
submission["trailers"] = [samples.get_trailer_object()]
# Continue updating the submission_json object with additional options as needed.
# After you've finished, call the Update API with the code below to save it:
is_ok, submission = dev_center.update_submission(application_id, submission_id, submission)
assert is_ok
# All images and packages should be located in a single ZIP file. In the submission JSON,
# the file names for all objects requiring them (icons, packages, etc.) must exactly
# match the file names from the ZIP file.
zip_file_path = ""
is_ok = dev_center.upload_zip_file_for_submission(application_id, submission_id, zip_file_path)
assert is_ok
# Committing the submission will start the submission process for it. Once committed,
# the submission can no longer be changed.
is_ok = dev_center.commit_submission(application_id, submission_id)
assert is_ok
# After committing, you can poll the commit API for the status of the submission's process using
# the following code.
waiting_for_commit_start = True
while waiting_for_commit_start:
is_ok, submission_status = dev_center.get_submission_status(application_id, submission_id)
assert is_ok
waiting_for_commit_start = submission_status == "CommitStarted"
if waiting_for_commit_start:
time.sleep(60)
次の例では、以下に示すクラスを定義します。
DevCenterAccessTokenClient
クラスでは、指定されたtenantId
、clientId
、clientSecret
の値を使って、Microsoft Store 申請 API で使用する Azure AD アクセス トークンを作成するヘルパー メソッドを定義します。DevCenterClient
クラスでは、Microsoft Store 申請 API のさまざまなメソッドを呼び出して、アプリの申請用のパッケージ、登録情報の画像、トレーラー ファイルを含む ZIP ファイルをアップロードするヘルパー メソッドを定義します。
import http.client
import json
import requests
class DevCenterAccessTokenClient(object):
"""A client for acquiring access tokens from AAD to use with the Dev Center Client."""
def __init__(self, tenant_id, client_id, client_secret):
self.tenant_id = tenant_id
self.client_id = client_id
self.client_secret = client_secret
def get_access_token(self, resource):
"""Acquires an access token to the specific resource via the AAD tenant."""
body_format = "grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}"
body = body_format.format(self.client_id, self.client_secret, resource)
access_headers = {"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"}
token_conn = http.client.HTTPSConnection("login.microsoftonline.com")
token_relative_path = "/{0}/oauth2/token".format(self.tenant_id)
token_conn.request("POST", token_relative_path, body, headers=access_headers)
token_response = token_conn.getresponse()
token_json = json.loads(token_response.read().decode())
token_conn.close()
return token_json["access_token"]
class DevCenterClient(object):
"""A client for the Dev Center API."""
def __init__(self, base_uri, access_token):
self.base_uri = base_uri
self.request_headers = {
"Authorization": "Bearer " + access_token,
"Content-type": "application/json",
"User-Agent": "Python"
}
def get_application(self, application_id):
"""Returns the application as defined in Dev Center."""
path = "/v1.0/my/applications/{0}".format(application_id)
return self._get(path)
def cancel_in_progress_submission(self, application_id, submission_id):
"""Cancels the in-progress submission."""
path = "/v1.0/my/applications/{0}/submissions/{1}".format(application_id, submission_id)
return self._delete(path)
def create_submission(self, application_id):
"""Creates a new submission in Dev Center. This is identical to clicking
the Create Submission button in Dev Center."""
path = "/v1.0/my/applications/{0}/submissions".format(application_id)
return self._post(path)
def update_submission(self, application_id, submission_id, submission):
"""Updates the submission in Dev Center using the JSON provided."""
path = "/v1.0/my/applications/{0}/submissions/{1}"
path = path.format(application_id, submission_id)
return self._put(path, submission)
def get_submission(self, application_id, submission_id):
"""Gets the submission in Dev Center."""
path = "/v1.0/my/applications/{0}/submissions/{1}"
path = path.format(application_id, submission_id)
return self._get(path)
def commit_submission(self, application_id, submission_id):
"""Commits the submission to Dev Center. Once committed, Dev Center will
begin processing the submission and verify package integrity and send
it for certification."""
path = "/v1.0/my/applications/{0}/submissions/{1}/commit"
path = path.format(application_id, submission_id)
return self._post(path)
def get_submission_status(self, application_id, submission_id):
"""Returns the current state of the submission in Dev Center,
such as is the submission in certification, committed, publishing,
etc."""
path = "/v1.0/my/applications/{0}/submissions/{1}/status"
path = path.format(application_id, submission_id)
response_ok, response_obj = self._get(path)
if "status" in response_obj:
return (response_ok, response_obj["status"])
else:
return (response_ok, "Unknown")
def upload_zip_file_for_submission(self, application_id, submission_id, zip_file_path):
"""Uploads a ZIP file for the Submission API for the submission object."""
is_ok, submission = self.get_submission(application_id, submission_id)
if not is_ok:
raise "Failed to get submission."
zip_file = open(zip_file_path, 'rb')
upload_uri = submission["fileUploadUrl"].replace("+", "%2B")
upload_headers = {"x-ms-blob-type": "BlockBlob"}
upload_response = requests.put(upload_uri, zip_file, headers=upload_headers)
upload_response.raise_for_status()
def _get(self, path):
return self._invoke("GET", path)
def _post(self, path, obj=None):
return self._invoke("POST", path, obj)
def _put(self, path, obj=None):
return self._invoke("PUT", path, obj)
def _delete(self, path):
return self._invoke("DELETE", path)
def _invoke(self, method, path, obj=None):
body = ""
if not obj is None:
body = json.dumps(obj)
conn = http.client.HTTPSConnection(self.base_uri)
conn.request(method, path, body, self.request_headers)
response = conn.getresponse()
response_body = response.read().decode()
response_body_length = int(response.headers["Content-Length"])
response_obj = None
if not response_body is None and response_body_length != 0:
response_obj = json.loads(response_body)
response_ok = self._response_ok(response)
conn.close()
return (response_ok, response_obj)
def _response_ok(self, response):
status_code = int(response.status)
return status_code >= 200 and status_code <= 299
次の例では、新しいサンプル アプリの申請用の JSON 形式の登録情報データを返すヘルパー関数を定義します。
def get_listings_object():
"""Gets a sample listings map for a submission."""
listings = {
# Each listing is targeted at a specific language-locale code, e.g. EN-US.
"en-us" : {
# This structure holds basic information to display in the store.
"baseListing" : {
"copyrightAndTrademarkInfo" : "(C) 2017 Microsoft",
# Up to 7 keywords may be provided in a listing.
"keywords" : ["SampleApp", "SampleFightingGame", "GameOptions"],
"licenseTerms" : "http://example.com/licenseTerms.aspx",
"privacyPolicy" : "http://example.com/privacyPolicy.aspx",
"supportContact" : "support@example.com",
"websiteUrl" : "http://example.com",
"description" : "A sample game showing off gameplay options code.",
"features" : ["Doesn't crash", "Likes to eat chips"],
"releaseNotes" : "Initial release",
"recommendedHardware" : [],
# If your app works better with specific hardware (or needs it), you can
# add or update values here.
"hardwarePreferences": ["Keyboard", "Mouse"],
# The title of the app must match a reserved name for the app in Dev Center.
# If it doesn't, attempting to update the submission will fail.
"title" : "Super Dev Center API Simulator 2017",
"images" : [
# There are several types of images available; at least one screenshot
# is required.
{
# The file name is relative to the root of the uploaded ZIP file.
"fileName" : "img/screenshot.png",
"description" : "A basic screenshot of the app.",
"imageType" : "Screenshot"
}
]
},
# If there are any specific overrides to above information for Windows 8,
# Windows 8.1, Windows Phone 7.1, 8.0, or 8.1, you can add information here.
"platformOverrides" : {}
}
}
return listings
def get_package_object():
"""Gets a sample package for the submission in Dev Center."""
package = {
# The file name is relative to the root of the uploaded ZIP file.
"fileName" : "bin/super_dev_ctr_api_sim.appxupload",
# If you haven't begun to upload the file yet, set this value to "PendingUpload".
"fileStatus" : "PendingUpload"
}
return package
def get_pricing_object():
"""Gets a sample pricing object for a submission."""
pricing = {
# How long the trial period is, if one is allowed. Valid values are NoFreeTrial,
# OneDay, SevenDays, FifteenDays, ThirtyDays, or TrialNeverExpires.
"trialPeriod" : "NoFreeTrial",
# Maps to the default price for the app.
"priceId" : "Free",
# If you'd like to offer your app in different markets at different prices, you
# can provide priceId values per language/locale code.
"marketSpecificPricing" : {}
}
return pricing
def get_device_families_object():
"""Gets a sample device families object for a submission."""
device_families = {
# Supported values are Desktop, Mobile, Xbox, and Holographic. To make
# the app available on that specific platform, set the value to True.
"Desktop" : True,
"Mobile" : False,
"Xbox" : True,
"Holographic" : False
}
return device_families
def get_gaming_options_object():
"""Gets a sample gaming options object for a submission."""
gaming_options = {
# The genres of your app.
"Genres" : ["Games_Fighting"],
# Set this to True if your game supports local multiplayer. This field is required.
"IsLocalMultiplayer" : True,
# If local multiplayer is supported, you must provide the minimum and maximum players
# supported. Valid values are between 2 and 1000 inclusive.
"LocalMultiplayerMinPlayers" : 2,
"LocalMultiplayerMaxPlayers" : 4,
# Set this to True if your game supports local co-op play. This field is required.
"IsLocalCooperative" : True,
# If local co-op is supported, you must provide the minimum and maximum players
# supported. Valid values are between 2 and 1000 inclusive.
"LocalCooperativeMinPlayers" : 2,
"LocalCooperativeMaxPlayers" : 4,
# Set this to True if your game supports online multiplayer. This field is required.
"IsOnlineMultiplayer" : True,
# If online multiplayer is supported, you must provide the minimum and maximum players
# supported. Valid values are between 2 and 1000 inclusive.
"OnlineMultiplayerMinPlayers" : 2,
"OnlineMultiplayerMaxPlayers" : 4,
# Set this to true if your game supports online co-op play. This field is required.
"IsOnlineCooperative" : True,
# If online co-op is supported, you must provide the minimum and maximum players
# supported. Valid values are between 2 and 1000 inclusive.
"OnlineCooperativeMinPlayers" : 2,
"OnlineCooperativeMaxPlayers" : 4,
# If your game supports broadcasting a stream to other players, set this field to True.
# The field is required.
"IsBroadcastingPrivilegeGranted" : True,
# If your game supports cross-device play (e.g. a player can play on an Xbox One with
# their friend who's playing on a PC), set this field to True. This field is required.
"IsCrossPlayEnabled" : True,
# If your game supports Kinect usage, set this field to "Enabled", otherwise, set it to
# "Disabled". This field is required.
"KinectDataForExternal" : "Disabled",
# Free text about any other peripherals that your game supports. This field is optional.
"OtherPeripherals" : "Supports the usage of all fighting joysticks."
}
return gaming_options
def get_trailer_object():
"""Gets a sample trailer object for the submission in Dev Center."""
trailer = {
# This is the filename of the trailer. The file name is a relative path to the
# root of the ZIP file to be uploaded to the API.
"VideoFileName" : "trailers/main/my_awesome_trailer.mpeg",
# Aside from the video itself, a trailer can have image assets such as screenshots
# or alternate images. These are separated by language-locale code, e.g. EN-US.
"TrailerAssets" : {
"en-us" : {
# The title of the trailer to display in the store.
"Title" : "Main Trailer",
# The list of images provided with the trailer that are shown
# when the trailer isn't playing.
"ImageList" : [
{
# The file name of the image. The file name is a relative
# path to the root of the ZIP
# file to be uploaded to the API.
"FileName" : "trailers/main/thumbnail.png",
# A plaintext description of what the image represents.
"Description" : "The thumbnail for the trailer shown " +
"before the user clicks play"
},
{
"FileName" : "trailers/main/alt-img.png",
"Description" : "The image to show after the trailer plays"
}
]
}
}
}
return trailer