ppc 1.3.2 → 2.0.0

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -339
  3. data/README.md +46 -28
  4. data/Rakefile +1 -0
  5. data/lib/ppc.rb +1 -1
  6. data/lib/ppc/api.rb +18 -14
  7. data/lib/ppc/api/baidu.rb +5 -14
  8. data/lib/ppc/api/baidu/account.rb +28 -23
  9. data/lib/ppc/api/baidu/bulk.rb +1 -1
  10. data/lib/ppc/api/baidu/creative.rb +47 -86
  11. data/lib/ppc/api/baidu/group.rb +43 -74
  12. data/lib/ppc/api/baidu/keyword.rb +73 -166
  13. data/lib/ppc/api/baidu/phone_new_creative.rb +8 -7
  14. data/lib/ppc/api/baidu/plan.rb +51 -34
  15. data/lib/ppc/api/baidu/report.rb +9 -29
  16. data/lib/ppc/api/qihu.rb +7 -111
  17. data/lib/ppc/api/qihu/account.rb +31 -25
  18. data/lib/ppc/api/qihu/bulk.rb +1 -2
  19. data/lib/ppc/api/qihu/creative.rb +57 -74
  20. data/lib/ppc/api/qihu/group.rb +40 -63
  21. data/lib/ppc/api/qihu/keyword.rb +58 -74
  22. data/lib/ppc/api/qihu/plan.rb +52 -32
  23. data/lib/ppc/api/qihu/rank.rb +11 -10
  24. data/lib/ppc/api/qihu/report.rb +41 -94
  25. data/lib/ppc/api/qihu/sublink.rb +44 -41
  26. data/lib/ppc/api/sm.rb +3 -12
  27. data/lib/ppc/api/sm/account.rb +20 -19
  28. data/lib/ppc/api/sm/creative.rb +36 -50
  29. data/lib/ppc/api/sm/group.rb +40 -61
  30. data/lib/ppc/api/sm/keyword.rb +44 -102
  31. data/lib/ppc/api/sm/phone_new_creative.rb +9 -8
  32. data/lib/ppc/api/sm/plan.rb +36 -26
  33. data/lib/ppc/api/sm/report.rb +5 -24
  34. data/lib/ppc/api/sogou.rb +5 -55
  35. data/lib/ppc/api/sogou/account.rb +17 -17
  36. data/lib/ppc/api/sogou/creative.rb +52 -75
  37. data/lib/ppc/api/sogou/group.rb +44 -91
  38. data/lib/ppc/api/sogou/keyword.rb +60 -143
  39. data/lib/ppc/api/sogou/plan.rb +37 -23
  40. data/lib/ppc/api/sogou/report.rb +2 -19
  41. data/lib/ppc/ext.rb +0 -8
  42. data/lib/ppc/operation.rb +60 -83
  43. data/lib/ppc/operation/account.rb +13 -19
  44. data/lib/ppc/operation/creative.rb +0 -20
  45. data/lib/ppc/operation/group.rb +18 -27
  46. data/lib/ppc/operation/keyword.rb +0 -27
  47. data/lib/ppc/operation/plan.rb +34 -22
  48. data/lib/ppc/operation/report.rb +4 -4
  49. data/lib/ppc/operation/sublink.rb +8 -0
  50. data/ppc.gemspec +5 -5
  51. metadata +16 -10
  52. data/lib/ppc/api/shenma.rb +0 -64
  53. data/lib/ppc/api/shenma/report.rb +0 -135
@@ -29,120 +29,16 @@ module PPC
29
29
  end
30
30
 
31
31
  def self.request_http_body(auth, params = {})
32
- params["format"] = 'json'
33
- params.map{|k,v| "#{k.to_s}=#{v.to_s}"}.join('&')
32
+ "format=json&" + params.map{|k,v| "#{k.to_s}=#{v.is_a?(Array) ? v.to_json : v}"}.join('&')
34
33
  end
35
34
 
36
- # def self.request( auth, service, method, params = {} )
37
- # p 123
38
- # url = "https://api.e.360.cn/2.0/#{service}/#{method}"
39
- # # 日后考虑将httpparty用Net/http代替
40
- # response = HTTParty.post(url,
41
- # body: params,
42
- # headers: {'apiKey' => auth[:api_key],
43
- # 'accessToken' => auth[:token],
44
- # 'serveToken' => Time.now.to_i.to_s }
45
- # )
46
- # p response
47
- # response.parsed_response
48
- # end
49
-
50
35
  def self.process( response, key, failure_key = '', &func )
51
- # special case solution
52
- # 只有account getInfo 的key 为 '',因为其特殊逻辑,单独处理
53
- result = { }
54
- if response == []
55
- result[:succ] = true
56
- result[:failure] = nil
57
- result[:result] = nil
58
- result[:no_quota] = false
59
- return result
60
- end
61
- if key == ''
62
- if !response['failures'].nil?
63
- result[:succ] = false
64
- result[:result] = nil
65
- result[:failure] = response['failures']
66
- result[:no_quota] = is_no_quota(response['failures'], '90401')
67
- return result
68
- end
69
- result[:succ] = true
70
- result[:failure] = nil
71
- result[:no_quota] = false
72
- result[:result] = func[response]
73
- return result
74
- end
75
- result[:result] = func[response[key]]
76
- result[:failure] = response['failures']
77
- result[:no_quota] = is_no_quota(response['failures'], '90401')
78
- result[:succ] = response['failures'].nil? || response['failures'].size.zero?
79
- #if response['failures'] != nil
80
- # result[:succ] = false
81
- # result[:failure] = response['failures']['item']
82
- # result[:result] = nil
83
- #else
84
- # result[:succ] = true
85
- # result[:result] = func[ key==''? response : response[ key ] ]
86
- # result[:failure] = failure_key == ''? nil : response[ failure_key ]
87
- #end
88
- return result
89
- end
90
-
91
- def self.to_json_string( items )
92
- '''
93
- convert list of string/int to list of json string
94
- '''
95
- return '' if items == nil
96
- items = [items] unless items.is_a? Array
97
- items_str = []
98
- items.each{ |x| items_str << x.to_s }
99
- items_str.to_json
100
- end
101
-
102
- def self.to_id_list( ids_str )
103
- return [] if ids_str == nil
104
- ids_str = [ids_str] unless ids_str.is_a? Array
105
- ids_i = []
106
- ids_str.each{ |id| ids_i<<id.to_i }
107
- ids_i
108
- end
109
-
110
- def self.make_type( params, map = @map)
111
- '''
112
- '''
113
- params = [ params ] unless params.is_a? Array
114
-
115
- types = []
116
- params.each do |param|
117
- type = {}
118
-
119
- map.each do |key|
120
- value = param[ key[0] ]
121
- type[ key[1] ] = value if value != nil
122
- end
123
-
124
- types << type
125
- end
126
- return types
127
- end
128
-
129
- def self.reverse_type( types, map = @map )
130
- '''
131
- '''
132
- types = [ types ] unless types.is_a? Array
133
-
134
- params = []
135
- types.each do |type|
136
- param = {}
137
-
138
- map.each do |key|
139
- value = type[ key[1].to_s ]
140
- param[ key[0] ] = value if value != nil
141
- end
142
-
143
- params << param
144
- end
145
- return params
36
+ result = {}
37
+ result[:succ] = response['failures'].nil? || response['failures'].size.zero?
38
+ result[:failure] = response['failures'] || response["failure_key"]
39
+ result[:result] = (key.empty? ? func[response] : func[response[key]]) rescue nil
40
+ result[:no_quota] = (response['failures']['code'] == '90401') rescue false
41
+ result
146
42
  end
147
43
 
148
44
  end
@@ -6,18 +6,26 @@ module PPC
6
6
  class Account < Qihu
7
7
  Service = 'account'
8
8
 
9
- @map = [
10
- [ :id, :uid ],
11
- [ :name, :userName ],
12
- [ :email, :email],
13
- [ :company, :companyName],
14
- [ :industry1, :industry1],
15
- [ :industry2, :industry2],
16
- [ :balance, :balance],
17
- [ :budget, :budget],
18
- [ :resources, :resources],
19
- [ :open_domains, :allowDomain]
20
- ]
9
+ AccountType = {
10
+ id: :uid,
11
+ name: :userName,
12
+ email: :email,
13
+ category: :category,
14
+ company: :companyName,
15
+ industry1: :industry1,
16
+ industry2: :industry2,
17
+ balance: :balance,
18
+ budget: :budget,
19
+ mv_budget: :mvBudget,
20
+ resources: :Resources,
21
+ "resources" => :resources,
22
+ status: :status,
23
+ open_mobile_domain: :allowMobileDomain,
24
+ open_domains: :allowDomain,
25
+ agency: :agency,
26
+ }
27
+ @map = AccountType
28
+
21
29
 
22
30
  def self.info( auth )
23
31
  response = request( auth, Service, 'getInfo' )
@@ -29,36 +37,35 @@ module PPC
29
37
  对奇虎两个update的在封装。如果所有操作成功,succ为true,否则为false
30
38
  failure中以字符串方式返回失败的操作
31
39
  '''
32
- result = {}
33
- result[:succ] = true
34
- result[:failure] = []
35
- result[:result] = []
40
+ result = {
41
+ succ: true,
42
+ failure: [],
43
+ result: [],
44
+ }
36
45
 
37
- if params[:budget] != nil
46
+ if params[:budget]
38
47
  budget_result = update_budget( auth, params[:budget] )
39
48
  result[:succ] = result[:succ] && budget_result[:succ]
40
49
  result[:failure] << 'budget' unless budget_result[:succ]
41
50
  end
42
51
 
43
- if params[:exclude_ip] != nil
52
+ if params[:exclude_ip]
44
53
  ip_result = update_exclude_ip( auth, params[:exclude_ip] )
45
54
  result[:succ] = result[:succ] && ip_result[:succ]
46
55
  result[:failure] << 'exclude_ip' unless budget_result[:succ]
47
56
  end
48
57
 
49
- return result
58
+ result
50
59
  end
51
60
 
52
61
  def self.get_all_object( auth, ids )
53
62
  #文档上面写的输入类型是String?
54
- body = { 'idList' => ids }
55
- response = request( auth, Service, 'getAllObjects' )
63
+ response = request( auth, Service, 'getAllObjects', { idList: ids } )
56
64
  process( response, 'account_getAllObjects_response' ){ |x| x }
57
65
  end
58
66
 
59
67
  def self.get_file_state( auth, id )
60
- body = { 'fileId' => id }
61
- response = request( auth, Service, 'getAllObjects' , body )
68
+ response = request( auth, Service, 'getAllObjects' , { fileId: id } )
62
69
  process( response, 'account_getFileState_response' ){ |x| x }
63
70
  end
64
71
 
@@ -69,13 +76,12 @@ module PPC
69
76
 
70
77
  private
71
78
  def self.update_budget( auth, budget )
72
- response = request( auth, Service, 'updateBudget', { budget:budget })
79
+ response = request( auth, Service, 'updateBudget', { budget: budget })
73
80
  process( response, 'affectedRecords' ){ | x | x.to_i==1 ? 'success' : 'failure' }
74
81
  end
75
82
 
76
83
  private
77
84
  def self.update_exclude_ip( auth, ips )
78
- ips = to_json_string( ips )
79
85
  response = request( auth, Service, 'updateExcludeIp', { excludeIpList: ips } )
80
86
  process( response, '' ){|x| x}
81
87
  end
@@ -8,8 +8,7 @@ module PPC
8
8
 
9
9
  def self.get_all_object( auth, ids )
10
10
  #文档上面写的输入类型是String?
11
- body = nil
12
- body = { 'idList' => to_json_string(ids) } if ids
11
+ body = { 'idList' => ids.map(&:to_s) }
13
12
  response = request( auth, Service, 'getAllObjects', body )
14
13
  process( response, 'fileId' ){ |x| x }
15
14
  end
@@ -5,101 +5,84 @@ module PPC
5
5
  class Creative< Qihu
6
6
  Service = 'creative'
7
7
 
8
- @map = [
9
- [:id,:id] ,
10
- [:group_id, :groupId],
11
- [:title,:title],
12
- [:description1, :description1],
13
- [:description2, :description2],
14
- [:pc_destination, :destinationUrl],
15
- [:pc_display, :displayUrl],
16
- [:mobile_destination, :mobileDestinationUrl],
17
- [:mobile_display, :mobileDisplayUrl]
18
- ]
8
+ CreativeType = {
9
+ id: :id,
10
+ plan_id: :campaignId,
11
+ group_id: :groupId,
12
+ title: :title,
13
+ description1: :description1,
14
+ description2: :description2,
15
+ description3: :descSecondLine,
16
+ pc_destination: :destinationUrl,
17
+ pc_display: :displayUrl,
18
+ mobile_destination: :mobileDestinationUrl,
19
+ mobile_display: :mobileDisplayUrl,
20
+ pause: :status,
21
+ mobile_pause: :mobileStatus,
22
+ add_time: :addTime,
23
+ updateTime: :updateTime,
24
+ }
25
+ @map = CreativeType
19
26
 
20
- @status_map = [
21
- [:id,:id],
22
- [:quality,:qualityScore],
23
- [:status,:status]
24
- ]
27
+ @status_map = {
28
+ id: :id,
29
+ quality: :qualityScore,
30
+ pause: :status,
31
+ }
32
+
33
+ def self.info( auth, ids )
34
+ response = request( auth, Service, 'getInfoByIdList', { idList: ids } )
35
+ process( response, 'creativeList'){ |x| reverse_type(x)[0] }
36
+ end
37
+
38
+ # combine two methods to provide another mether
39
+ def self.all( auth, group_id )
40
+ results = self.ids( auth, group_id )
41
+ return results unless results[:succ]
42
+ self.get( auth , results[:result] )
43
+ end
44
+
45
+ def self.ids( auth, group_id )
46
+ response = request( auth, Service, 'getIdListByGroupId', {"groupId" => group_id[0]} )
47
+ process( response, 'creativeIdList' ){ |x| x.map(&:to_i) }
48
+ end
25
49
 
26
50
  def self.get( auth, ids )
27
- body = { 'idList' => to_json_string( ids ) }
28
- response = request( auth, Service, 'getInfoByIdList', body )
51
+ response = request( auth, Service, 'getInfoByIdList', { idList: ids } )
29
52
  process( response, 'creativeList'){ |x| reverse_type(x) }
30
53
  end
31
54
 
32
55
  def self.add( auth, creatives )
33
- creative_types = make_type( creatives ).to_json
34
- body = { 'creatives' => creative_types}
35
- response = request( auth, Service, 'add', body )
36
- process( response, 'creativeIdList'){ |x| to_id_hash_list(x) }
37
- end
38
-
39
- # helper function for self.add() method
40
- private
41
- def self.to_id_hash_list( str )
42
- reuturn [] if str == nil
43
- str = [str] unless str.is_a?Array
44
- x= []
45
- str.each{ |i| x << { id: i.to_i } }
46
- return x
56
+ response = request( auth, Service, 'add', { creatives: make_type( creatives ) } )
57
+ process( response, 'creativeIdList'){ |x| x.map{|tmp| { id: i.to_i } } }
47
58
  end
48
59
 
49
60
  def self.update( auth, creatives )
50
- creative_types = make_type( creatives ).to_json
51
- body = { 'creatives' => creative_types}
52
- response = request( auth, Service, 'update', body )
53
- process( response, 'affectedRecords', 'failCreativeIds' ){ |x| x }
61
+ response = request( auth, Service, 'update', { creatives: make_type( creatives ) } )
62
+ process( response, 'affectedRecords', 'failCreativeIds' ){ |x| x }
54
63
  end
55
64
 
56
- # 对update的再封装实现activate方法,未测试
57
- def self.activate( auth, ids )
58
- creatives = []
59
- ids.each{ |id| creatives << { id: id, status:'enable'} }
60
- update( auth, creatives )
61
- end
62
-
63
65
  def self.delete( auth, ids )
64
- ids = to_json_string( ids )
65
- body = { 'idList' => ids }
66
- response = request( auth, Service, 'deleteByIdList', body )
67
- process( response, 'affectedRecords' ){ |x|x }
66
+ response = request( auth, Service, 'deleteByIdList', { idList: ids } )
67
+ process( response, 'affectedRecords' ){ |x|x }
68
68
  end
69
69
 
70
- def self.status( auth, ids )
71
- body = { idList: to_json_string( ids ) }
72
- response = request( auth, Service, 'getStatusByIdList', body )
73
- process( response, 'creativeList' ){ |x| reverse_type( x, @status_map ) }
70
+ def self.enable( auth, ids )
71
+ self.update( auth, ids.map{ |id| { id: id, pause: "enable"} } )
74
72
  end
75
73
 
76
- # quality 本质上和 status 在一个方法里面
77
- def self.quality( auth, ids )
78
- status( auth, ids)
74
+ def self.pause( auth, ids )
75
+ self.update( auth, ids.map{ |id| { id: id, pause: "pause"} } )
79
76
  end
80
77
 
81
- def self.search_id_by_group_id( auth, id, status = nil)
82
- # 处理条件
83
- body = {}
84
- body['status'] = status if status
85
- body['groupId'] = id
86
- response = request( auth, Service, 'getIdListByGroupId', body )
87
- # 伪装成百度接口
88
- process( response, 'creativeIdList' ){
89
- |x|
90
- [ { group_id:id, creative_ids:to_id_list(x) } ]
91
- }
78
+ def self.status( auth, ids )
79
+ response = request( auth, Service, 'getStatusByIdList', { idList: ids } )
80
+ process( response, 'creativeList' ){ |x| reverse_type( x, @status_map ) }
92
81
  end
93
82
 
94
- # combine two methods to provide another mether
95
- def self.search_by_group_id( auth, id )
96
- creative_ids = search_id_by_group_id( auth, id )
97
- response = get( auth , creative_ids )
98
- # 伪装成百度接口
99
- if response[:succ]
100
- response[:result] = [ { group_id:id, creatives:response[:result ] } ]
101
- end
102
- return response
83
+ # quality 本质上和 status 在一个方法里面
84
+ def self.quality( auth, ids )
85
+ self.status( auth, ids)
103
86
  end
104
87
 
105
88
  end
@@ -5,89 +5,66 @@ module PPC
5
5
  class Group < Qihu
6
6
  Service = 'group'
7
7
 
8
- @map = [
9
- [:id, :id ],
10
- [:plan_id, :campaignId],
11
- [:status, :status ],
12
- [:name, :name ],
13
- [:price, :price ],
8
+ GroupType = {
9
+ id: :id,
10
+ plan_id: :campaignId,
11
+ pause: :status,
12
+ name: :name,
13
+ price: :price,
14
14
  # negateive为json格式,make_type要定制
15
- [:add_time, :addTime],
16
- [:update_time, :updateTime]
17
- ]
15
+ negative: :negativeWords,
16
+ exact_negative: :exactNegativeWords,
17
+ add_time: :addTime,
18
+ update_time: :updateTime,
19
+ from_time: :fromTime,
20
+ }
21
+ @map = GroupType
18
22
 
19
- # 再次封装提供all和ids
20
- def self.ids( auth )
21
- '''
22
- 接口与其他的相同,但是无论如何:succ均为true,不返回任何错误信息
23
- 因为是重复调用了search的接口,速度非常的慢,* 慎用 *
24
- '''
25
- plan_ids = ::PPC::API::Qihu::Plan::ids( auth )[:result]
26
- plan_group_ids = []
23
+ def self.info( auth, ids )
24
+ response = request( auth, Service, 'getInfoByIdList', { idList: ids } )
25
+ process( response, 'groupList' ){ |x| reverse_type(x)[0] }
26
+ end
27
+
28
+ def self.all( auth, plan_id )
29
+ group_ids = self.ids( auth, plan_id )[:result][:group_ids]
30
+ self.get( auth, group_ids )
31
+ end
27
32
 
28
- plan_ids.each do |plan_id|
29
- plan_group_id = {}
30
- plan_group_id[:plan_id] = plan_id
31
- plan_group_id[:group_ids] = search_id_by_plan_id( auth, plan_id )[:result]
32
- plan_group_ids << plan_group_id
33
- end
34
- return { succ: true, failure:nil, result: plan_group_ids }
33
+ def self.ids( auth, plan_id )
34
+ response = request( auth, Service, 'getIdListByCampaignId', { campaignId: plan_id[0] } )
35
+ process( response, 'groupIdList' ){ |x| { plan_id: plan_id[0], group_ids: x.map(&:to_i) } }
35
36
  end
36
37
 
37
- def self.all( auth )
38
- '''
39
- unimplemented due to ineffciency
40
- '''
41
- # unimplement
38
+ def self.get( auth, ids )
39
+ response = request( auth, Service, 'getInfoByIdList', { idList: ids } )
40
+ process( response, 'groupList' ){ |x| reverse_type(x) }
42
41
  end
43
42
 
44
- # 奇虎组服务不提供批量add,delete和update方法
45
- def self.add( auth, group )
46
- response = request( auth, Service, 'add', make_type( group )[0] )
47
- # 保证接口一致性
48
- process( response, 'id' ){ |x| [ { id:x.to_i } ] }
43
+ def self.add( auth, groups )
44
+ groups.each{ |group| group[:negative] = {exact: group.delete(:exact_negative), phrase: group.delete(:negative)} if group[:exact_negative] || group[:negative] }
45
+ response = request( auth, Service, 'batchAdd', {groups: make_type( groups )} )
46
+ process( response, 'groupIdList' ){ |x| x.map.with_index{|id, index| {id: id, name: groups[index][:name]}} }
49
47
  end
50
48
 
49
+ # 奇虎组服务不提供批量delete和update方法
51
50
  def self.update( auth, group )
52
- if group[:negative] || group[:exact_negative]
53
- ng = {"exact" => group[:exact_negative], "phrase" => group[:negative]}.to_json
54
- end
51
+ group[0][:negative] = {exact: group[0].delete(:exact_negative), phrase: group[0].delete(:negative)}.to_json if group[0][:exact_negative] || group[0][:negative]
55
52
  params = make_type(group)[0]
56
- params.merge!({:negativeWords => ng}) if ng
57
53
  response = request( auth, Service, 'update', params )
58
- # 保证接口一致性
59
- process( response, 'id' ){ |x|[ { id:x.to_i } ] }
60
- end
61
-
62
- def self.get( auth, ids )
63
- ids = to_json_string( ids )
64
- body = { 'idList' => ids }
65
- response = request( auth, Service, 'getInfoByIdList', body )
66
- process( response, 'groupList' ){ |x| reverse_type(x) }
54
+ process( response, 'id' ){ |x| [{id: x, name: group[0][:name]}] }
67
55
  end
68
56
 
69
57
  def self.delete( auth, id )
70
- response = request( auth, Service, 'deleteById', { id: id} )
58
+ response = request( auth, Service, 'deleteById', {id: id[0]} )
71
59
  process( response, 'affectedRecords' ){ |x| x == '1' }
72
60
  end
73
61
 
74
- def self.search_id_by_plan_id( auth, id )
75
- response = request( auth, Service, 'getIdListByCampaignId', { 'campaignId' => id.to_s })
76
- #为了保持接口一致性,这里也是伪装成了百度的接口
77
- process( response, 'groupIdList' ){
78
- |x|
79
- [ { plan_id:id, group_ids: to_id_list(x) } ]
80
- }
62
+ def self.enable( auth, id )
63
+ self.update(auth, [{id: id[0], pause: "enable"}])
81
64
  end
82
65
 
83
- # combine searchIdbyId and get to provide another method
84
- def self.search_by_plan_id( auth, id )
85
- group_ids = search_id_by_plan_id( auth, id )[:result][0][:group_ids]
86
- response = get( auth, group_ids )
87
- if response[:succ]
88
- response[:result] = [ { plan_id:id, groups:response[:result]}]
89
- end
90
- return response
66
+ def self.pause( auth, id )
67
+ self.update(auth, [{id: id[0], pause: "pause"}])
91
68
  end
92
69
 
93
70
  end