ppc 1.3.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/LICENSE +334 -17
  4. data/README.md +75 -3
  5. data/lib/ppc.rb +3 -1
  6. data/lib/ppc/api.rb +137 -2
  7. data/lib/ppc/api/baidu.rb +20 -99
  8. data/lib/ppc/api/baidu/account.rb +21 -21
  9. data/lib/ppc/api/baidu/bulk.rb +39 -3
  10. data/lib/ppc/api/baidu/creative.rb +33 -33
  11. data/lib/ppc/api/baidu/group.rb +14 -14
  12. data/lib/ppc/api/baidu/keyword.rb +57 -46
  13. data/lib/ppc/api/baidu/phone_new_creative.rb +51 -0
  14. data/lib/ppc/api/baidu/plan.rb +34 -31
  15. data/lib/ppc/api/baidu/rank.rb +23 -0
  16. data/lib/ppc/api/baidu/report.rb +100 -81
  17. data/lib/ppc/api/qihu.rb +150 -0
  18. data/lib/ppc/api/qihu/account.rb +86 -0
  19. data/lib/ppc/api/qihu/bulk.rb +35 -0
  20. data/lib/ppc/api/qihu/creative.rb +108 -0
  21. data/lib/ppc/api/qihu/group.rb +96 -0
  22. data/lib/ppc/api/qihu/keyword.rb +113 -0
  23. data/lib/ppc/api/qihu/plan.rb +64 -0
  24. data/lib/ppc/api/qihu/rank.rb +25 -0
  25. data/lib/ppc/api/qihu/report.rb +159 -0
  26. data/lib/ppc/api/qihu/sublink.rb +71 -0
  27. data/lib/ppc/api/shenma.rb +64 -0
  28. data/lib/ppc/api/shenma/report.rb +135 -0
  29. data/lib/ppc/api/sm.rb +50 -0
  30. data/lib/ppc/api/sm/account.rb +44 -0
  31. data/lib/ppc/api/sm/bulk.rb +69 -0
  32. data/lib/ppc/api/sm/creative.rb +86 -0
  33. data/lib/ppc/api/sm/group.rb +94 -0
  34. data/lib/ppc/api/sm/keyword.rb +132 -0
  35. data/lib/ppc/api/sm/phone_new_creative.rb +51 -0
  36. data/lib/ppc/api/sm/plan.rb +63 -0
  37. data/lib/ppc/api/sm/report.rb +136 -0
  38. data/lib/ppc/api/sogou.rb +118 -0
  39. data/lib/ppc/api/sogou/account.rb +42 -0
  40. data/lib/ppc/api/sogou/bulk.rb +69 -0
  41. data/lib/ppc/api/sogou/creative.rb +117 -0
  42. data/lib/ppc/api/sogou/group.rb +123 -0
  43. data/lib/ppc/api/sogou/keyword.rb +188 -0
  44. data/lib/ppc/api/sogou/plan.rb +66 -0
  45. data/lib/ppc/api/sogou/report.rb +129 -0
  46. data/lib/ppc/ext.rb +17 -0
  47. data/lib/ppc/operation.rb +50 -36
  48. data/lib/ppc/operation/account.rb +19 -7
  49. data/lib/ppc/operation/creative.rb +7 -7
  50. data/lib/ppc/operation/group.rb +2 -2
  51. data/lib/ppc/operation/keyword.rb +11 -8
  52. data/lib/ppc/operation/report.rb +23 -0
  53. data/ppc.gemspec +14 -4
  54. data/spec/baidu/api_baidu_account_spec.rb +16 -0
  55. data/spec/baidu/api_baidu_creative_spec.rb +73 -0
  56. data/spec/{api_baidu_group_spec.rb → baidu/api_baidu_group_spec.rb} +12 -17
  57. data/spec/baidu/api_baidu_keyword_spec.rb +61 -0
  58. data/spec/baidu/api_baidu_phone_spec.rb +22 -0
  59. data/spec/{api_baidu_plan_spec.rb → baidu/api_baidu_plan_spec.rb} +11 -10
  60. data/spec/baidu/api_baidu_report_spec.rb +44 -0
  61. data/spec/{api_baidu_spec.rb → baidu/api_baidu_spec.rb} +10 -15
  62. data/spec/operation/operation_baidu_report_spec.rb +17 -0
  63. data/spec/operation/operation_baidu_spec.rb +126 -0
  64. data/spec/operation/operation_qihu_report_spec.rb +18 -0
  65. data/spec/operation/operation_qihu_spec.rb +94 -0
  66. data/spec/operation/operation_sm_report_spec.rb +21 -0
  67. data/spec/operation/operation_sogou_report_spec.rb +17 -0
  68. data/spec/operation/operation_sogou_spec.rb +88 -0
  69. data/spec/{operation_spec_helper.rb → operation/operation_spec_helper.rb} +3 -5
  70. data/spec/qihu/api_qihu_account_spec.rb +29 -0
  71. data/spec/qihu/api_qihu_creative_spec.rb +48 -0
  72. data/spec/qihu/api_qihu_group_spec.rb +40 -0
  73. data/spec/qihu/api_qihu_keyword_spec.rb +50 -0
  74. data/spec/qihu/api_qihu_plan_spec.rb +39 -0
  75. data/spec/qihu/api_qihu_report_spec.rb +54 -0
  76. data/spec/qihu/api_qihu_sublink_spec.rb +36 -0
  77. data/spec/sm/api_sm_account_spec.rb +8 -0
  78. data/spec/sm/api_sm_creative_spec.rb +52 -0
  79. data/spec/sm/api_sm_group_spec.rb +75 -0
  80. data/spec/sm/api_sm_keyword_spec.rb +59 -0
  81. data/spec/sm/api_sm_plan_spec.rb +39 -0
  82. data/spec/sm/api_sm_report_spec.rb +30 -0
  83. data/spec/sogou/api_sogou_account_spec.rb +17 -0
  84. data/spec/sogou/api_sogou_creative_spec.rb +51 -0
  85. data/spec/sogou/api_sogou_group_spec.rb +50 -0
  86. data/spec/sogou/api_sogou_keyword_spec.rb +54 -0
  87. data/spec/sogou/api_sogou_plan_spec.rb +43 -0
  88. data/spec/sogou/api_sogou_report_spec.rb +51 -0
  89. data/spec/spec_helper.rb +49 -10
  90. metadata +140 -46
  91. data/lib/ppc/baidu.rb +0 -152
  92. data/lib/ppc/baidu/account.rb +0 -88
  93. data/lib/ppc/baidu/bulk.rb +0 -52
  94. data/lib/ppc/baidu/group.rb +0 -99
  95. data/lib/ppc/baidu/key.rb +0 -38
  96. data/lib/ppc/baidu/plan.rb +0 -85
  97. data/lib/ppc/baidu/report.rb +0 -82
  98. data/spec/api_baidu_account_spec.rb +0 -18
  99. data/spec/api_baidu_creative_spec.rb +0 -71
  100. data/spec/api_baidu_keyword_spec.rb +0 -64
  101. data/spec/api_baidu_report_spec.rb +0 -32
  102. data/spec/baidu_account_spec.rb +0 -32
  103. data/spec/baidu_bulk_spec.rb +0 -21
  104. data/spec/baidu_group_spec.rb +0 -56
  105. data/spec/baidu_plan_spec.rb +0 -129
  106. data/spec/baidu_report_spec.rb +0 -24
  107. data/spec/operation_spec.rb +0 -87
@@ -0,0 +1,113 @@
1
+ # -*- coding:utf-8 -*-
2
+ module PPC
3
+ module API
4
+ class Qihu
5
+ class Keyword< Qihu
6
+ Service = 'keyword'
7
+
8
+ @map = [
9
+ [:id, :id],
10
+ [:group_id, :groupId],
11
+ [:keyword, :word],
12
+ [:price, :price],
13
+ [:match_type, :matchType],
14
+ [:pc_destination, :url],
15
+ [:mobile_destination, :mobileUrl],
16
+ [:status, :status]
17
+ ]
18
+
19
+ @status_map = [
20
+ [:id,:id],
21
+ [:quality,:qualityScore],
22
+ [:status,:status]
23
+ ]
24
+
25
+ def self.get( auth, ids )
26
+ ids = to_json_string( ids )
27
+ body = { 'idList' => ids }
28
+ response = request( auth, Service, 'getInfoByIdList', body )
29
+ process( response, 'keywordList'){ |x| reverse_type(x) }
30
+ end
31
+
32
+ def self.add( auth, keywords )
33
+ keyword_types = make_type( keywords ).to_json
34
+ body = { 'keywords' => keyword_types}
35
+ response = request( auth, Service, 'add', body )
36
+ p response
37
+ process( response, 'keywordIdList'){ |x| to_id_hash_list(x) }
38
+ end
39
+
40
+ # helper function for self.add() method
41
+ private
42
+ def self.to_id_hash_list( str )
43
+ reuturn [] if str == nil
44
+ str = [str] unless str.is_a?Array
45
+ x= []
46
+ str.each{ |i| x << { id: i.to_i } }
47
+ return x
48
+ end
49
+
50
+ def self.update( auth, keywords )
51
+ keyword_types = make_type( keywords ).to_json
52
+ body = { 'keywords' => keyword_types}
53
+ response = request( auth, Service, 'update', body )
54
+ process( response, 'affectedRecords', 'failKeywordIds' ){ |x| x }
55
+ end
56
+
57
+ # 对update的再封装实现activate方法
58
+ def self.activate( auth, ids )
59
+ keywords = []
60
+ ids.each{ |id| keywords << { id: id, status:'enable'} }
61
+ update( auth, keywords )
62
+ end
63
+
64
+ def self.delete( auth, ids )
65
+ body = { 'idList' => to_json_string( ids ) }
66
+ response = request( auth, Service, 'deleteByIdList', body )
67
+ process( response, 'affectedRecords' ){ |x| x }
68
+ end
69
+
70
+ def self.status( auth, ids )
71
+ body = { idList: to_json_string( ids ) }
72
+ response = request( auth, Service, 'getStatusByIdList', body )
73
+ process( response, 'keywordList' ){ |x| reverse_type(x, @status_map) }
74
+ end
75
+
76
+ # quality 本质上和 status 在一个方法里面
77
+ def self.quality( auth, ids )
78
+ status( auth, ids)
79
+ end
80
+
81
+ def self.search_id_by_group_id( auth, id, status = nil, match_type = nil )
82
+ # 处理条件
83
+ body = {}
84
+ body['status'] = status if status
85
+ body['matchType'] = match_type if match_type
86
+ body['groupId'] = id
87
+ response = request( auth, Service, 'getIdListByGroupId', body )
88
+ # 伪装成百度接口
89
+ process( response, 'keywordIdList' ){
90
+ |x|
91
+ [{group_id:id, keyword_ids:to_id_list(x)}]
92
+ }
93
+ end
94
+
95
+ # combine search_id and get to provide another method
96
+ def self.search_by_group_id( auth, id )
97
+ keyword_ids = search_id_by_group_id( auth, id )[:result][0][:keyword_ids]
98
+ response = get( auth, keyword_ids )
99
+ if response[:succ]
100
+ # 伪装成百度接口
101
+ response[:result] = [ { group_id:id, keywords:response[:result] } ]
102
+ end
103
+ return response
104
+ end
105
+
106
+ def self.getChangedIdList
107
+ # unimplemented
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,64 @@
1
+ # -*- coding:utf-8 -*-
2
+ module PPC
3
+ module API
4
+ class Qihu
5
+ class Plan < Qihu
6
+ Service = 'campaign'
7
+
8
+ @map = [
9
+ [:id, :id],
10
+ [:name,:name],
11
+ [:budget, :budget],
12
+ [:region, :region],
13
+ [:schedule, :schedule],
14
+ [:startDate, :startDate],
15
+ [:endDate, :endDate ],
16
+ [:status,:status],
17
+ [:extend_ad_type,:extendAdType]
18
+ ]
19
+
20
+
21
+ def self.get(auth, ids)
22
+ '''
23
+ :Type ids: ( Array of ) String or integer
24
+ '''
25
+ ids = to_json_string( ids )
26
+ body = {'idList' => ids}
27
+ response = request( auth, Service, 'getInfoByIdList', body )
28
+ process( response, 'campaignList' ){ |x| reverse_type(x) }
29
+ end
30
+
31
+ # move getCampaignId to plan module for operation call
32
+ def self.ids( auth )
33
+ response = request( auth, 'account', 'getCampaignIdList' )
34
+ process( response, 'campaignIdList' ){ |x| to_id_list(x)}
35
+ end
36
+
37
+ # combine two original method to provice new method
38
+ def self.all( auth )
39
+ plan_ids = ids( auth )[:result]
40
+ get( auth, plan_ids )
41
+ end
42
+
43
+ # 奇虎计划API不提供批量服务
44
+ def self.add( auth, plan )
45
+ response = request( auth, Service, 'add', make_type( plan )[0])
46
+ # 这里将返回的简单int做一个array和hash的封装一保证接口和百度,搜狗的一致性
47
+ process( response, 'id' ){ |x| [ { id:x.to_i } ] }
48
+ end
49
+
50
+ def self.update( auth, plan )
51
+ response = request( auth, Service, 'update', make_type( plan )[0])
52
+ #同上,保证接口一致性
53
+ process( response, 'id' ){ |x| [ { id:x.to_i } ] }
54
+ end
55
+
56
+ def self.delete( auth, id )
57
+ response = request( auth, Service, 'deleteById', { id: id } )
58
+ process( response, 'affectedRecords' ){ |x| x == '1'? 'success' : 'fail' }
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ module PPC
2
+ module API
3
+ class Qihu
4
+ class Rank < Qihu
5
+ Service = 'tool'
6
+
7
+ @map = [
8
+ [:id,:id] ,
9
+ [:group_id, :groupId],
10
+ [:anchor,:text],
11
+ [:url, :link],
12
+ [:image, :image],
13
+ [:status, :status]
14
+ ]
15
+
16
+ def self.getRank( auth, region, queryInfo )
17
+ body = {'region'=> region, 'queryInfo' => queryInfo}
18
+ response = request(auth, Service, 'realTimeQueryResult', body)
19
+ # process( response, 'sublinkList'){ |x| reverse_type( x['item'] ) }
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,159 @@
1
+ # -*- coding:utf-8 -*-
2
+ require 'json'
3
+
4
+ module PPC
5
+ module API
6
+ class Qihu
7
+ class Report< Qihu
8
+ Service = 'report'
9
+
10
+ @map =[
11
+ [:queryword,:queryword],
12
+ [:plan_id,:campaignId],
13
+ [:creative_id,:creativeId],
14
+ [:keyword,:keyword],
15
+ [:views,:views],
16
+ [:clicks,:clicks],
17
+ [:startDate,:startDate],
18
+ [:endDate,:endDate],
19
+ [:date,:date],
20
+ [:keyword_id,:keywordId],
21
+ [:group_id,:groupId],
22
+ [:cost,:totalCost],
23
+ [:position,:avgPosition],
24
+ [:total_num,:totalNumber],
25
+ [:total_page,:totalPage]
26
+ ]
27
+
28
+ ###################
29
+ # API abstraction #
30
+ ###################
31
+ def self.abstract( auth, type_name, method_name, key, param = nil, &func )
32
+ body = make_type( param )
33
+ response = request( auth, Service, method_name, body )
34
+ process( response, key ){ |x| func[ x ] }
35
+ end
36
+
37
+ type_list = ['keyword', 'query', 'creative', 'sublink']
38
+ type_list.each do |type|
39
+ # type
40
+ define_singleton_method type.to_sym do |auth, param|
41
+ abstract( auth, type, type, type+'List', param ){ |x| x}
42
+ end
43
+ # typeCount
44
+ define_singleton_method (type+'_count').to_sym do |auth, param|
45
+ response = abstract( auth, type, type+'Count', '', param ){ |x| get_item(x) }
46
+ response[:result] = response[:result][0]
47
+ return response
48
+ end
49
+ # typeNow
50
+ define_singleton_method (type+'_now').to_sym do |auth, param|
51
+ abstract( auth, type, type+'Now', type+'List', param ){ |x| x['item']}
52
+ end
53
+ # typeNowCount
54
+ define_singleton_method (type+'_now_count').to_sym do |auth, param|
55
+ response = abstract( auth, type, type+'NowCount', '', param ){ |x| get_item(x) }
56
+ response[:result] = response[:result][0]
57
+ return response
58
+ end
59
+ end
60
+
61
+ ############################
62
+ # Interfaces for operation #
63
+ ############################
64
+ def self.keyword_report( auth, param, debug = false )
65
+ download_report(auth, 'keyword', param, debug )
66
+ end
67
+
68
+ def self.creative_report( auth, param, debug = false )
69
+ download_report(auth, 'creative', param, debug)
70
+ end
71
+
72
+ def self.download_report(auth, type, param, debug = false)
73
+ # deal_with time
74
+ now = Time.now.to_s[0...10]
75
+ is_now = now==parse_date(param[:startDate])
76
+
77
+ # get page num
78
+ if is_now
79
+ method = (type+'_now_count').to_sym
80
+ count = send(method, auth, param)[:result]
81
+ method = (type+'_now').to_sym
82
+ else
83
+ method = (type+'_count').to_sym
84
+ count = send(method, auth, param)[:result]
85
+ method = type.to_sym
86
+ end
87
+
88
+ report = []
89
+ count[:total_page].to_i.times do | page_i|
90
+ p "Start downloading #{page_i+1}th page, totally #{count[:total_page]} pages"
91
+ param[:page] = page_i +1
92
+ report_i = send(method, auth, param)[:result]
93
+ report += report_i
94
+ end
95
+
96
+ return report
97
+ end
98
+
99
+ ###################
100
+ # Helper Function #
101
+ ###################
102
+ # incase idlist == nil
103
+ private
104
+ def self.get_item( params )
105
+ return nil if params == nil
106
+ return reverse_type( params )
107
+ end
108
+
109
+ private
110
+ def self.make_type( param )
111
+ type = {}
112
+ # add option
113
+ type[:level] = param[:level] || 'account'
114
+ type[:page] = param[:page] || 1
115
+ # add ids
116
+ if param[:ids] != nil
117
+ ids = param[:ids]
118
+ ids = [ ids ] unless ids.is_a? Array
119
+ type[:IdList] = ids.to_json
120
+ end
121
+ # add date
122
+ if param[:startDate]==nil || param[:endDate]==nil
123
+ type[:startDate], type[:endDate] = get_date()
124
+ else
125
+ type[:startDate] = parse_date( param[:startDate] )
126
+ type[:endDate] = parse_date( param[:endDate] )
127
+ end
128
+
129
+ return type
130
+ end
131
+
132
+ private
133
+ def self.get_date()
134
+ endDate = Time.now.to_s[0,10]
135
+ startDate = (Time.now - 24*3600).to_s[0,10]
136
+ return startDate,endDate
137
+ end
138
+
139
+ private
140
+ def self.parse_date( date )
141
+ """
142
+ Cast string to time:
143
+ 'YYYYMMDD' => Time
144
+ """
145
+ if date
146
+ y = date[0..3]
147
+ m = date[4..5]
148
+ d = date[6..7]
149
+ date = Time.new( y, m, d )
150
+ else
151
+ date = (Time.now - 24*3600)
152
+ end
153
+ date.to_s[0,10]
154
+ end
155
+
156
+ end # Report
157
+ end # Qihu
158
+ end # API
159
+ end # PPC
@@ -0,0 +1,71 @@
1
+ # -*- coding:utf-8 -*-
2
+ module PPC
3
+ module API
4
+ class Qihu
5
+ class Sublink< Qihu
6
+ Service = 'sublink'
7
+
8
+ @map = [
9
+ [:id,:id] ,
10
+ [:group_id, :groupId],
11
+ [:anchor,:text],
12
+ [:url, :link],
13
+ [:image, :image],
14
+ [:status, :status]
15
+ ]
16
+
17
+ def self.get( auth, ids )
18
+ body = { 'idList' => to_json_string( ids ) }
19
+ response = request( auth, Service, 'getInfoByIdList', body )
20
+ process( response, 'sublinkList'){ |x| reverse_type( x['item'] ) }
21
+ end
22
+
23
+ def self.add( auth, sublinks )
24
+ sublink_types = make_type( sublinks ).to_json
25
+ body = { 'sublinks' => sublink_types}
26
+ response = request( auth, Service, 'add', body )
27
+ process( response, 'sublinkIdList'){ |x| to_id_hash_list( x['item'] ) }
28
+ end
29
+
30
+ def self.delete( auth, ids )
31
+ ids = to_json_string( ids )
32
+ body = { 'idList' => ids }
33
+ response = request( auth, Service, 'deleteByIdList', body )
34
+ process( response, 'affectedRecords' ){ |x|x }
35
+ end
36
+
37
+ # helper function for self.add() method
38
+ private
39
+ def self.to_id_hash_list( str )
40
+ reuturn [] if str == nil
41
+ str = [str] unless str.is_a?Array
42
+ x= []
43
+ str.each{ |i| x << { id: i.to_i } }
44
+ return x
45
+ end
46
+
47
+ def self.update( auth, sublinks )
48
+ sublink_types = make_type( sublinks ).to_json
49
+ body = { 'sublinks' => sublink_types}
50
+ response = request( auth, Service, 'update', body )
51
+ process( response, 'affectedRecords', 'failKeywordIds' ){ |x| x }
52
+ end
53
+
54
+ # 对update的再封装实现activate方法,未测试
55
+ def self.activate( auth, ids )
56
+ sublinks = []
57
+ ids.each{ |id| sublinks << { id: id, status:'enable'} }
58
+ update( auth, sublinks )
59
+ end
60
+
61
+ def self.delete( auth, ids )
62
+ ids = to_json_string( ids )
63
+ body = { 'idList' => ids }
64
+ response = request( auth, Service, 'deleteByIdList', body )
65
+ process( response, 'affectedRecords' ){ |x|x }
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,64 @@
1
+ module PPC
2
+ module API
3
+ class Baidu
4
+ @map = nil
5
+ @@debug = false
6
+
7
+ def self.debug_on
8
+ @@debug = true
9
+ end
10
+
11
+ def self.debug_off
12
+ @@debug = false
13
+ end
14
+
15
+ def self.request( auth, path, params = {} )
16
+ '''
17
+ request should return whole http response including header
18
+ '''
19
+ uri = URI("https://e.sm.cn#{path}")
20
+ http_body = {
21
+ header: {
22
+ username: auth[:username],
23
+ password: auth[:password],
24
+ token: auth[:token],
25
+ target: auth[:tarvget]
26
+ },
27
+ body: params
28
+ }.to_json
29
+
30
+ http_header = {
31
+ 'Content-Type' => 'application/json; charset=UTF-8'
32
+ }
33
+
34
+ http = Net::HTTP.new(uri.host, 443)
35
+ # 是否显示http通信输出
36
+ http.set_debug_output( $stdout ) if @@debug
37
+ http.use_ssl = true
38
+
39
+ response = http.post(uri.path, http_body, http_header)
40
+ response = JSON.parse( response.body )
41
+ end
42
+
43
+ def self.process( response, key, &func)
44
+ '''
45
+ Process Http response. If operation successes, return value of given keys.
46
+ You can process the result using function &func, or do nothing by passing
47
+ block {|x|x}
48
+ ===========================
49
+ @Output: resultType{ desc: boolean, failure: Array, result: Array }
50
+
51
+ failure is the failures part of response\'s header
52
+ result is the processed response body.
53
+ '''
54
+ p response
55
+ result = {}
56
+ result[:succ] = response['header']['desc']=='success'? true : false
57
+ result[:failure] = response['header']['failures']
58
+ result[:result] = func[ response['body'][key] ]
59
+ return result
60
+ end # process
61
+
62
+ end
63
+ end
64
+ end