Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,30 @@ Tests run against a stubbed transport, so nothing touches the network.
## License

MIT. See [LICENSE](LICENSE).

### Google Ads

Campaigns, ad groups, ads, audiences and insights are on `client.ads` and dispatch by
connection. What only Google has is under `client.ads.google`:

```ruby
keywords = client.ads.google.keywords(
connection_id: 'c4d5e6f7-…',
customer_id: '1234567890'
)

client.ads.google.create_keyword(
workspace_id: '7d2b8c11-…',
connection_id: 'c4d5e6f7-…',
customer_id: '1234567890',
ad_group_id: '1234567890~adGroup~77',
text: 'running shoes',
match_type: 'EXACT'
)
```

Also `keyword_ideas`, `keyword_metrics`, `search_terms`, `bid_strategies`, `ad_schedule`
and `set_ad_schedule`, the negative keyword lists, `assets` and `asset_groups`,
`local_services_leads`, the conversion methods, and `query` for a raw read-only GAQL
SELECT. Changes need the `publish` scope as well as `ads`; `customer_id` has to name an
account the connection's grant reaches.
1 change: 1 addition & 0 deletions lib/fopost/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require 'fopost/resources/account_groups'
require 'fopost/resources/accounts'
require 'fopost/resources/ads'
require 'fopost/resources/google_ads'
require 'fopost/resources/ai'
require 'fopost/resources/broadcasts'
require 'fopost/resources/contacts'
Expand Down
104 changes: 104 additions & 0 deletions lib/fopost/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1368,4 +1368,108 @@ class Enrollment < Model
# On a skipped step, the reason it was skipped.
attribute :error
end

# ─── Google Ads ───────────────────────────────────────────────────

# A keyword on an ad group. `id` is `<customer_id>~keyword~<ad_group_id>~<criterion_id>`.
class GoogleKeyword < Model
attribute :id
attribute :ad_group_id
attribute :text
attribute :match_type
attribute :status
# Account currency, minor units.
attribute :cpc_bid_minor
attribute :negative
end

class GoogleKeywordIdea < Model
attribute :text
attribute :avg_monthly_searches
attribute :competition
attribute :low_top_of_page_bid_minor
attribute :high_top_of_page_bid_minor
end

class GoogleSearchTerm < Model
attribute :term
attribute :ad_group_id
attribute :status
attribute :metrics
end

class GoogleBidStrategy < Model
attribute :id
attribute :name
attribute :type
attribute :status
attribute :campaign_count
end

class GoogleAdScheduleSlot < Model
attribute :id
attribute :day_of_week
attribute :start_hour
attribute :end_hour
attribute :bid_modifier
end

# A negative keyword list.
class GoogleSharedSet < Model
attribute :id
attribute :name
attribute :type
attribute :member_count
end

# A sitelink, callout or structured snippet.
class GoogleAsset < Model
attribute :id
attribute :name
attribute :type
attribute :text
attribute :final_url
end

# Where an asset is attached; an asset with no links serves nowhere.
class GoogleAssetLink < Model
attribute :id
attribute :asset_id
attribute :level
attribute :owner_id
attribute :field_type
attribute :status
end

# A Performance Max asset group.
class GoogleAssetGroup < Model
attribute :id
attribute :campaign_id
attribute :name
attribute :status
attribute :final_urls
end

# A lead from Local Services Ads, read live and never stored.
class GoogleLocalServicesLead < Model
attribute :id
attribute :category
attribute :service
attribute :contact_name
attribute :phone
attribute :email
attribute :status
attribute :type
attribute :created_at
end

class GoogleConversionAction < Model
attribute :id
attribute :name
attribute :category
attribute :status
attribute :type
attribute :counting_type
attribute :value_minor
end
end
17 changes: 16 additions & 1 deletion lib/fopost/resources/ads.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@

module Fopost
module Resources
# `client.ads` — Meta ads, catalogs, audiences, the ad archive and lead forms.
# `client.ads` — ads, catalogs, audiences, the ad archive and lead forms.
#
# Meta is what this resource covers; the Google-only surface is `client.ads.google`.
#
# Every method needs the `ads` scope. {#boost}, {#create}, {#set_status}
# and {#delete} spend money and also need `publish`, as do the create,
# update, delete and duplicate methods for campaigns, ad sets and network
# ads, and {#bulk_set_status}.
class Ads < Base
# The Search surface no other network has: keywords, assets, conversions, GAQL.
def google
@google ||= GoogleAds.new(http)
end

# Boosts and ads created through FoPost, with insights from their last refresh.
def list(workspace_id: nil)
parse_list(Ad, unwrap(http.get('/ads', { 'workspace_id' => workspace_id })))
Expand Down Expand Up @@ -41,6 +48,14 @@ def authorize_meta(workspace_id:, method: nil, return_to: nil)
url.nil? ? '' : url.to_s
end

# The Google login URL; the caller finishes it in their own browser.
def authorize_google(workspace_id:, return_to: nil)
body = compact_nil('workspaceId' => workspace_id, 'returnTo' => return_to)
result = unwrap(http.post('/ads/connections/google/authorize', body))
url = result.is_a?(Hash) ? result['url'] : nil
url.nil? ? '' : url.to_s
end

# Also deletes every ad record created through the connection.
def delete_connection(connection_id, workspace_id:)
http.request(:delete, "/ads/connections/#{connection_id}", params: { 'workspace_id' => workspace_id })
Expand Down
Loading
Loading