sklik-api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|