Exemplo de Transferência de um Código de Relatório de Estado do Catálogo
Este exemplo mostra como obter o estado das ofertas de produtos que foram carregadas para o catálogo especificado. Se as ofertas falharem na validação ou revisão editorial, o exemplo transfere o relatório que mostra os motivos pelos quais a oferta falhou na revisão.
Para obter informações sobre a classe utilizada por este exemplo, veja Estado. Para obter informações sobre como obter o relatório, consulte Como Posso Obter o Estado das Ofertas de Produto?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Net;
using System.Globalization;
using System.IO.Compression; // Add reference System.IO.Compression.FileSystem
using Content.OAuth;
using Newtonsoft.Json; // NuGet Json.NET
namespace StatusReport
{
class Program
{
// The client ID and secret that you were given when you
// registered your application.
private static string clientId = "<CLIENTIDGOESHERE>";
private static string clientSecret = "<CLIENTSECRETGOESHERE>";
private static string storedRefreshToken = "<REFRESHTOKENGOESHERE OR NULL>";
private static CodeGrantOauth tokens = null;
private static DateTime tokenExpiration;
private static string devToken = "<DEVELOPERTOKENGOESHERE>";
// URI templates used to get resources.
public const string BaseUri = "https://content.api.bingads.microsoft.com/shopping/v9.1";
public static string BmcUri = BaseUri + "/bmc/{0}";
// Query string.
public static string queryString = "?alt=json";
// Replace with your store ID and catalog ID.
public static long merchantId = <STOREIDGOESHERE>;
public static long catalogId = <CATALOGIDGOESHERE>;
// The path where the compressed report is written to.
public static string downloadPath = string.Format(@"c:\reports\{0}.zip",
"Rejection_" + DateTime.Now.ToString("yyyyMMddThhmm"));
static void Main(string[] args)
{
try
{
var headers = GetCredentialHeaders();
// Build URL with query string and store id.
var url = string.Format(StatusUri + queryString, merchantId, catalogId);
// Get the catalog's status.
var status = GetResource(url, headers, typeof(CatalogStatus)) as CatalogStatus;
PrintCatalogStatus(status, catalogId);
Console.WriteLine();
}
catch (WebException e)
{
Console.WriteLine("\n" + e.Message);
HttpWebResponse response = (HttpWebResponse)e.Response;
// If the request is bad, the API returns the errors in the
// body of the request. For cases where the path may be valid, but
// the resource does not belong to the user, the API returns not found.
if (HttpStatusCode.BadRequest == response.StatusCode ||
HttpStatusCode.NotFound == response.StatusCode ||
HttpStatusCode.InternalServerError == response.StatusCode)
{
using (Stream stream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(stream);
string json = reader.ReadToEnd();
reader.Close();
try
{
var errors = JsonConvert.DeserializeObject<ContentError>(json);
PrintErrors(errors);
}
catch (Exception deserializeError)
{
// This case occurs when the path is not valid.
if (HttpStatusCode.NotFound == response.StatusCode)
{
Console.WriteLine("Path not found: " + response.ResponseUri);
}
else
{
Console.WriteLine(deserializeError.Message);
}
}
}
}
}
catch (Exception e)
{
Console.WriteLine("\n" + e.Message);
}
}
// Gets the catalog's status.
private static object GetResource(string uri, WebHeaderCollection headers, Type resourceType)
{
object resource = null;
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
request.Headers = headers;
request.Accept = "application/json";
var response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
var reader = new StreamReader(responseStream);
string json = reader.ReadToEnd();
reader.Close();
resource = JsonConvert.DeserializeObject(json, resourceType);
}
return resource;
}
// Prints the catalog's status.
private static void PrintCatalogStatus(CatalogStatus status, long catalogId)
{
Console.WriteLine("Catalog {0} Status Report", catalogId);
Console.WriteLine("Published offers: " + status.PublishedCount);
Console.WriteLine("Rejected offers: " + status.RejectedCount);
if (status.RejectedCount > 0)
{
var reportPath = GetRejectionReport(status.RejectionReportUrl);
Console.WriteLine("Report was written to " + reportPath);
}
}
// Gets the rejection report. The report is
// named MerchantCatalogReport.csv.
private static string GetRejectionReport(string url)
{
DownloadReport(url, downloadPath);
var reportPath = DecompressZipFile(downloadPath);
return reportPath;
}
// Downloads the rejection report and writes
// it to the specified folder.
private static void DownloadReport(string uri, string path)
{
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
request.Accept = "application/x-zip-compressed";
var response = (HttpWebResponse)request.GetResponse();
var fileInfo = new FileInfo(path);
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
}
var bufferSize = 100 * 1024;
var fileStream = new FileStream(fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize);
using (BinaryReader reader = new BinaryReader(response.GetResponseStream()))
{
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
while (true)
{
byte[] buffer = reader.ReadBytes(bufferSize);
writer.Write(buffer);
if (buffer.Length != bufferSize)
{
break;
}
}
}
}
fileStream.Close();
}
// Decompresses the report.
private static string DecompressZipFile(String path)
{
var fileInfo = new FileInfo(downloadPath);
var decompressedFileName = fileInfo.Name.Remove(fileInfo.Name.Length - fileInfo.Extension.Length);
var decompressedPath = fileInfo.Directory.FullName + "\\" + decompressedFileName;
ZipFile.ExtractToDirectory(downloadPath, decompressedPath);
return decompressedPath;
}
// Print errors.
private static void PrintErrors(ContentError contentError)
{
Console.WriteLine("HTTP status code: " + contentError.Error.Code);
foreach (Error error in contentError.Error.Errors)
{
Console.WriteLine("reason: {0}\nmessage: {1}\nlocation type: {2}\nlocation: {3}\n",
error.Reason, error.Message, error.LocationType, error.Location);
}
}
// Gets the AuthenticationToken and DeveloperToken headers
// that are required to call the API. This example use OAuth
// instead of using the legacy credentials (username and password).
private static WebHeaderCollection GetCredentialHeaders()
{
// TODO: Add logic to get the logged on user's refresh token
// from secured storage.
tokens = GetOauthTokens(storedRefreshToken);
var headers = new WebHeaderCollection();
headers.Add("AuthenticationToken", tokens.AccessToken);
headers.Add("DeveloperToken", devToken);
return headers;
}
// Gets the OAuth tokens. If the refresh token doesn't exist, get
// the user's consent and a new access and refresh token.
private static CodeGrantOauth GetOauthTokens(string refreshToken)
{
CodeGrantOauth auth = null;
if (string.IsNullOrEmpty(refreshToken))
{
auth = new CodeGrantOauth(clientId, clientSecret);
auth.GetAccessToken();
}
else
{
auth = new CodeGrantOauth(clientId, clientSecret);
auth.RefreshAccessToken(refreshToken);
// Refresh tokens can become invalid for several reasons
// such as the user's password changed.
if (!string.IsNullOrEmpty(auth.Error))
{
auth = GetOauthTokens(null);
}
}
// TODO: Store the new refresh token in secured storage
// for the logged on user.
storedRefreshToken = auth.RefreshToken;
tokenExpiration = DateTime.Now.AddSeconds(auth.Expiration);
return auth;
}
}
// Reporting classes.
public class CatalogStatus
{
[JsonProperty("catalogId")]
public ulong CatalogId { get; set; }
[JsonProperty("publishedCount")]
public ulong PublishedCount { get; set; }
[JsonProperty("rejectedCount")]
public ulong RejectedCount { get; set; }
[JsonProperty("rejectionReportUrl")]
public string RejectionReportUrl { get; set; }
}
// Classes used to handle errors.
public class Error
{
[JsonProperty("location")]
public string Location { get; set; }
[JsonProperty("locationType")]
public string LocationType { get; set; }
[JsonProperty("domain")]
public string Domain { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("reason")]
public string Reason { get; set; }
}
public class ErrorCollection
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("errors")]
public List<Error> Errors { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
public class ContentError
{
[JsonProperty("error")]
public ErrorCollection Error { get; set; }
}
}
package status.capi.microsoft.com;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.zip.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// Uses Jackson to serialize and deserialize JSON.
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
class Catalogs {
// Jackson object used to serialize and deserialize JSON.
private static ObjectMapper jsonMapper = new ObjectMapper();
private static String accessToken = "<accesstokengoeshere>";
public static String devToken = "<devtokengoeshere>";
// URI templates used to get resources.
public static final String BaseUri = "https://content.api.bingads.microsoft.com/shopping/v9.1";
public static final String BmcUri = BaseUri + "/bmc/%d";
// Replace with your IDs.
public static long merchantId = <merchantidgoeshere>;
public static long catalogId = <catalogidgoeshere>;
// The path where the uncompressed report is written to.
public static String downloadPath = "c:\\reports\\";
// Gets the status of the products in the specified catalog. If any
// of the products failed editorial review, the example downloads
// the report.
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
try {
Map<String, String> headers = getCredentialHeaders();
String url = String.format(StatusUri, merchantId, catalogId);
// Get the catalog's status.
CatalogStatus status = (CatalogStatus) getResource(url, headers, CatalogStatus.class);
// Prints the status and downloads the report.
printCatalogStatus(status, catalogId);
System.out.println();
}
catch (CapiException e) {
if (e.getErrors() != null){
printErrors(e.getErrors());
}
else
{
System.out.println(e.getMessage());
}
}
catch (IOException e) {
System.out.println(e.getMessage());
}
catch (Exception e) {
System.out.println("\n" + e.getMessage());
}
}
// Get tokens for the authentication headers.
private static Map<String, String> getCredentialHeaders()
{
// TODO: Add logic to get the user's OAuth token.
Map<String, String> headers = new HashMap<String, String>();
headers.put("AuthenticationToken", accessToken);
headers.put("DeveloperToken", devToken);
return headers;
}
// Generic method to get a resource from the specified URL.
private static Object getResource(String uri, Map<String, String> headers, Class\<?> type) throws IOException, CapiException
{
Object resource = null;
HttpURLConnection connection = null;
URL url;
InputStream istream = null;
BufferedReader reader = null;
final int BUFFER_SIZE = 2048;
try {
url = new URL(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
for (Entry\<String, String> header : headers.entrySet())
{
connection.setRequestProperty(header.getKey(), header.getValue());
}
connection.setUseCaches(false);
int statusCode = connection.getResponseCode();
if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
istream = connection.getErrorStream();
}
else {
istream = connection.getInputStream();
}
StringBuffer response = new StringBuffer();
reader = new BufferedReader(new InputStreamReader(istream));
char[] buffer = new char[BUFFER_SIZE];
int len = 0;
while ((len = reader.read(buffer)) != -1) {
response.append(new String(buffer, 0, len));
}
System.out.println(response);
if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
if (statusCode == HttpURLConnection.HTTP_BAD_REQUEST) {
ContentError errors = jsonMapper.readValue(response.toString(), ContentError.class);
throw new CapiException("Batch request failed.\n", errors.getError().getErrors());
}
else {
throw new CapiException(response.toString());
}
}
resource = jsonMapper.readValue(response.toString(), type);
}
finally {
if (connection != null) {
connection.disconnect();
}
if (reader != null) {
reader.close();
}
if (istream != null) {
istream.close();
}
}
return resource;
}
// Prints the catalog's status. If a product failed review,
// downloads the rejection report.
private static void printCatalogStatus(CatalogStatus status, long catalogId) throws IOException, CapiException
{
System.out.printf("Catalog %d Status Report\n", catalogId);
System.out.println("Published offers: " + status.getPublishedCount());
System.out.println("Rejected offers: " + status.getRejectedCount());
if (status.getRejectedCount() > 0)
{
String reportPath = getRejectionReport(status.getRejectionReportUrl());
System.out.println("Report was written to " + reportPath);
}
}
// Gets the rejection report. The report is
// named MerchantCatalogReport.csv.
private static String getRejectionReport(String url) throws IOException, CapiException
{
return downloadReport(url, downloadPath);
}
// Downloads the rejection report and writes
// it to the specified path.
private static String downloadReport(String uri, String path) throws IOException, CapiException
{
HttpURLConnection connection = null;
URL url;
InputStream istream = null;
BufferedReader reader = null;
ZipInputStream zipIStream = null;
BufferedOutputStream dest = null;
String downloadFilePath = null;
final int BUFFER_SIZE = 2048;
try {
url = new URL(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/zip");
connection.setUseCaches(false);
int statusCode = connection.getResponseCode();
StringBuffer response = new StringBuffer();
if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
istream = connection.getErrorStream();
reader = new BufferedReader(new InputStreamReader(istream));
char[] buffer = new char[2048];
int len = 0;
while ((len = reader.read(buffer)) != -1) {
response.append(new String(buffer, 0, len));
}
throw new CapiException(response.toString());
}
istream = connection.getInputStream();
zipIStream = new ZipInputStream(new BufferedInputStream(istream));
// Build the download path.
String filename = zipIStream.getNextEntry().getName();
String filenameParts[] = filename.split(Pattern.quote("."));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddhhmmss");
String timestamp = LocalDateTime.now().format(formatter);
downloadFilePath = path + filenameParts[0] + "_" + timestamp + "." + filenameParts[1];
// The download file is ZIP compressed. Uncompress the
// file and write it to the download path.
System.out.println("\nExtracting: " + filename);
int len;
byte buffer[] = new byte[BUFFER_SIZE];
FileOutputStream ostream = new FileOutputStream(downloadFilePath);
dest = new BufferedOutputStream(ostream, BUFFER_SIZE);
while ((len = zipIStream.read(buffer, 0, BUFFER_SIZE)) != -1) {
dest.write(buffer, 0, len);
}
dest.flush();
}
finally {
if (connection != null) {
connection.disconnect();
}
if (reader != null) {
reader.close();
}
if (istream != null) {
istream.close();
}
if (zipIStream != null) {
zipIStream.close();
}
if (dest != null) {
dest.close();
}
}
return downloadFilePath;
}
// Print errors.
private static void printErrors(List<Error> errors)
{
for (Error error : errors)
{
System.out.printf("reason: %s\nmessage: %s\ndomain: %s\n\n",
error.getReason(), error.getMessage(), error.getDomain());
}
}
}
@SuppressWarnings("serial")
class CapiException extends Exception {
private List<Error> errors = null;
public CapiException(String message) {
super(message);
}
public CapiException(String message, List<Error> errors) {
super(message);
this.errors = errors;
}
public CapiException(Throwable cause) {
super(cause);
}
public CapiException(String message, Throwable cause) {
super(message, cause);
}
public List<Error> getErrors() { return this.errors; }
public void setErrors(List<Error> value) { this.errors = value; }
}
// Reporting classes.
class CatalogStatus
{
private long catalogId;
private long publishedCount;
private long rejectedCount;
private String rejectionReportUrl;
public long getCatalogId() { return this.catalogId; }
public void setCatalogId(long value) { this.catalogId = value; }
public long getPublishedCount() { return this.publishedCount; }
public void setPublishedCount(long value) { this.publishedCount = value; }
public long getRejectedCount() { return this.rejectedCount; }
public void setRejectedCount(long value) { this.rejectedCount = value; }
public String getRejectionReportUrl() { return this.rejectionReportUrl; }
public void setRejectionReportUrl(String value) { this.rejectionReportUrl = value; }
}
//Classes used to handle errors.
class Error
{
private String location;
private String locationType;
private String domain;
private String message;
private String reason;
public String getLocation() { return this.location; }
public String getLocationType() { return this.locationType; }
public String getDomain() { return this.domain; }
public String getMessage() { return this.message; }
public String getReason() { return this.reason; }
}
class ErrorCollection
{
private List<Error> errors;
public List<Error> getErrors() { return this.errors; }
}
class ContentError
{
private ErrorCollection error;
public ErrorCollection getError() { return this.error; }
}
"""Content API Catalog Status Example"""
import json
import zipfile
import os
import requests
BASE_URI = 'https://content.api.bingads.microsoft.com/shopping/v9.1'
BMC_URI = BASE_URI + '/bmc/{0}'
CLIENT_ID = '<CLIENTIDGOESHERE>'
DEV_TOKEN = '<DEVELOPERTOKENGOESHERE>'
MERCHANT_ID = '<STOREIDGOESHERE>'
AUTHENTICATION_TOKEN = '<AUTHENTICATIONTOKENGOESHERE>'
AUTHENTICATION_HEADERS = {'DeveloperToken': DEV_TOKEN, 'AuthenticationToken': AUTHENTICATION_TOKEN}
DOWNLOAD_PATH = "MerchantCatalogReport.zip"
def main():
"""The main entry point of this example"""
catalog_status = get_catalog_status_report(retrieve_default_catalog()['id'])
print_json(catalog_status)
if catalog_status['rejectedCount'] > 0:
report_path = get_rejection_report(catalog_status['rejectionReportUrl'])
zipped_report = zipfile.ZipFile(report_path, 'r') # read the zipfile
zipped_report.extractall('.')
print('Report was written to ' + os.path.join(os.getcwd(), 'MerchantCatalogReport.csv'))
def get_rejection_report(rejection_report_url, download_path=DOWNLOAD_PATH):
"""Download rejection report"""
headers = dict(AUTHENTICATION_HEADERS)
headers['Accept'] = 'application/x-zip-compressed'
report_response = requests.get(rejection_report_url, headers)
report_response.raise_for_status()
content = report_response.content
print(content)
report_file = open(DOWNLOAD_PATH, 'wb')
report_file.write(content)
report_file.close()
return download_path
def retrieve_default_catalog():
"""Retrieve the default catalog"""
catalogs = list_catalogs()
for catalog in catalogs:
if catalog['isDefault']:
return catalog
return None
CATALOGS_URI = BMC_URI + "/catalogs"
def list_catalogs():
"""list catalogs for the current merchant"""
url = CATALOGS_URI.format(MERCHANT_ID)
response = requests.get(url, headers=AUTHENTICATION_HEADERS)
response.raise_for_status()
return json.loads(response.text)['catalogs']
def print_json(obj):
"""Print the object as json"""
print(json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': ')))
# Main execution
if __name__ == '__main__':
main()