sklik-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.
- data/.document +5 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +75 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/config/access.rb.example +8 -0
- data/lib/sklik-api.rb +42 -0
- data/lib/sklik-api/access.rb +54 -0
- data/lib/sklik-api/campaign.rb +132 -0
- data/lib/sklik-api/campaign_parts/adgroup.rb +127 -0
- data/lib/sklik-api/campaign_parts/adtext.rb +99 -0
- data/lib/sklik-api/campaign_parts/keyword.rb +109 -0
- data/lib/sklik-api/connection.rb +82 -0
- data/lib/sklik-api/sklik_object.rb +60 -0
- data/lib/sklik-api/xmlrpc_setup.rb +21 -0
- data/sklik-api.gemspec +101 -0
- data/test/fake_web.rb +40 -0
- data/test/helper.rb +22 -0
- data/test/unit/campaign.rb +103 -0
- metadata +236 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi
|
3
|
+
class Adtext
|
4
|
+
|
5
|
+
NAME = "ad"
|
6
|
+
|
7
|
+
include Object
|
8
|
+
=begin
|
9
|
+
Example of input hash
|
10
|
+
{
|
11
|
+
:headline => "Super headline",
|
12
|
+
:description1 => "Trying to do ",
|
13
|
+
:description2 => "best description ever",
|
14
|
+
:display_url => "my_test_url.cz",
|
15
|
+
:url => "http://my_test_url.cz"
|
16
|
+
}
|
17
|
+
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
def initialize adgroup, args
|
22
|
+
@adtext_data = nil
|
23
|
+
#set adtext owner adgroup
|
24
|
+
@adgroup = adgroup
|
25
|
+
|
26
|
+
super args
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def create_args
|
31
|
+
raise ArgumentError, "Adtexts need's to know adgroup_id" unless @adgroup.args[:adgroup_id]
|
32
|
+
out = []
|
33
|
+
#add campaign id to know where to create adgroup
|
34
|
+
out << @adgroup.args[:adgroup_id]
|
35
|
+
|
36
|
+
#add adtext struct
|
37
|
+
args = {}
|
38
|
+
args[:creative1] = @args[:headline]
|
39
|
+
args[:creative2] = @args[:description1]
|
40
|
+
args[:creative3] = @args[:description2]
|
41
|
+
args[:clickthruText] = @args[:display_url]
|
42
|
+
args[:clickthruUrl] = @args[:url]
|
43
|
+
out << args
|
44
|
+
|
45
|
+
#return output
|
46
|
+
out
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find adgroup, args = {}
|
50
|
+
out = []
|
51
|
+
super(NAME, adgroup.args[:adgroup_id]).each do |adtext|
|
52
|
+
if args[:adtext_id].nil? || (args[:adtext_id] && args[:adtext_id].to_i == adtext[:id].to_i)
|
53
|
+
out << Adtext.new(
|
54
|
+
adgroup,
|
55
|
+
:adtext_id => adtext[:id],
|
56
|
+
:headline => adtext[:creative1],
|
57
|
+
:description1 => adtext[:creative2],
|
58
|
+
:description2 => adtext[:creative3],
|
59
|
+
:display_url =>adtext[:clickthruText],
|
60
|
+
:url => adtext[:clickthruUrl],
|
61
|
+
:name => adtext[:name],
|
62
|
+
:status => fix_status(adtext)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
out
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.fix_status adtext
|
70
|
+
if adtext[:removed] == true
|
71
|
+
return :stopped
|
72
|
+
elsif adtext[:status] == "active"
|
73
|
+
return :running
|
74
|
+
elsif adtext[:status] == "suspend"
|
75
|
+
return :paused
|
76
|
+
else
|
77
|
+
return :unknown
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_hash
|
82
|
+
if @adtext_data
|
83
|
+
@adtext_data
|
84
|
+
else
|
85
|
+
@adtext_data = @args
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def save
|
90
|
+
if @args[:adtext_id] #do update
|
91
|
+
|
92
|
+
else #do save
|
93
|
+
#create adtext
|
94
|
+
create
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi
|
3
|
+
class Keyword
|
4
|
+
|
5
|
+
NAME = "keyword"
|
6
|
+
|
7
|
+
include Object
|
8
|
+
=begin
|
9
|
+
Example of input hash
|
10
|
+
{
|
11
|
+
:keyword => "\"some funny keyword\""
|
12
|
+
}
|
13
|
+
=end
|
14
|
+
|
15
|
+
def initialize adgroup, args
|
16
|
+
@keyword_data = nil
|
17
|
+
#set keyword owner adgroup
|
18
|
+
@adgroup = adgroup
|
19
|
+
|
20
|
+
super args
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_args
|
24
|
+
raise ArgumentError, "Keyword need's to know adgroup_id" unless @adgroup.args[:adgroup_id]
|
25
|
+
out = []
|
26
|
+
#add campaign id to know where to create adgroup
|
27
|
+
out << @adgroup.args[:adgroup_id]
|
28
|
+
|
29
|
+
#add adtext struct
|
30
|
+
args = {}
|
31
|
+
args[:name] = strip_match_type @args[:keyword]
|
32
|
+
args[:matchType] = get_math_type @args[:keyword]
|
33
|
+
out << args
|
34
|
+
|
35
|
+
#return output
|
36
|
+
out
|
37
|
+
end
|
38
|
+
|
39
|
+
def strip_match_type keyword
|
40
|
+
keyword.gsub(/(\[|\]|\")/, "")
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_math_type keyword
|
44
|
+
if /^\[.*\]$/ =~ keyword
|
45
|
+
return "exact"
|
46
|
+
elsif /^\".*\"$/ =~ keyword
|
47
|
+
return "phrase"
|
48
|
+
else
|
49
|
+
return "broad"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.apply_math_type keyword, match_type
|
54
|
+
return case match_type
|
55
|
+
when "broad" then keyword
|
56
|
+
when "phrase" then "\"#{keyword}\""
|
57
|
+
when "exact" then "[#{keyword}]"
|
58
|
+
else keyword
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.find adgroup, args = {}
|
63
|
+
out = []
|
64
|
+
super(NAME, adgroup.args[:adgroup_id]).each do |keyword|
|
65
|
+
if args[:keyword_id].nil? || (args[:keyword_id] && args[:keyword_id].to_i == keyword[:id].to_i)
|
66
|
+
out << Keyword.new(
|
67
|
+
adgroup,
|
68
|
+
:keyword_id => keyword[:id],
|
69
|
+
:keyword => apply_math_type(keyword[:name], keyword[:matchType] ),
|
70
|
+
:status => fix_status(keyword)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
out
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.fix_status keyword
|
78
|
+
if keyword[:removed] == true
|
79
|
+
return :stopped
|
80
|
+
elsif keyword[:status] == "active"
|
81
|
+
return :running
|
82
|
+
elsif keyword[:status] == "suspend"
|
83
|
+
return :paused
|
84
|
+
elsif keyword[:status] == "nonactive"
|
85
|
+
return :paused_by_low_cpc
|
86
|
+
else
|
87
|
+
return :unknown
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_hash
|
92
|
+
if @keyword_data
|
93
|
+
@keyword_data
|
94
|
+
else
|
95
|
+
@keyword_data = @args[:keyword]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def save
|
100
|
+
if @args[:keyword_id] #do update
|
101
|
+
|
102
|
+
else #do save
|
103
|
+
#create adtext
|
104
|
+
create
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi
|
3
|
+
class Connection
|
4
|
+
|
5
|
+
MAX_RETRIES = 3
|
6
|
+
DEFAULTS = {
|
7
|
+
:debug => false,
|
8
|
+
:timeout => 100
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize args = {}
|
12
|
+
@args = DEFAULTS.merge(args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.connection
|
16
|
+
@connection ||= SklikApi::Connection.new(:debug => false)
|
17
|
+
end
|
18
|
+
|
19
|
+
#prepare connection to sklik
|
20
|
+
def connection
|
21
|
+
server = XMLRPC::Client.new3(:host => "api.sklik.cz", :path => "/RPC2", :port => 443, :use_ssl => true, :timeout => @args[:timeout])
|
22
|
+
server.instance_variable_get(:@http).instance_variable_set(:@verify_mode, OpenSSL::SSL::VERIFY_NONE)
|
23
|
+
#fix of UTF-8 encoding
|
24
|
+
server.extend(XMLRPCWorkAround)
|
25
|
+
#debug mode to see what XMLRPC is doing
|
26
|
+
server.set_debug(File.open("log/xmlrpc-#{Time.now.strftime("%Y_%m_%d-%H_%M_%S")}.log","a:UTF-8")) if @args[:debug]
|
27
|
+
|
28
|
+
server
|
29
|
+
end
|
30
|
+
|
31
|
+
#Get session is method for login into sklik
|
32
|
+
#save session for other requests until it expires
|
33
|
+
#every taxonomy has its own session!
|
34
|
+
def get_session force = false
|
35
|
+
@session ||= {}
|
36
|
+
if @session.has_key?(SklikApi::Access.uniq_identifier) && !force
|
37
|
+
@session[SklikApi::Access.uniq_identifier]
|
38
|
+
else
|
39
|
+
begin
|
40
|
+
param = connection.call("client.login", SklikApi::Access.email, SklikApi::Access.password).symbolize_keys
|
41
|
+
if param[:status] == 401
|
42
|
+
raise ArgumentError, "Invalid login for: #{SklikApi::Access.email}"
|
43
|
+
elsif param[:status] == 200
|
44
|
+
return @session[SklikApi::Access.uniq_identifier] = param[:session]
|
45
|
+
else
|
46
|
+
raise ArgumentError, param[:statusMessage]
|
47
|
+
end
|
48
|
+
rescue XMLRPC::FaultException => e
|
49
|
+
raise ArgumentError, "#{e.faultString}, #{e.faultCode}"
|
50
|
+
rescue Exception => e
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# method to wrap method call to sklik -> allow retry and problem with session expiration
|
57
|
+
def call method, *args
|
58
|
+
retry_count = MAX_RETRIES
|
59
|
+
begin
|
60
|
+
#get response from sklik
|
61
|
+
param = connection.call( method, get_session, *args ).symbolize_keys
|
62
|
+
if param[:status] == 200
|
63
|
+
return yield(param)
|
64
|
+
elsif param[:status] == 406
|
65
|
+
raise ArgumentError, "Sklik returned: #{param[:statusMessage]} \n #{args.inspect}"
|
66
|
+
elsif param[:statusMessage] == "Session has expired or is malformed."
|
67
|
+
raise ArgumentError, "session has expired"
|
68
|
+
else
|
69
|
+
raise ArgumentError, "There is error from sklik #{method}: #{param[:statusMessage]}"
|
70
|
+
end
|
71
|
+
rescue Exception => e
|
72
|
+
pp "Rescuing from request by: #{e.class} - #{e.message}"
|
73
|
+
#if session expired then get new one! and retry
|
74
|
+
get_session(true) if e.message == "session has expired"
|
75
|
+
|
76
|
+
retry_count -= 1
|
77
|
+
retry if retry_count > 0
|
78
|
+
raise e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi
|
3
|
+
module Object
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def connection
|
12
|
+
SklikApi::Connection.connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def find name, id = nil
|
16
|
+
if id
|
17
|
+
args = ["list#{name.pluralize.camelize}", id]
|
18
|
+
else
|
19
|
+
args = ["list#{name.pluralize.camelize}"]
|
20
|
+
end
|
21
|
+
return connection.call(*args) { |param|
|
22
|
+
#return list of all objects
|
23
|
+
param[name.pluralize.to_sym].collect{|c| c.symbolize_keys }
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
#get connection for request
|
30
|
+
def connection
|
31
|
+
SklikApi::Connection.connection
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize args
|
35
|
+
@args = args
|
36
|
+
return self
|
37
|
+
end
|
38
|
+
|
39
|
+
def args
|
40
|
+
@args
|
41
|
+
end
|
42
|
+
|
43
|
+
def create
|
44
|
+
out = connection.call("#{self.class::NAME}.create", *create_args ) { |param|
|
45
|
+
param["#{self.class::NAME}Id".to_sym]
|
46
|
+
}
|
47
|
+
@args["#{self.class.to_s.downcase.split(":").last}_id".to_sym] = out
|
48
|
+
@args
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_args
|
52
|
+
raise(NoMethodError, "Please implement 'create_args' method in class: #{self.class} - should return array which will be placed into create method")
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_hash
|
56
|
+
raise(NoMethodError, "Please implement 'to_hash' method in class: #{self.class} - should return hash which contains all data")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
#Settings for sklik & eTarget
|
4
|
+
XMLRPC::Config.const_set(:ENABLE_NIL_PARSER, true)
|
5
|
+
XMLRPC::Config.const_set(:ENABLE_NIL_CREATE, true)
|
6
|
+
|
7
|
+
#Hack for enabling debug mode!
|
8
|
+
class XMLRPC::Client
|
9
|
+
def set_debug out = $stderr
|
10
|
+
@http.set_debug_output(out);
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
#Hack for force encoding to UTF-8
|
15
|
+
module XMLRPCWorkAround
|
16
|
+
def do_rpc(request, async=false)
|
17
|
+
data = super
|
18
|
+
data.force_encoding("UTF-8")
|
19
|
+
data
|
20
|
+
end
|
21
|
+
end
|
data/sklik-api.gemspec
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "sklik-api"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ondrej Bartas"]
|
12
|
+
s.date = "2012-03-25"
|
13
|
+
s.description = "Sklik advertising PPC api for creating campaigns and updating them when they runs"
|
14
|
+
s.email = "ondrej@bartas.cz"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"config/access.rb.example",
|
27
|
+
"lib/sklik-api.rb",
|
28
|
+
"lib/sklik-api/access.rb",
|
29
|
+
"lib/sklik-api/campaign.rb",
|
30
|
+
"lib/sklik-api/campaign_parts/adgroup.rb",
|
31
|
+
"lib/sklik-api/campaign_parts/adtext.rb",
|
32
|
+
"lib/sklik-api/campaign_parts/keyword.rb",
|
33
|
+
"lib/sklik-api/connection.rb",
|
34
|
+
"lib/sklik-api/sklik_object.rb",
|
35
|
+
"lib/sklik-api/xmlrpc_setup.rb",
|
36
|
+
"sklik-api.gemspec",
|
37
|
+
"test/fake_web.rb",
|
38
|
+
"test/helper.rb",
|
39
|
+
"test/unit/campaign.rb"
|
40
|
+
]
|
41
|
+
s.homepage = "http://github.com/ondrejbartas/sklik-api"
|
42
|
+
s.licenses = ["MIT"]
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = "1.8.10"
|
45
|
+
s.summary = "Sklik advertising PPC api for creating campaigns"
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<unicode>, ["~> 0.4.0"])
|
53
|
+
s.add_runtime_dependency(%q<text>, ["~> 0.2.0"])
|
54
|
+
s.add_runtime_dependency(%q<i18n>, ["~> 0.6.0"])
|
55
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.1.0"])
|
56
|
+
s.add_development_dependency(%q<rack-test>, [">= 0"])
|
57
|
+
s.add_development_dependency(%q<shoulda-context>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<turn>, ["~> 0.8.2"])
|
59
|
+
s.add_development_dependency(%q<minitest>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<ansi>, ["~> 1.2.5"])
|
61
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
62
|
+
s.add_development_dependency(%q<fakeweb>, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<thin>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<shotgun>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<rcov>, ["= 0.9.10"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<json>, [">= 0"])
|
68
|
+
s.add_dependency(%q<unicode>, ["~> 0.4.0"])
|
69
|
+
s.add_dependency(%q<text>, ["~> 0.2.0"])
|
70
|
+
s.add_dependency(%q<i18n>, ["~> 0.6.0"])
|
71
|
+
s.add_dependency(%q<activesupport>, ["~> 3.1.0"])
|
72
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
73
|
+
s.add_dependency(%q<shoulda-context>, [">= 0"])
|
74
|
+
s.add_dependency(%q<turn>, ["~> 0.8.2"])
|
75
|
+
s.add_dependency(%q<minitest>, [">= 0"])
|
76
|
+
s.add_dependency(%q<ansi>, ["~> 1.2.5"])
|
77
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
78
|
+
s.add_dependency(%q<fakeweb>, [">= 0"])
|
79
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
80
|
+
s.add_dependency(%q<shotgun>, [">= 0"])
|
81
|
+
s.add_dependency(%q<rcov>, ["= 0.9.10"])
|
82
|
+
end
|
83
|
+
else
|
84
|
+
s.add_dependency(%q<json>, [">= 0"])
|
85
|
+
s.add_dependency(%q<unicode>, ["~> 0.4.0"])
|
86
|
+
s.add_dependency(%q<text>, ["~> 0.2.0"])
|
87
|
+
s.add_dependency(%q<i18n>, ["~> 0.6.0"])
|
88
|
+
s.add_dependency(%q<activesupport>, ["~> 3.1.0"])
|
89
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
90
|
+
s.add_dependency(%q<shoulda-context>, [">= 0"])
|
91
|
+
s.add_dependency(%q<turn>, ["~> 0.8.2"])
|
92
|
+
s.add_dependency(%q<minitest>, [">= 0"])
|
93
|
+
s.add_dependency(%q<ansi>, ["~> 1.2.5"])
|
94
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
95
|
+
s.add_dependency(%q<fakeweb>, [">= 0"])
|
96
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
97
|
+
s.add_dependency(%q<shotgun>, [">= 0"])
|
98
|
+
s.add_dependency(%q<rcov>, ["= 0.9.10"])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|