naver-searchad-api 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +140 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/naver/searchad/api.rb +35 -0
- data/lib/naver/searchad/api/ad-keyword/service.rb +78 -0
- data/lib/naver/searchad/api/ad/service.rb +64 -0
- data/lib/naver/searchad/api/adgroup/service.rb +62 -0
- data/lib/naver/searchad/api/auth.rb +65 -0
- data/lib/naver/searchad/api/campaign/service.rb +52 -0
- data/lib/naver/searchad/api/core/api_command.rb +96 -0
- data/lib/naver/searchad/api/core/base_service.rb +91 -0
- data/lib/naver/searchad/api/core/http_command.rb +145 -0
- data/lib/naver/searchad/api/core/logging.rb +15 -0
- data/lib/naver/searchad/api/errors.rb +57 -0
- data/lib/naver/searchad/api/options.rb +49 -0
- data/lib/naver/searchad/api/version.rb +24 -0
- data/naver-searchad-api.gemspec +33 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 33ea00d32f02d980670575aa962514ce94a1358b
|
4
|
+
data.tar.gz: ffdc63d9829036d32449d0e9b6530174d4116f65
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab19d8d053206588737c1d1935fcd25c52193a96d0380c471be96673fde68a28a3fe7c9992feffaf099f739543dc930f0da43a06719462ab52466f6a83454789
|
7
|
+
data.tar.gz: cc778485156e7db9484c908ce917d356dd696d52c48d4463712bca53cd5e2e2d5cc8ac18b20f76bacee442b764647be05694c621b5aa6b201ff86dabefb05fe1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Forward3D
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# Naver Searchad API
|
2
|
+
|
3
|
+
A Client GEM for [Naver Searchad API](http://naver.github.io/searchad-apidoc/#/guides)
|
4
|
+
|
5
|
+
## Alpha
|
6
|
+
|
7
|
+
This library is in Alpha. We will make an effort to support the library, but we reserve the right to make incompatible
|
8
|
+
changes when necessary.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'naver-searchad-api'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install naver-searchad-api
|
25
|
+
|
26
|
+
## Compatibility
|
27
|
+
|
28
|
+
naver-searchad-api supports the following Ruby implementations:
|
29
|
+
|
30
|
+
* MRI 2.0
|
31
|
+
* MRI 2.1
|
32
|
+
* MRI 2.2
|
33
|
+
* MRI 2.3
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
### Basic usage
|
38
|
+
|
39
|
+
To use an API, instantiate the service. For example to use the Campaign API:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'naver/searchad/api/campaign/service'
|
43
|
+
|
44
|
+
Campaign = Naver::Searchad::Api::Campaign # Alias the module
|
45
|
+
campaign = Campaign::Service.new
|
46
|
+
campaign.authorization = Naver::Searchad::Api::Auth.get_application_default # See Below Authorization
|
47
|
+
|
48
|
+
# Read campaigns by ids
|
49
|
+
campaign.list_campaigns_by_ids(['campaign_id_1', 'campaign_id_2']) do |res, err|
|
50
|
+
puts res
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create a campaign
|
54
|
+
campaign = { ..... }
|
55
|
+
campaign.create_campaign(campaign) do |res, err|
|
56
|
+
puts res
|
57
|
+
end
|
58
|
+
|
59
|
+
# Delete
|
60
|
+
campaign_id = ... # A campaign id to delete
|
61
|
+
campaign.delete_campaign(campaign_id)
|
62
|
+
```
|
63
|
+
|
64
|
+
### Naming conventions vs JSON representation
|
65
|
+
|
66
|
+
Object properties in the ruby client use the standard ruby convention for naming -- snake_case. This differs from the underlying JSON representation which typically uses camelCase for properties.
|
67
|
+
|
68
|
+
|
69
|
+
### Callbacks
|
70
|
+
|
71
|
+
A block can be specified when making calls. If present, the block will be called with the result or error, rather than
|
72
|
+
returning the result from the call or raising the error. Example:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# Read a campaign
|
76
|
+
campaign.get_campaign('campaign_id_1') do |res, err|
|
77
|
+
if err
|
78
|
+
# Handle error
|
79
|
+
else
|
80
|
+
# Handle response
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
## Authorization
|
86
|
+
|
87
|
+
[Naver Searchad API authorization](http://naver.github.io/searchad-apidoc/#/samples) is used to authorize applications.
|
88
|
+
|
89
|
+
### Authorization using environment variables
|
90
|
+
|
91
|
+
The [Naver Searchad API Auth](https://github.com/forward3d/naver-searchad-api/blob/master/lib/naver/searchad/api/auth.rb) also supports authorization via
|
92
|
+
environment variables. Simply set the following variables for your application:
|
93
|
+
|
94
|
+
```sh
|
95
|
+
NAVER_API_KEY="YOUR NAVER DEVELOPER API KEY"
|
96
|
+
NAVER_API_SECRET="YOUR NAVER DEVELOPER API SECRET"
|
97
|
+
NAVER_API_CLIENT_ID="YOUR NAVER DEVELOPER CLIENT ID"
|
98
|
+
```
|
99
|
+
|
100
|
+
## Logging
|
101
|
+
|
102
|
+
The client includes a `Logger` instance that can be used to capture debugging information.
|
103
|
+
|
104
|
+
To set the logging level for the client:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
Naver::Searchad::Api.logger.level = Logger::DEBUG
|
108
|
+
```
|
109
|
+
|
110
|
+
When running in a Rails environment, the client will default to using `::Rails.logger`. If you
|
111
|
+
prefer to use a separate logger instance for API calls, this can be changed via one of two ways.
|
112
|
+
|
113
|
+
The first is to provide a new logger instance:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
Naver::Searchad::Api.logger = Logger.new(STDERR)
|
117
|
+
```
|
118
|
+
|
119
|
+
## License
|
120
|
+
|
121
|
+
This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE).
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/forward3d/naver-searchad-api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
126
|
+
|
127
|
+
We encourage contributors to follow [Bozhidar's ruby style guide](https://github.com/bbatsov/ruby-style-guide) in this project.
|
128
|
+
|
129
|
+
Pull requests (with tests) are appreciated. Please help with:
|
130
|
+
|
131
|
+
* Reporting bugs
|
132
|
+
* Suggesting features
|
133
|
+
* Writing or improving documentation
|
134
|
+
* Fixing typos
|
135
|
+
* Cleaning whitespace
|
136
|
+
* Refactoring code
|
137
|
+
* Adding tests
|
138
|
+
|
139
|
+
If you report a bug and don't include a fix, please include a failing test.
|
140
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "naver/searchad/api"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'naver/searchad/api/version'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Naver
|
5
|
+
module Searchad
|
6
|
+
module Api
|
7
|
+
ROOT = File.expand_path('..', File.dirname(__dir__))
|
8
|
+
|
9
|
+
def self.logger
|
10
|
+
@logger ||= (rails_logger || default_logger)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :logger
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self.default_logger
|
20
|
+
logger = Logger.new(STDOUT)
|
21
|
+
logger.level = Logger::WARN
|
22
|
+
logger
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.rails_logger
|
26
|
+
if defined?(::Rails) && ::Rails.respond_to?(:logger) &&
|
27
|
+
!::Rails.logger.nil?
|
28
|
+
::Rails.logger
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
module AdKeyword
|
5
|
+
class Service < Naver::Searchad::Api::Core::BaseService
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super('https://api.naver.com/', 'ncc/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def list_ad_keywords_by_ids(ad_keyword_ids, options: nil, &block)
|
12
|
+
command = make_command(:get, 'keywords', options)
|
13
|
+
command.query['ids'] = ad_keyword_ids.join(',')
|
14
|
+
execute_command(command, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_ad_keywords_by_adgroup_id(adgroup_id, options: nil, &block)
|
18
|
+
command = make_command(:get, 'keywords', options)
|
19
|
+
command.query['nccAdgroupId'] = adgroup_id
|
20
|
+
execute_command(command, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_ad_keywords_by_label_id(label_id, options: nil, &block)
|
24
|
+
command = make_command(:get, 'keywords', options)
|
25
|
+
command.query['nccLabelId'] = label_id
|
26
|
+
execute_command(command, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_ad_keyword(ad_keyword_id, options: nil, &block)
|
30
|
+
command = make_command(:get, 'keywords/{ad_keyword_id}', options)
|
31
|
+
command.params['ad_keyword_id'] = ad_keyword_id
|
32
|
+
execute_command(command, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_ad_keywords(ad_keywords, adgroup_id, options: nil, &block)
|
36
|
+
ad_keywords.each { |kw| validates_presence_of(%w[keyword], kw) }
|
37
|
+
|
38
|
+
command = make_command(:post, 'keywords', options)
|
39
|
+
command.query['nccAdgroupId'] = adgroup_id
|
40
|
+
command.request_object = ad_keywords
|
41
|
+
execute_command(command, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_ad_keyword(ad_keyword, field: '', options: nil, &block)
|
45
|
+
validates_presence_of(%w[nccKeywordId nccAdgroupId], ad_keyword)
|
46
|
+
|
47
|
+
command = make_command(:put, 'keywords/{ad_keyword_id}', options)
|
48
|
+
command.params['ad_keyword_id'] = ad_keyword['nccKeywordId']
|
49
|
+
command.query['fields'] = field
|
50
|
+
command.request_object = ad_keyword
|
51
|
+
execute_command(command, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_ad_keywords(ad_keywords, field: '', options: nil, &block)
|
55
|
+
ad_keywords.each { |kw| validates_presence_of(%w[nccKeywordId nccAdgroupId], kw) }
|
56
|
+
|
57
|
+
command = make_command(:put, 'keywords', options)
|
58
|
+
command.query['fields'] = field
|
59
|
+
command.request_object = ad_keywords
|
60
|
+
execute_command(command, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete_ad_keyword(ad_keyword_id, options: nil, &block)
|
64
|
+
command = make_command(:delete, 'keywords/{ad_keyword_id}', options)
|
65
|
+
command.params['ad_keyword_id'] = ad_keyword_id
|
66
|
+
execute_command(command, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_ad_keywords(ad_keyword_ids, options: nil, &block)
|
70
|
+
command = make_command(:delete, 'keywords', options)
|
71
|
+
command.query['ids'] = ad_keyword_ids.join(',')
|
72
|
+
execute_command(command, &block)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
module Ad
|
5
|
+
class Service < Naver::Searchad::Api::Core::BaseService
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super('https://api.naver.com/', 'ncc/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def list(ad_ids, options: nil, &block)
|
12
|
+
command = make_command(:get, 'ads', options)
|
13
|
+
command.query['ids'] = ad_ids.join(',')
|
14
|
+
execute_command(command, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_by_adgroup_id(adgroup_id, options: nil, &block)
|
18
|
+
command = make_command(:get, 'ads', options)
|
19
|
+
command.query['nccAdgroupId'] = adgroup_id
|
20
|
+
execute_command(command, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_ad(ad_id, options: nil, &block)
|
24
|
+
command = make_command(:get, 'ads/{ad_id}', options)
|
25
|
+
command.params['ad_id'] = ad_id
|
26
|
+
execute_command(command, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_ad(ad, options: nil, &block)
|
30
|
+
validates_presence_of(%w[ad nccAdgroupId type], ad)
|
31
|
+
|
32
|
+
command = make_command(:post, 'ads', options)
|
33
|
+
command.request_object = ad
|
34
|
+
execute_command(command, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_ad(ad, field: '', options: nil, &block)
|
38
|
+
validates_presence_of(%w[nccAdId adAttr], ad)
|
39
|
+
|
40
|
+
command = make_command(:put, 'ads/{ad_id}', options)
|
41
|
+
command.params['ad_id'] = ad['nccAdId']
|
42
|
+
command.query['fields'] = field
|
43
|
+
command.request_object = ad
|
44
|
+
execute_command(command, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_ad(ad_id, options: nil, &block)
|
48
|
+
command = make_command(:delete, 'ads/{ad_id}', options)
|
49
|
+
command.params['ad_id'] = ad_id
|
50
|
+
execute_command(command, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def copy_ads(ad_ids, target_ad_group_id, user_lock, options: nil, &block)
|
54
|
+
command = make_command(:put, 'ads', options)
|
55
|
+
command.query['ids'] = ad_ids.join(',')
|
56
|
+
command.query['targetAdgroupId'] = target_ad_group_id
|
57
|
+
command.query['userLock'] = user_lock
|
58
|
+
execute_command(command, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
module Adgroup
|
5
|
+
class Service < Naver::Searchad::Api::Core::BaseService
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super('https://api.naver.com/', 'ncc/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def list_adgroups_by_ids(adgroup_ids, options: nil, &block)
|
12
|
+
command = make_command(:get, 'adgroups', options)
|
13
|
+
command.query['ids'] = adgroup_ids.join(',')
|
14
|
+
execute_command(command, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_adgroups_by_campaign_id(campaign_id, options: nil, &block)
|
18
|
+
command = make_command(:get, 'adgroups', options)
|
19
|
+
command.query['nccCampaignId'] = campaign_id
|
20
|
+
execute_command(command, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_adgroups_by_label_id(label_id, options: nil, &block)
|
24
|
+
command = make_command(:get, 'adgroups', options)
|
25
|
+
command.query['nccLabelId'] = label_id
|
26
|
+
execute_command(command, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_adgroup(adgroup_id, options: nil, &block)
|
30
|
+
command = make_command(:get, 'adgroups/{adgroup_id}', options)
|
31
|
+
command.params['adgroup_id'] = adgroup_id
|
32
|
+
execute_command(command, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_adgroup(adgroup, options: nil, &block)
|
36
|
+
validates_presence_of(%w[nccCampaignId pcChannelId mobileChannelId name], adgroup)
|
37
|
+
|
38
|
+
command = make_command(:post, 'adgroups', options)
|
39
|
+
command.request_object = adgroup
|
40
|
+
execute_command(command, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_adgroup(adgroup, field: nil, options: nil, &block)
|
44
|
+
validates_presence_of(%w[nccAdgroupId], adgroup)
|
45
|
+
|
46
|
+
command = make_command(:put, 'adgroups/{adgroup_id}', options)
|
47
|
+
command.params['adgroup_id'] = adgroup['nccAdgroupId']
|
48
|
+
command.query['fields'] = field if field
|
49
|
+
command.request_object = adgroup
|
50
|
+
execute_command(command, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_adgroup(adgroup_id, options: nil, &block)
|
54
|
+
command = make_command(:delete, 'adgroups/{adgroup_id}', options)
|
55
|
+
command.params['adgroup_id'] = adgroup_id
|
56
|
+
execute_command(command, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
module Auth
|
5
|
+
class CustomerAcccountCredentials
|
6
|
+
TIMESTAMP_HEADER = 'X-Timestamp'.freeze
|
7
|
+
API_KEY_HEADER = 'X-API-KEY'.freeze
|
8
|
+
CUSTOMER_HEADER = 'X-Customer'.freeze
|
9
|
+
SIGNATURE_HEADER = 'X-Signature'.freeze
|
10
|
+
|
11
|
+
attr_reader :api_key
|
12
|
+
attr_reader :api_secret
|
13
|
+
attr_reader :customer_id
|
14
|
+
|
15
|
+
def initialize(api_key, api_secret, customer_id)
|
16
|
+
@api_key = api_key
|
17
|
+
@api_secret = api_secret
|
18
|
+
@customer_id = customer_id
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply(hash, request_uri, method)
|
22
|
+
timestamp = Time.now.to_i
|
23
|
+
|
24
|
+
hash[TIMESTAMP_HEADER] = timestamp
|
25
|
+
hash[API_KEY_HEADER] = api_key
|
26
|
+
hash[CUSTOMER_HEADER] = customer_id
|
27
|
+
hash[SIGNATURE_HEADER] = generate_signature(api_secret, request_uri, method, timestamp)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def generate_signature(secret, request_uri, method, timestamp)
|
33
|
+
method = method.to_s.upcase if method.is_a?(Symbol)
|
34
|
+
|
35
|
+
Base64.encode64(OpenSSL::HMAC.digest(
|
36
|
+
OpenSSL::Digest::SHA256.new,
|
37
|
+
secret,
|
38
|
+
[timestamp, method, request_uri].join('.')
|
39
|
+
)).gsub("\n", '')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class DefaultCredentials
|
44
|
+
API_KEY_ENV_VAR = 'NAVER_API_KEY'.freeze
|
45
|
+
API_SECRET_ENV_VAR = 'NAVER_API_SECRET'.freeze
|
46
|
+
CUSTOMER_ID_ENV_VAR = 'NAVER_API_CLIENT_ID'.freeze
|
47
|
+
|
48
|
+
def self.from_env
|
49
|
+
CustomerAcccountCredentials.new(
|
50
|
+
ENV[API_KEY_ENV_VAR],
|
51
|
+
ENV[API_SECRET_ENV_VAR],
|
52
|
+
ENV[CUSTOMER_ID_ENV_VAR]
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_application_default
|
58
|
+
DefaultCredentials.from_env
|
59
|
+
end
|
60
|
+
|
61
|
+
module_function :get_application_default
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../core/base_service'
|
2
|
+
|
3
|
+
module Naver
|
4
|
+
module Searchad
|
5
|
+
module Api
|
6
|
+
module Campaign
|
7
|
+
class Service < Naver::Searchad::Api::Core::BaseService
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super('https://api.naver.com/', 'ncc/')
|
11
|
+
end
|
12
|
+
|
13
|
+
def list_campaigns_by_ids(campaign_ids, options: nil, &block)
|
14
|
+
command = make_command(:get, 'campaigns/', options)
|
15
|
+
command.query['ids'] = campaign_ids.join(',')
|
16
|
+
execute_command(command, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_campaign(campaign_id, options: nil, &block)
|
20
|
+
command = make_command(:get, 'campaigns/{campaign_id}', options)
|
21
|
+
command.params['campaign_id'] = campaign_id
|
22
|
+
execute_command(command, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_campaign(campaign, options: nil, &block)
|
26
|
+
validates_presence_of(%w[campaignTp name customerId], campaign)
|
27
|
+
|
28
|
+
command = make_command(:post, 'campaigns', options)
|
29
|
+
command.request_object = campaign
|
30
|
+
execute_command(command, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_campaign(campaign, field: nil, options: nil, &block)
|
34
|
+
validates_presence_of(%w[nccCampaignId], campaign)
|
35
|
+
|
36
|
+
command = make_command(:put, 'campaigns/{campaign_id}', options)
|
37
|
+
command.params['campaign_id'] = campaign['nccCampaignId']
|
38
|
+
command.query['fields'] = field if field
|
39
|
+
command.request_object = campaign
|
40
|
+
execute_command(command, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_campaign(campaign_id, options: nil, &block)
|
44
|
+
command = make_command(:delete, 'campaigns/{campaign_id}', options)
|
45
|
+
command.params['campaign_id'] = campaign_id
|
46
|
+
execute_command(command, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require_relative 'http_command'
|
2
|
+
|
3
|
+
module Naver
|
4
|
+
module Searchad
|
5
|
+
module Api
|
6
|
+
module Core
|
7
|
+
class ApiCommand < HttpCommand
|
8
|
+
JSON_CONTENT_TYPE = 'application/json'.freeze
|
9
|
+
#
|
10
|
+
# More error codes can be found at the below url
|
11
|
+
# https://github.com/naver/searchad-apidoc/blob/master/NaverSA_API_Error_Code_MAP.md
|
12
|
+
#
|
13
|
+
ERROR_CODE_MAPPING = {
|
14
|
+
'1002' => Naver::Searchad::Api::InvalidRequestError,
|
15
|
+
'1018' => Naver::Searchad::Api::NotEnoughPermissionError,
|
16
|
+
'3506' => Naver::Searchad::Api::DuplicatedCampaignNameError,
|
17
|
+
'3710' => Naver::Searchad::Api::DuplicatedAdgroupNameError,
|
18
|
+
}
|
19
|
+
|
20
|
+
attr_accessor :request_object
|
21
|
+
|
22
|
+
def prepare!
|
23
|
+
if request_object
|
24
|
+
self.header['Content-Type'] ||= JSON_CONTENT_TYPE
|
25
|
+
self.body = request_object.to_json
|
26
|
+
end
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def decode_response_body(content_type, body)
|
31
|
+
return super unless content_type
|
32
|
+
return nil unless content_type.start_with?(JSON_CONTENT_TYPE)
|
33
|
+
|
34
|
+
decoded_response = JSON.parse(body)
|
35
|
+
deep_snake_case_params!(decoded_response)
|
36
|
+
if decoded_response.kind_of?(Hash)
|
37
|
+
OpenStruct.new(decoded_response)
|
38
|
+
elsif decoded_response.kind_of?(Array)
|
39
|
+
decoded_response.map { |h| OpenStruct.new(h) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_status(status, header = nil, body = nil, message = nil)
|
44
|
+
case status
|
45
|
+
when 400, 402..500
|
46
|
+
code, message = parse_error(body)
|
47
|
+
raise ERROR_CODE_MAPPING[code].new(
|
48
|
+
message,
|
49
|
+
status_code: status,
|
50
|
+
header: header,
|
51
|
+
body: body
|
52
|
+
) if ERROR_CODE_MAPPING.key?(code)
|
53
|
+
end
|
54
|
+
|
55
|
+
super(status, header, body, message)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def deep_snake_case_params!(val)
|
61
|
+
case val
|
62
|
+
when Array
|
63
|
+
val.map {|v| deep_snake_case_params! v }
|
64
|
+
when Hash
|
65
|
+
val.keys.each do |k, v = val[k]|
|
66
|
+
val.delete k
|
67
|
+
val[to_snake_case(k)] = deep_snake_case_params!(v)
|
68
|
+
end
|
69
|
+
val
|
70
|
+
else
|
71
|
+
val
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_snake_case(str)
|
76
|
+
str.gsub(/::/, '/').
|
77
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
78
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
79
|
+
tr("-", "_").
|
80
|
+
downcase
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_error(body)
|
84
|
+
obj = JSON.parse(body)
|
85
|
+
message = obj['title']
|
86
|
+
message << ", #{obj['detail']}" if obj['detail']
|
87
|
+
|
88
|
+
[obj['code'].to_s, message]
|
89
|
+
rescue
|
90
|
+
[nil, nil]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
require 'addressable/template'
|
3
|
+
require 'httpclient'
|
4
|
+
require_relative '../options'
|
5
|
+
require_relative '../version'
|
6
|
+
require_relative 'api_command'
|
7
|
+
require_relative 'logging'
|
8
|
+
|
9
|
+
module Naver
|
10
|
+
module Searchad
|
11
|
+
module Api
|
12
|
+
module Core
|
13
|
+
class BaseService
|
14
|
+
include Logging
|
15
|
+
|
16
|
+
attr_reader :request_options
|
17
|
+
attr_reader :client_options
|
18
|
+
attr_reader :url
|
19
|
+
attr_reader :base_path
|
20
|
+
|
21
|
+
def initialize(url, base_path = '')
|
22
|
+
@url = url
|
23
|
+
@base_path = base_path
|
24
|
+
@request_options = RequestOptions.default.dup
|
25
|
+
@client_options = ClientOptions.default.dup
|
26
|
+
end
|
27
|
+
|
28
|
+
def authorization=(authorization)
|
29
|
+
@request_options.authorization = authorization
|
30
|
+
end
|
31
|
+
|
32
|
+
def authorization
|
33
|
+
request_options.authorization
|
34
|
+
end
|
35
|
+
|
36
|
+
def client
|
37
|
+
@client ||= new_client
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def make_command(method, path, options = {})
|
43
|
+
template = Addressable::Template.new(url + base_path + path)
|
44
|
+
command = ApiCommand.new(method, template)
|
45
|
+
command.options = request_options.merge(options)
|
46
|
+
apply_command_defaults(command)
|
47
|
+
command
|
48
|
+
end
|
49
|
+
|
50
|
+
def execute_command(command, &block)
|
51
|
+
command.execute(client, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def apply_command_defaults(command)
|
55
|
+
# To be implemented by subclasses
|
56
|
+
end
|
57
|
+
|
58
|
+
def validates_presence_of(fields, object)
|
59
|
+
fields.each do |key|
|
60
|
+
unless object.key?(key)
|
61
|
+
raise MissingRequiredAttributeError.new(
|
62
|
+
"Require #{key} attribute in object")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def new_client
|
70
|
+
HTTPClient.new.tap do |client|
|
71
|
+
client.transparent_gzip_decompression = true
|
72
|
+
client.proxy = client_options.proxy_url if client_options.proxy_url
|
73
|
+
client.connect_timeout = client_options.open_timeout_sec if client_options.open_timeout_sec
|
74
|
+
client.receive_timeout = client_options.read_timeout_sec if client_options.read_timeout_sec
|
75
|
+
client.send_timeout = client_options.send_timeout_sec if client_options.send_timeout_sec
|
76
|
+
client.follow_redirect_count = 5
|
77
|
+
client.default_header = { 'User-Agent' => user_agent }
|
78
|
+
client.debug_dev = logger if client_options.log_http_requests
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def user_agent
|
83
|
+
"#{client_options.application_name}/#{client_options.application_version} "\
|
84
|
+
"naver-searchad-api/#{Naver::Searchad::Api::VERSION} "\
|
85
|
+
"#{Naver::Searchad::Api::OS_VERSION} (gzip)"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
require 'addressable/template'
|
3
|
+
require_relative '../errors'
|
4
|
+
require_relative '../options'
|
5
|
+
require_relative 'logging'
|
6
|
+
|
7
|
+
module Naver
|
8
|
+
module Searchad
|
9
|
+
module Api
|
10
|
+
module Core
|
11
|
+
class HttpCommand
|
12
|
+
include Logging
|
13
|
+
|
14
|
+
attr_reader :url
|
15
|
+
attr_reader :method
|
16
|
+
attr_accessor :body
|
17
|
+
attr_accessor :header
|
18
|
+
attr_accessor :options
|
19
|
+
attr_accessor :query
|
20
|
+
attr_accessor :params
|
21
|
+
|
22
|
+
def initialize(method, url, body: nil)
|
23
|
+
@options = RequestOptions.default.dup
|
24
|
+
@url = url.is_a?(String) ? Addressable::Template.new(url) : url
|
25
|
+
@method = method
|
26
|
+
@header = {}
|
27
|
+
@body = body
|
28
|
+
@query = {}
|
29
|
+
@params = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(client, &block)
|
33
|
+
prepare!
|
34
|
+
|
35
|
+
logger.debug("Executing HTTP #{method} #{url}")
|
36
|
+
request_header = {}
|
37
|
+
apply_request_options(request_header)
|
38
|
+
|
39
|
+
http_res = client.request(method.to_s.upcase,
|
40
|
+
url.to_s,
|
41
|
+
query: nil,
|
42
|
+
body: body,
|
43
|
+
header: request_header,
|
44
|
+
follow_redirect: true)
|
45
|
+
|
46
|
+
logger.debug("Returned status(#{http_res.status}) and #{http_res.inspect}")
|
47
|
+
response = process_response(http_res.status.to_i, http_res.header, http_res.body)
|
48
|
+
|
49
|
+
logger.debug("Success - #{response}")
|
50
|
+
success(response, &block)
|
51
|
+
rescue => e
|
52
|
+
logger.debug("Error - #{e.inspect}")
|
53
|
+
error(e, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def prepare!
|
57
|
+
normalize_unicode = true
|
58
|
+
if options
|
59
|
+
@header.merge!(options.header) if options.header
|
60
|
+
normalize_unicode = options.normalize_unicode
|
61
|
+
end
|
62
|
+
|
63
|
+
if url.is_a?(Addressable::Template)
|
64
|
+
@url = url.expand(params, nil, normalize_unicode)
|
65
|
+
@url.query_values = query.merge(url.query_values || {})
|
66
|
+
end
|
67
|
+
|
68
|
+
@body = '' unless body
|
69
|
+
end
|
70
|
+
|
71
|
+
def process_response(status, header, body)
|
72
|
+
check_status(status, header, body)
|
73
|
+
decode_response_body(header['Content-Type'].first, body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_status(status, header = nil, body = nil, message = nil)
|
77
|
+
case status
|
78
|
+
when 200...300
|
79
|
+
nil
|
80
|
+
when 301, 302, 303, 307
|
81
|
+
message ||= "Redirect to #{header['Location']}"
|
82
|
+
raise Naver::Searchad::Api::RedirectError.new(
|
83
|
+
message, status_code: status, header: header, body: body)
|
84
|
+
when 401
|
85
|
+
message ||= 'Unauthorized'
|
86
|
+
raise Naver::Searchad::Api::AuthorizationError.new(
|
87
|
+
message, status_code: status, header: header, body: body)
|
88
|
+
when 429
|
89
|
+
message ||= 'Rate limit exceeded'
|
90
|
+
raise Naver::Searchad::Api::RateLimitError.new(
|
91
|
+
message, status_code: status, header: header, body: body)
|
92
|
+
when 400, 402...500
|
93
|
+
message ||= 'Invalid request'
|
94
|
+
raise Naver::Searchad::Api::RequestError.new(
|
95
|
+
message, status_code: status, header: header, body: body)
|
96
|
+
when 500...600
|
97
|
+
message ||= 'Server error'
|
98
|
+
raise Naver::Searchad::Api::ServerError.new(
|
99
|
+
message, status_code: status, header: header, body: body)
|
100
|
+
else
|
101
|
+
logger.warn("Encountered unexpected status code #{status}")
|
102
|
+
message ||= 'Unknown error'
|
103
|
+
raise Naver::Searchad::Api::UnknownError.new(
|
104
|
+
message, status_code: status, header: header, body: body)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def decode_response_body(content_type, body)
|
109
|
+
body
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def apply_request_options(req_header)
|
115
|
+
options.authorization.apply(req_header, url.path, method) if options.authorization.respond_to?(:apply)
|
116
|
+
req_header.merge!(header)
|
117
|
+
end
|
118
|
+
|
119
|
+
def success(result, &block)
|
120
|
+
block.call(result, nil) if block_given?
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
124
|
+
def error(err, &block)
|
125
|
+
if err.is_a?(HTTPClient::BadResponseError)
|
126
|
+
begin
|
127
|
+
res = err.res
|
128
|
+
check_status(res.status.to_i, res.header, res.body)
|
129
|
+
rescue Naver::Searchad::Api::Error => e
|
130
|
+
err = e
|
131
|
+
end
|
132
|
+
elsif err.is_a?(HTTPClient::TimeoutError) || err.is_a?(SocketError)
|
133
|
+
err = Naver::Searchad::Api::TransmissionError.new(err)
|
134
|
+
end
|
135
|
+
if block_given?
|
136
|
+
block.call(nil, err)
|
137
|
+
else
|
138
|
+
raise err
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
class Error < StandardError
|
5
|
+
attr_reader :status_code
|
6
|
+
attr_reader :header
|
7
|
+
attr_reader :body
|
8
|
+
|
9
|
+
def initialize(err, status_code: nil, header: nil, body: nil)
|
10
|
+
@cause = nil
|
11
|
+
|
12
|
+
if err.respond_to?(:backtrace)
|
13
|
+
super(err.message)
|
14
|
+
@cause = err
|
15
|
+
else
|
16
|
+
super(err.to_s)
|
17
|
+
end
|
18
|
+
@status_code = status_code
|
19
|
+
@header = header unless header.nil?
|
20
|
+
@body = body
|
21
|
+
end
|
22
|
+
|
23
|
+
def backtrace
|
24
|
+
if @cause
|
25
|
+
@cause.backtrace
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end # Error
|
31
|
+
|
32
|
+
RedirectError = Class.new(Error)
|
33
|
+
|
34
|
+
AuthorizationError = Class.new(Error)
|
35
|
+
|
36
|
+
RequestError = Class.new(Error)
|
37
|
+
|
38
|
+
RateLimitError = Class.new(Error)
|
39
|
+
|
40
|
+
ServerError = Class.new(Error)
|
41
|
+
|
42
|
+
UnknownError = Class.new(Error)
|
43
|
+
|
44
|
+
TransmissionError = Class.new(Error)
|
45
|
+
|
46
|
+
NotEnoughPermissionError = Class.new(RequestError)
|
47
|
+
|
48
|
+
MissingRequiredAttributeError = Class.new(RequestError)
|
49
|
+
|
50
|
+
InvalidRequestError = Class.new(RequestError)
|
51
|
+
|
52
|
+
DuplicatedCampaignNameError = Class.new(RequestError)
|
53
|
+
|
54
|
+
DuplicatedAdgroupNameError = Class.new(RequestError)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
RequestOptions = Struct.new(
|
5
|
+
:authorization,
|
6
|
+
:header,
|
7
|
+
:normalize_unicode
|
8
|
+
)
|
9
|
+
|
10
|
+
ClientOptions = Struct.new(
|
11
|
+
:application_name,
|
12
|
+
:application_version,
|
13
|
+
:proxy_url,
|
14
|
+
:open_timeout_sec,
|
15
|
+
:read_timeout_sec,
|
16
|
+
:send_timeout_sec,
|
17
|
+
:log_http_requests
|
18
|
+
)
|
19
|
+
|
20
|
+
class ClientOptions
|
21
|
+
def self.default
|
22
|
+
@options ||= ClientOptions.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class RequestOptions
|
27
|
+
def self.default
|
28
|
+
@options ||= RequestOptions.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge(options)
|
32
|
+
return self unless options
|
33
|
+
|
34
|
+
new_options = dup
|
35
|
+
members.each do |opt|
|
36
|
+
opt = opt.to_sym
|
37
|
+
new_options[opt] = options[opt] unless options[opt].nil?
|
38
|
+
end
|
39
|
+
new_options
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ClientOptions.default.log_http_requests = false
|
44
|
+
ClientOptions.default.application_name = 'naver-searchad-api'
|
45
|
+
ClientOptions.default.application_version = '0.0.0'
|
46
|
+
RequestOptions.default.normalize_unicode = false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Naver
|
2
|
+
module Searchad
|
3
|
+
module Api
|
4
|
+
VERSION = '0.0.1'
|
5
|
+
|
6
|
+
OS_VERSION = begin
|
7
|
+
if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
|
8
|
+
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
|
9
|
+
elsif RUBY_PLATFORM =~ /darwin/i
|
10
|
+
"Mac OS X/#{`sw_vers -productVersion`}"
|
11
|
+
elsif RUBY_PLATFORM == 'java'
|
12
|
+
require 'java'
|
13
|
+
name = java.lang.System.getProperty('os.name')
|
14
|
+
version = java.lang.System.getProperty('os.version')
|
15
|
+
"#{name}/#{version}"
|
16
|
+
else
|
17
|
+
`uname -sr`.sub(' ', '/')
|
18
|
+
end
|
19
|
+
rescue
|
20
|
+
RUBY_PLATFORM
|
21
|
+
end.gsub("\n", '')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'naver/searchad/api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'naver-searchad-api'
|
8
|
+
spec.version = Naver::Searchad::Api::VERSION
|
9
|
+
spec.authors = ['Min Kim']
|
10
|
+
spec.email = %w[developers@forward3d.com min.kim@forward3d.com]
|
11
|
+
|
12
|
+
spec.summary = %q(Naver Searchad API ruby client)
|
13
|
+
spec.description = %q(Naver Searchad API ruby client)
|
14
|
+
spec.homepage = 'http://forward3d.com'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'httpclient', '>= 2.8.1', '< 3.0'
|
25
|
+
spec.add_runtime_dependency 'addressable', '~> 2.5'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
|
+
spec.add_development_dependency 'simplecov', '~> 0.14'
|
31
|
+
spec.add_development_dependency 'webmock', '~> 2.1'
|
32
|
+
spec.add_development_dependency 'factory_json', '~> 0.1.1'
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: naver-searchad-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Min Kim
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httpclient
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.8.1
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.8.1
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: addressable
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.5'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.5'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.14'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.14'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rake
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '10.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ~>
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '10.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ~>
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ~>
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: simplecov
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ~>
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0.14'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ~>
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.14'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: webmock
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.1'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ~>
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '2.1'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: factory_json
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ~>
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 0.1.1
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ~>
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 0.1.1
|
131
|
+
description: Naver Searchad API ruby client
|
132
|
+
email:
|
133
|
+
- developers@forward3d.com
|
134
|
+
- min.kim@forward3d.com
|
135
|
+
executables: []
|
136
|
+
extensions: []
|
137
|
+
extra_rdoc_files: []
|
138
|
+
files:
|
139
|
+
- .gitignore
|
140
|
+
- .rspec
|
141
|
+
- .travis.yml
|
142
|
+
- Gemfile
|
143
|
+
- LICENSE
|
144
|
+
- README.md
|
145
|
+
- Rakefile
|
146
|
+
- bin/console
|
147
|
+
- bin/setup
|
148
|
+
- lib/naver/searchad/api.rb
|
149
|
+
- lib/naver/searchad/api/ad-keyword/service.rb
|
150
|
+
- lib/naver/searchad/api/ad/service.rb
|
151
|
+
- lib/naver/searchad/api/adgroup/service.rb
|
152
|
+
- lib/naver/searchad/api/auth.rb
|
153
|
+
- lib/naver/searchad/api/campaign/service.rb
|
154
|
+
- lib/naver/searchad/api/core/api_command.rb
|
155
|
+
- lib/naver/searchad/api/core/base_service.rb
|
156
|
+
- lib/naver/searchad/api/core/http_command.rb
|
157
|
+
- lib/naver/searchad/api/core/logging.rb
|
158
|
+
- lib/naver/searchad/api/errors.rb
|
159
|
+
- lib/naver/searchad/api/options.rb
|
160
|
+
- lib/naver/searchad/api/version.rb
|
161
|
+
- naver-searchad-api.gemspec
|
162
|
+
homepage: http://forward3d.com
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
metadata: {}
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 2.6.10
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: Naver Searchad API ruby client
|
186
|
+
test_files: []
|