ppc 1.3.0 → 1.3.2

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 (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