sunflower 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,287 +1,288 @@
1
- # coding: utf-8
2
- require 'rest-client'
3
- require 'json'
4
- require 'cgi'
5
-
6
- # Main class. To start working, you have to create new Sunflower:
7
- # s = Sunflower.new('en.wikipedia.org')
8
- # And then log in:
9
- # s.login('Username','password')
10
- #
11
- # If you have ran setup, you can just use
12
- # s = Sunflower.new.login
13
- #
14
- # Then you can request data from API using #API method.
15
- #
16
- # To log data to file, use #log method (works like puts). Use RestClient.log=<io> to log all requests.
17
- #
18
- # You can use multiple Sunflowers at once, to work on multiple wikis.
19
-
20
- class SunflowerError < StandardError; end
21
-
22
- class Sunflower
23
- VERSION = '0.3'
24
-
25
- INVALID_CHARS = %w(# < > [ ] | { })
26
- INVALID_CHARS_REGEX = Regexp.union *INVALID_CHARS
27
-
28
- # Path to user data file.
29
- def self.path
30
- File.join(ENV['HOME'], 'sunflower-userdata')
31
- end
32
-
33
- # Options for this Sunflower.
34
- attr_accessor :summary, :always_do_code_cleanup
35
-
36
- attr_accessor :cookie, :headers, :wikiURL, :warnings, :log
37
-
38
- # Initialize a new Sunflower working on a wiki with given URL, for ex. "pl.wikipedia.org".
39
- def initialize url=nil
40
- begin
41
- r=File.read(Sunflower.path)
42
- @userdata=r.split(/\r?\n/).map{|i| i.strip}
43
- rescue
44
- @userdata=[]
45
- end
46
-
47
- if !url
48
- if !@userdata.empty?
49
- url=@userdata[0]
50
- else
51
- raise SunflowerError, 'initialize: no URL supplied and no userdata found!'
52
- end
53
- end
54
-
55
- @warnings=true
56
- @log=false
57
-
58
- @wikiURL=url
59
-
60
- @loggedin=false
61
- end
62
-
63
- # Call the API. Returns a hash of JSON response. Request can be a HTTP request string or a hash.
64
- def API request
65
- if request.is_a? String
66
- request += '&format=json'
67
- elsif request.is_a? Hash
68
- request.merge!({format:'json'})
69
- end
70
-
71
- resp = RestClient.post(
72
- 'http://'+@wikiURL+'/w/api.php',
73
- request,
74
- {:user_agent => "Sunflower #{VERSION} alpha", :cookies => @cookies}
75
- )
76
- JSON.parse resp.to_str
77
- end
78
-
79
- # Log in using given info.
80
- def login user='', password=''
81
- if user=='' || password==''
82
- if !@userdata.empty?
83
- user=@userdata[1] if user==''
84
- password=@userdata[2] if password==''
85
- else
86
- raise SunflowerError, 'login: no user/pass supplied and no userdata found!'
87
- end
88
- end
89
-
90
- raise SunflowerError, 'bad username!' if user =~ INVALID_CHARS_REGEX
91
-
92
-
93
- # 1. get the login token
94
- response = RestClient.post(
95
- 'http://'+@wikiURL+'/w/api.php?'+"action=login&lgname=#{user}&lgpassword=#{password}"+'&format=json',
96
- nil,
97
- {:user_agent => 'Sunflower alpha'}
98
- )
99
-
100
- @cookies = response.cookies
101
- json = JSON.parse response.to_str
102
- token, prefix = json['login']['token'], json['login']['cookieprefix']
103
-
104
-
105
- # 2. actually log in
106
- response = RestClient.post(
107
- 'http://'+@wikiURL+'/w/api.php?'+"action=login&lgname=#{user}&lgpassword=#{password}&lgtoken=#{token}"+'&format=json',
108
- nil,
109
- {:user_agent => 'Sunflower alpha', :cookies => @cookies}
110
- )
111
-
112
- json = JSON.parse response.to_str
113
-
114
- @cookies = @cookies.merge(response.cookies).merge({
115
- "#{prefix}UserName" => json['login']['lgusername'].to_s,
116
- "#{prefix}UserID" => json['login']['lguserid'].to_s,
117
- "#{prefix}Token" => json['login']['lgtoken'].to_s
118
- })
119
-
120
-
121
- raise SunflowerError, 'unable to log in (no cookies received)!' if !@cookies
122
-
123
-
124
- # 3. confirm you did log in by checking the watchlist.
125
- @loggedin=true
126
- r=self.API('action=query&list=watchlistraw')
127
- if r['error'] && r['error']['code']=='wrnotloggedin'
128
- @loggedin=false
129
- raise SunflowerError, 'unable to log in!'
130
- end
131
-
132
- # 4. check bot rights
133
- r=self.API('action=query&list=allusers&aulimit=1&augroup=bot&aufrom='+user)
134
- unless r['query']['allusers'][0]['name']==user
135
- warn 'Sunflower - this user does not have bot rights!' if @warnings
136
- @is_bot=false
137
- else
138
- @is_bot=true
139
- end
140
-
141
- return self
142
- end
143
-
144
- def log t
145
- File.open('log.txt','a'){|f| f.puts t}
146
- end
147
-
148
- def is_bot?
149
- @is_bot
150
- end
151
- end
152
-
153
- # Class representng single Wiki page. To load specified page, use #new/#get/#load method.
154
- #
155
- # When calling Page.new, at first only the text will be loaded - attributes and edit token will be loaded when needed, or when you call #preload_attrs.
156
- #
157
- # If you are using multiple Sunflowers, you have to specify which wiki this page belongs to using second argument of function; you can pass whole URL (same as when creating new Sunflower) or just language code.
158
- #
159
- # To save page, use #save/#put method. Optional argument is new title page, if ommited, page is saved at old title. Summary can be passed as second parameter. If it's ommited, s.summary is used. If it's empty too, error is raised.
160
- #
161
- # To get Sunflower instance which this page belongs to, use #sunflower of #belongs_to.
162
- class Page
163
- INVALID_CHARS = %w(# < > [ ] | { })
164
- INVALID_CHARS_REGEX = Regexp.union *INVALID_CHARS
165
-
166
- attr_accessor :text
167
- attr_reader :orig_text
168
-
169
- attr_reader :sunflower
170
- alias :belongs_to :sunflower
171
-
172
- attr_reader :pageid, :ns, :title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection #prop=info
173
-
174
- # calling any of these accessors will fetch the data.
175
- [:pageid, :ns, :title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection].each do |meth|
176
- define_method meth do
177
- preload_attrs unless @preloaded_attrs
178
- instance_variable_get "@#{meth}"
179
- end
180
- end
181
-
182
- def initialize title='', wiki=''
183
- raise SunflowerError, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
184
-
185
- @modulesExecd=[] #used by sunflower-commontasks.rb
186
- @summaryAppend=[] #used by sunflower-commontasks.rb
187
-
188
- @title=title
189
- wiki=wiki+'.wikipedia.org' if wiki.index('.')==nil && wiki!=''
190
-
191
- if wiki==''
192
- count=ObjectSpace.each_object(Sunflower){|o| @sunflower=o}
193
- raise SunflowerError, 'you must pass wiki name if using multiple Sunflowers at once!' if count>1
194
- else
195
- ObjectSpace.each_object(Sunflower){|o| @sunflower=o if o.wikiURL==wiki}
196
- end
197
-
198
- if title==''
199
- @text=''
200
- @orig_text=''
201
- return
202
- end
203
-
204
- preload_text
205
- end
206
-
207
- def preload_text
208
- r = @sunflower.API('action=query&prop=revisions&rvprop=content&titles='+CGI.escape(@title))
209
- r = r['query']['pages'].first
210
- if r['missing']
211
- @text = ''
212
- else
213
- @text = r['revisions'][0]['*']
214
- end
215
-
216
- @orig_text = @text
217
-
218
- @preloaded_text = true
219
- end
220
-
221
- def preload_attrs
222
- r = @sunflower.API('action=query&prop=info&inprop=protection&intoken=edit&titles='+CGI.escape(@title))
223
- r = r['query']['pages'].first
224
- r.each{|key, value|
225
- self.instance_variable_set('@'+key, value)
226
- }
227
-
228
- @preloaded_attrs = true
229
- end
230
-
231
- def dump_to file
232
- if file.respond_to? :write #probably file or IO
233
- file.write @text
234
- else #filename?
235
- File.open(file.to_s, 'w'){|f| f.write @text}
236
- end
237
- end
238
-
239
- def dump
240
- self.dump_to @title.gsub(/[^a-zA-Z0-9\-]/,'_')+'.txt'
241
- end
242
-
243
- def save title=@title, summary=nil
244
- preload_attrs unless @preloaded_attrs
245
-
246
- summary = @sunflower.summary if !summary
247
-
248
- raise SunflowerError, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
249
- raise SunflowerError, 'no summary!' if (!summary or summary=='') && @summaryAppend.empty?
250
-
251
- unless @summaryAppend.empty?
252
- if !summary or summary==''
253
- summary = @summaryAppend.uniq.join(', ')
254
- else
255
- summary = summary.sub(/,\s*\Z/, '') + ', ' + @summaryAppend.uniq.join(', ')
256
- end
257
- end
258
-
259
- if @orig_text==@text && title==@title
260
- @sunflower.log('Page '+title+' not saved - no changes.')
261
- return
262
- end
263
-
264
-
265
-
266
- self.code_cleanup if @sunflower.always_do_code_cleanup && self.respond_to?('code_cleanup')
267
-
268
- r=@sunflower.API("action=edit&bot=1&title=#{CGI.escape(title)}&text=#{CGI.escape(@text)}&summary=#{CGI.escape(summary)}&token=#{CGI.escape(@edittoken)}")# if @sunflower.isBot?
269
- end
270
- alias :put :save
271
-
272
- def self.get title, wiki=''
273
- Page.new(title, wiki)
274
- end
275
-
276
- def self.load title, wiki=''
277
- Page.new(title, wiki)
278
- end
279
- end
280
-
281
- class Hash
282
- # just a lil patch
283
- def first
284
- self.values[0]
285
- end
286
- end
287
-
1
+ # coding: utf-8
2
+ require 'rest-client'
3
+ require 'json'
4
+ require 'cgi'
5
+
6
+ class SunflowerError < StandardError; end
7
+
8
+ # Main class. To start working, you have to create new Sunflower:
9
+ # s = Sunflower.new('en.wikipedia.org')
10
+ # And then log in:
11
+ # s.login('Username','password')
12
+ #
13
+ # If you have ran setup, you can just use
14
+ # s = Sunflower.new.login
15
+ #
16
+ # Then you can request data from API using #API method.
17
+ #
18
+ # To log data to file, use #log method (works like puts). Use RestClient.log=<io> to log all requests.
19
+ #
20
+ # You can use multiple Sunflowers at once, to work on multiple wikis.
21
+ class Sunflower
22
+ VERSION = '0.4'
23
+
24
+ INVALID_CHARS = %w(# < > [ ] | { })
25
+ INVALID_CHARS_REGEX = Regexp.union *INVALID_CHARS
26
+
27
+ # Path to user data file.
28
+ def self.path
29
+ File.join(ENV['HOME'], 'sunflower-userdata')
30
+ end
31
+
32
+ # Options for this Sunflower.
33
+ attr_accessor :summary, :always_do_code_cleanup
34
+
35
+ attr_accessor :cookie, :headers, :wikiURL, :warnings, :log
36
+
37
+ # Initialize a new Sunflower working on a wiki with given URL, for ex. "pl.wikipedia.org".
38
+ def initialize url=nil
39
+ begin
40
+ r=File.read(Sunflower.path)
41
+ @userdata=r.split(/\r?\n/).map{|i| i.strip}
42
+ rescue
43
+ @userdata=[]
44
+ end
45
+
46
+ if !url
47
+ if !@userdata.empty?
48
+ url=@userdata[0]
49
+ else
50
+ raise SunflowerError, 'initialize: no URL supplied and no userdata found!'
51
+ end
52
+ end
53
+
54
+ @warnings=true
55
+ @log=false
56
+
57
+ @wikiURL=url
58
+
59
+ @loggedin=false
60
+ end
61
+
62
+ # Call the API. Returns a hash of JSON response. Request can be a HTTP request string or a hash.
63
+ def API request
64
+ if request.is_a? String
65
+ request += '&format=json'
66
+ elsif request.is_a? Hash
67
+ request.merge!({format:'json'})
68
+ end
69
+
70
+ resp = RestClient.post(
71
+ 'http://'+@wikiURL+'/w/api.php',
72
+ request,
73
+ {:user_agent => "Sunflower #{VERSION} alpha", :cookies => @cookies}
74
+ )
75
+ JSON.parse resp.to_str
76
+ end
77
+
78
+ # Log in using given info.
79
+ def login user='', password=''
80
+ if user=='' || password==''
81
+ if !@userdata.empty?
82
+ user=@userdata[1] if user==''
83
+ password=@userdata[2] if password==''
84
+ else
85
+ raise SunflowerError, 'login: no user/pass supplied and no userdata found!'
86
+ end
87
+ end
88
+
89
+ raise SunflowerError, 'bad username!' if user =~ INVALID_CHARS_REGEX
90
+
91
+
92
+ # 1. get the login token
93
+ response = RestClient.post(
94
+ 'http://'+@wikiURL+'/w/api.php?'+"action=login&lgname=#{CGI.escape user}&lgpassword=#{CGI.escape password}"+'&format=json',
95
+ nil,
96
+ {:user_agent => 'Sunflower alpha'}
97
+ )
98
+
99
+ @cookies = response.cookies
100
+ json = JSON.parse response.to_str
101
+ token, prefix = json['login']['token'], json['login']['cookieprefix']
102
+
103
+
104
+ # 2. actually log in
105
+ response = RestClient.post(
106
+ 'http://'+@wikiURL+'/w/api.php?'+"action=login&lgname=#{CGI.escape user}&lgpassword=#{CGI.escape password}&lgtoken=#{token}"+'&format=json',
107
+ nil,
108
+ {:user_agent => 'Sunflower alpha', :cookies => @cookies}
109
+ )
110
+
111
+ json = JSON.parse response.to_str
112
+
113
+ @cookies = @cookies.merge(response.cookies).merge({
114
+ "#{prefix}UserName" => json['login']['lgusername'].to_s,
115
+ "#{prefix}UserID" => json['login']['lguserid'].to_s,
116
+ "#{prefix}Token" => json['login']['lgtoken'].to_s
117
+ })
118
+
119
+
120
+ raise SunflowerError, 'unable to log in (no cookies received)!' if !@cookies
121
+
122
+
123
+ # 3. confirm you did log in by checking the watchlist.
124
+ @loggedin=true
125
+ r=self.API('action=query&list=watchlistraw')
126
+ if r['error'] && r['error']['code']=='wrnotloggedin'
127
+ @loggedin=false
128
+ raise SunflowerError, 'unable to log in!'
129
+ end
130
+
131
+ # 4. check bot rights
132
+ r=self.API('action=query&list=allusers&aulimit=1&augroup=bot&aufrom='+(CGI.escape user))
133
+ unless r['query']['allusers'][0]['name']==user
134
+ warn 'Sunflower - this user does not have bot rights!' if @warnings
135
+ @is_bot=false
136
+ else
137
+ @is_bot=true
138
+ end
139
+
140
+ return self
141
+ end
142
+
143
+ def log t
144
+ File.open('log.txt','a'){|f| f.puts t}
145
+ end
146
+
147
+ def is_bot?
148
+ @is_bot
149
+ end
150
+ end
151
+
152
+ # Class representng single Wiki page. To load specified page, use #new/#get/#load method.
153
+ #
154
+ # When calling Page.new, at first only the text will be loaded - attributes and edit token will be loaded when needed, or when you call #preload_attrs.
155
+ #
156
+ # If you are using multiple Sunflowers, you have to specify which wiki this page belongs to using second argument of function; you can pass whole URL (same as when creating new Sunflower) or just language code.
157
+ #
158
+ # To save page, use #save/#put method. Optional argument is new title page, if ommited, page is saved at old title. Summary can be passed as second parameter. If it's ommited, s.summary is used. If it's empty too, error is raised.
159
+ #
160
+ # To get Sunflower instance which this page belongs to, use #sunflower of #belongs_to.
161
+ class Page
162
+ INVALID_CHARS = %w(# < > [ ] | { })
163
+ INVALID_CHARS_REGEX = Regexp.union *INVALID_CHARS
164
+
165
+ attr_accessor :text
166
+ attr_reader :orig_text
167
+
168
+ attr_reader :sunflower
169
+ alias :belongs_to :sunflower
170
+
171
+ attr_reader :pageid, :ns, :title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection #prop=info
172
+
173
+ # calling any of these accessors will fetch the data.
174
+ [:pageid, :ns, :title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection].each do |meth|
175
+ define_method meth do
176
+ preload_attrs unless @preloaded_attrs
177
+ instance_variable_get "@#{meth}"
178
+ end
179
+ end
180
+
181
+ def initialize title='', wiki=''
182
+ raise SunflowerError, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
183
+
184
+ @modulesExecd=[] #used by sunflower-commontasks.rb
185
+ @summaryAppend=[] #used by sunflower-commontasks.rb
186
+
187
+ @title=title
188
+ wiki=wiki+'.wikipedia.org' if wiki.index('.')==nil && wiki!=''
189
+
190
+ if wiki==''
191
+ count=ObjectSpace.each_object(Sunflower){|o| @sunflower=o}
192
+ raise SunflowerError, 'you must pass wiki name if using multiple Sunflowers at once!' if count>1
193
+ else
194
+ ObjectSpace.each_object(Sunflower){|o| @sunflower=o if o.wikiURL==wiki}
195
+ end
196
+
197
+ if title==''
198
+ @text=''
199
+ @orig_text=''
200
+ return
201
+ end
202
+
203
+ preload_text
204
+ end
205
+
206
+ def preload_text
207
+ r = @sunflower.API('action=query&prop=revisions&rvprop=content&titles='+CGI.escape(@title))
208
+ r = r['query']['pages'].first
209
+ if r['missing']
210
+ @text = ''
211
+ elsif r['invalid']
212
+ raise SunflowerError, 'title invalid: '+@title
213
+ else
214
+ @text = r['revisions'][0]['*']
215
+ end
216
+
217
+ @orig_text = @text
218
+
219
+ @preloaded_text = true
220
+ end
221
+
222
+ def preload_attrs
223
+ r = @sunflower.API('action=query&prop=info&inprop=protection&intoken=edit&titles='+CGI.escape(@title))
224
+ r = r['query']['pages'].first
225
+ r.each{|key, value|
226
+ self.instance_variable_set('@'+key, value)
227
+ }
228
+
229
+ @preloaded_attrs = true
230
+ end
231
+
232
+ def dump_to file
233
+ if file.respond_to? :write #probably file or IO
234
+ file.write @text
235
+ else #filename?
236
+ File.open(file.to_s, 'w'){|f| f.write @text}
237
+ end
238
+ end
239
+
240
+ def dump
241
+ self.dump_to @title.gsub(/[^a-zA-Z0-9\-]/,'_')+'.txt'
242
+ end
243
+
244
+ def save title=@title, summary=nil
245
+ preload_attrs unless @preloaded_attrs
246
+
247
+ summary = @sunflower.summary if !summary
248
+
249
+ raise SunflowerError, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
250
+ raise SunflowerError, 'no summary!' if (!summary or summary=='') && @summaryAppend.empty?
251
+
252
+ unless @summaryAppend.empty?
253
+ if !summary or summary==''
254
+ summary = @summaryAppend.uniq.join(', ')
255
+ else
256
+ summary = summary.sub(/,\s*\Z/, '') + ', ' + @summaryAppend.uniq.join(', ')
257
+ end
258
+ end
259
+
260
+ if @orig_text==@text && title==@title
261
+ @sunflower.log('Page '+title+' not saved - no changes.')
262
+ return
263
+ end
264
+
265
+
266
+
267
+ self.code_cleanup if @sunflower.always_do_code_cleanup && self.respond_to?('code_cleanup')
268
+
269
+ r=@sunflower.API("action=edit&bot=1&title=#{CGI.escape(title)}&text=#{CGI.escape(@text)}&summary=#{CGI.escape(summary)}&token=#{CGI.escape(@edittoken)}")# if @sunflower.isBot?
270
+ end
271
+ alias :put :save
272
+
273
+ def self.get title, wiki=''
274
+ Page.new(title, wiki)
275
+ end
276
+
277
+ def self.load title, wiki=''
278
+ Page.new(title, wiki)
279
+ end
280
+ end
281
+
282
+ class Hash
283
+ # just a lil patch
284
+ def first
285
+ self.values[0]
286
+ end
287
+ end
288
+