Giter VIP home page Giter VIP logo

nodejs-google-adwords's Introduction

nodejs google adwords

NPM Downloads LICENSE Build Status Coverage Status StackShare

Google Ads API Client Library for Node.js. This library is developed for Google Adwords SOAP + WSDL API (v201809).

OAuth

Replace your GCP OAuth 2.0 client ID and open this link in browser,

https://accounts.google.com/o/oauth2/auth?client_id={Your Client ID}&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadwords&redirect_uri=urn:ietf:wg:oauth:2.0:oob&access_type=offline&approval_prompt=auto

The OAuth2.0 Client type should be other, not web application

If you use client id which client type is web application, you will get below error:

After finish the oauth workflow, you will get authorization code, for example: 4/0wA_JBMyfVH1ZEqZlAr0sOn_XmdzUrBgCjrpi9fVs9TudrjZUDzuUmU

Using authorization code exchange credentials:

curl \
  -d code=4/MgGqR_qEUkzq95LlP_Am8clUbX8t733PvtoMuZ_xsmAA8NHdjK07xXo \
  -d client_id=<client id> \
  -d client_secret=<client secret> \
  -d redirect_uri=urn:ietf:wg:oauth:2.0:oob \
  -d grant_type=authorization_code https://accounts.google.com/o/oauth2/token

Credentials response:

{
  "access_token": "<Access token>",
  "expires_in": 3600,
  "refresh_token": "<Refresh token>",
  "scope": "https://www.googleapis.com/auth/adwords",
  "token_type": "Bearer"
}

You can revoke your access token from: https://myaccount.google.com/u/0/permissions

Above workflow is only for server-side local development without a front-end(client-side), after you make a front-end application, then you can create a OAuth2.0 Client on GCP with web application type and set up your Authorized JavaScript origins and Authorized redirect URIs like below:

Then, when user perform the oauth workflow, you can confirm the oauth workflow on server-side, and store the refresh_token, access_token and other informations in your database. When user click create campaign button on your front-end application, it will send a HTTP request to your server-side, then, you can get the user's access_token from database, can call google adwords api using this access_token.

Environment variables

ADWORDS_CLIENT_ID=<GCP OAuth 2.0 client ID>
ADWORDS_SECRET=<GCP OAuth 2.0 client secret>
ADWORDS_DEVELOPER_TOKEN=<Google Adwords Developer Token>
ADWORDS_CLIENT_CUSTOMER_ID=153-935-9847
ADWORDS_USER_AGENT=Google Ads API Client Library for Node.js
ADWORDS_REFRESH_TOKEN=<OAuth Refresh Token>

Put above environment variables into .env file for local development.

Usage

Initialize AdwordsService with above environment variables

const adwordsService = new AdWordsService({
  clientCustomerId: credentials.ADWORDS_CLIENT_CUSTOMER_ID,
  developerToken: credentials.ADWORDS_DEVELOPER_TOKEN,
  userAgent: credentials.ADWORDS_USER_AGENT,
  clientId: credentials.ADWORDS_CLIENT_ID,
  clientSecret: credentials.ADWORDS_SECRET,
  credentials: {
    refresh_token: credentials.ADWORDS_REFRESH_TOKEN,
  },
});

Get budgets by page:

async function getByPage() {
  const budgetService = adwordsService.getService('BudgetService');
  const paging: IPaging = {
    startIndex: 0,
    numberResults: 2,
  };
  return await budgetService.getByPage(paging);
}

Create a budget:

async function createBudget() {
  const budgetService = adwordsService.getService('BudgetService');

  const budget: IBudget = {
    name: faker.lorem.word(),
    amount: {
      microAmount: BudgetService.UNIT,
    },
    deliveryMethod: Budget.BudgetDeliveryMethod.STANDARD,
    isExplicitlyShared: false,
    status: Budget.BudgetStatus.ENABLED,
  };

  return await budgetService.add(budget);
}

Get campaigns by page:

async function getCampaignsByPages() {
  const paging: IPaging = {
    startIndex: 0,
    numberResults: 1,
  };
  return await campaignService.getByPage(paging);
}

Same usage for other Google Adwords resources

TODO

References

nodejs-google-adwords's People

Contributors

dependabot[bot] avatar mrdulin avatar vtrifonov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

nodejs-google-adwords's Issues

AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR

operation: Update the status of multiple ad group ads.

error:

soap:Client: [AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR @ operations[0].operand.ad, AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR @ operations[1].operand.ad]: {"ApiExceptionFault":{"message":"[AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR @ operations[0].operand.ad, AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR @ operations[1].operand.ad]","ApplicationException.Type":"ApiException","errors":[{"attributes":{"xsi:type":"AdError"},"fieldPath":"operations[0].operand.ad","fieldPathElements":[{"field":"operations","index":0},{"field":"operand"},{"field":"ad"}],"trigger":"","errorString":"AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR","ApiError.Type":"AdError","reason":"CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR"},{"attributes":{"xsi:type":"AdError"},"fieldPath":"operations[1].operand.ad","fieldPathElements":[{"field":"operations","index":1},{"field":"operand"},{"field":"ad"}],"trigger":"","errorString":"AdError.CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR","ApiError.Type":"AdError","reason":"CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR"}]}}

test case:

  it('#update', async () => {
    const ads: IAd[] = [
      {
        id: '331943088184',
        attributes: {
          'xsi:type': 'ExpandedTextAd'
        }
      },
      {
        id: '331195148581',
        attributes: {
          'xsi:type': 'ExpandedTextAd'
        }
      }
    ];
    const adGroupId = '72029524744';
    const adGroupAds: IAdGroupAd[] = ads.map((ad: IAd) => {
      const adGroupAd: IAdGroupAd = {
        adGroupId,
        ad,
        status: AdGroupAd.Status.ENABLED
      };
      return adGroupAd;
    });

    const actualValue = await adGroupAdService.update(adGroupAds);
  });

Promises not resolved correctly

With the examples from the readme, I alway get undefined when calling getByPage or getAll method. However if the verbose is enabled the data gets printed to the console, so I guess the promises are not being resolved correctly.

DistinctError.DUPLICATE_ELEMENT

Error:

soap:Client: [DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[28], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[29], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[31], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[33], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[99]]: {"ApiExceptionFault":{"message":"[DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[28], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[29], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[31], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[33], DistinctError.DUPLICATE_ELEMENT @ serviceSelector.fields[99]]","ApplicationException.Type":"ApiException","errors":[{"attributes":{"xsi:type":"DistinctError"},"fieldPath":"serviceSelector.fields[28]","fieldPathElements":[{"field":"serviceSelector"},{"field":"fields","index":28}],"trigger":"","errorString":"DistinctError.DUPLICATE_ELEMENT","ApiError.Type":"DistinctError","reason":"DUPLICATE_ELEMENT"},{"attributes":{"xsi:type":"DistinctError"},"fieldPath":"serviceSelector.fields[29]","fieldPathElements":[{"field":"serviceSelector"},{"field":"fields","index":29}],"trigger":"","errorString":"DistinctError.DUPLICATE_ELEMENT","ApiError.Type":"DistinctError","reason":"DUPLICATE_ELEMENT"},{"attributes":{"xsi:type":"DistinctError"},"fieldPath":"serviceSelector.fields[31]","fieldPathElements":[{"field":"serviceSelector"},{"field":"fields","index":31}],"trigger":"","errorString":"DistinctError.DUPLICATE_ELEMENT","ApiError.Type":"DistinctError","reason":"DUPLICATE_ELEMENT"},{"attributes":{"xsi:type":"DistinctError"},"fieldPath":"serviceSelector.fields[33]","fieldPathElements":[{"field":"serviceSelector"},{"field":"fields","index":33}],"trigger":"","errorString":"DistinctError.DUPLICATE_ELEMENT","ApiError.Type":"DistinctError","reason":"DUPLICATE_ELEMENT"},{"attributes":{"xsi:type":"DistinctError"},"fieldPath":"serviceSelector.fields[99]","fieldPathElements":[{"field":"serviceSelector"},{"field":"fields","index":99}],"trigger":"","errorString":"DistinctError.DUPLICATE_ELEMENT","ApiError.Type":"DistinctError","reason":"DUPLICATE_ELEMENT"}]}}

SelectorFields:

private static readonly selectorFields = [
    'AccentColor',
    'AdGroupId',
    'AdStrengthInfo',
    'AdType',
    'AdvertisingId',
    'AllowFlexibleColor',
    'Automated',
    'BaseAdGroupId',
    'BaseCampaignId',
    'BusinessName',
    'CallOnlyAdBusinessName',
    'CallOnlyAdCallTracked',
    'CallOnlyAdConversionTypeId',
    'CallOnlyAdCountryCode',
    'CallOnlyAdDescription1',
    'CallOnlyAdDescription2',
    'CallOnlyAdDisableCallConversion',
    'CallOnlyAdPhoneNumber',
    'CallOnlyAdPhoneNumberVerificationUrl',
    'CallToActionText',
    'CreationTime',
    'CreativeFinalAppUrls',
    'CreativeFinalMobileUrls',
    'CreativeFinalUrlSuffix',
    'CreativeFinalUrls',
    'CreativeTrackingUrlTemplate',
    'CreativeUrlCustomParameters',
    'Description',
    'Description',
    'Description',
    'Description1',
    'Description1',
    'Description2',
    'Description2',
    'DevicePreference',
    'Dimensions',
    'DisplayUploadAdGmailTeaserBusinessName',
    'DisplayUploadAdGmailTeaserDescription',
    'DisplayUploadAdGmailTeaserHeadline',
    'DisplayUploadAdGmailTeaserLogoImage',
    'DisplayUrl',
    'ExpandedDynamicSearchCreativeDescription2',
    'ExpandedTextAdDescription2',
    'ExpandedTextAdHeadlinePart3',
    'ExpandingDirections',
    'FileSize',
    'FormatSetting',
    'GmailHeaderImage',
    'GmailMarketingImage',
    'GmailTeaserBusinessName',
    'GmailTeaserDescription',
    'GmailTeaserHeadline',
    'GmailTeaserLogoImage',
    'Headline',
    'HeadlinePart1',
    'HeadlinePart2',
    'Height',
    'Id',
    'ImageCreativeName',
    'IndustryStandardCommercialIdentifier',
    'IsCookieTargeted',
    'IsTagged',
    'IsUserInterestTargeted',
    'Labels',
    'LandscapeLogoImage',
    'LogoImage',
    'LongHeadline',
    'MainColor',
    'MarketingImage',
    'MarketingImageCallToActionText',
    'MarketingImageCallToActionTextColor',
    'MarketingImageDescription',
    'MarketingImageHeadline',
    'MediaId',
    'MimeType',
    'MultiAssetResponsiveDisplayAdAccentColor',
    'MultiAssetResponsiveDisplayAdAllowFlexibleColor',
    'MultiAssetResponsiveDisplayAdBusinessName',
    'MultiAssetResponsiveDisplayAdCallToActionText',
    'MultiAssetResponsiveDisplayAdDescriptions',
    'MultiAssetResponsiveDisplayAdDynamicSettingsPricePrefix',
    'MultiAssetResponsiveDisplayAdDynamicSettingsPromoText',
    'MultiAssetResponsiveDisplayAdFormatSetting',
    'MultiAssetResponsiveDisplayAdHeadlines',
    'MultiAssetResponsiveDisplayAdLandscapeLogoImages',
    'MultiAssetResponsiveDisplayAdLogoImages',
    'MultiAssetResponsiveDisplayAdLongHeadline',
    'MultiAssetResponsiveDisplayAdMainColor',
    'MultiAssetResponsiveDisplayAdMarketingImages',
    'MultiAssetResponsiveDisplayAdSquareMarketingImages',
    'MultiAssetResponsiveDisplayAdYouTubeVideos',
    'Path1',
    'Path2',
    'PolicySummary',
    'PricePrefix',
    'ProductImages',
    'ProductVideoList',
    'PromoText',
    'ReadyToPlayOnTheWeb',
    'ReadyToPlayOnTheWeb',
    'ReferenceId',
    'ResponsiveSearchAdDescriptions',
    'ResponsiveSearchAdHeadlines',
    'ResponsiveSearchAdPath1',
    'ResponsiveSearchAdPath2',
    'RichMediaAdCertifiedVendorFormatId',
    'RichMediaAdDuration',
    'RichMediaAdImpressionBeaconUrl',
    'RichMediaAdName',
    'RichMediaAdSnippet',
    'RichMediaAdSourceUrl',
    'RichMediaAdType',
    'ShortHeadline',
    'SourceUrl',
    'SquareMarketingImage',
    'Status',
    'SystemManagedEntitySource',
    'TemplateAdDuration',
    'TemplateAdName',
    'TemplateAdUnionId',
    'TemplateElementFieldName',
    'TemplateElementFieldText',
    'TemplateElementFieldType',
    'TemplateId',
    'TemplateOriginAdId',
    'UniqueName',
    'UniversalAppAdDescriptions',
    'UniversalAppAdHeadlines',
    'UniversalAppAdHtml5MediaBundles',
    'UniversalAppAdImages',
    'UniversalAppAdMandatoryAdText',
    'UniversalAppAdYouTubeVideos',
    'Url',
    'UrlData',
    'Urls',
    'VideoTypes',
    'Width',
    'YouTubeVideoIdString'
  ];

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.