Class: Wikian::Post

Inherits:
Object
  • Object
show all
Defined in:
lib/wikian/post.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Post

Returns a new instance of Post.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/wikian/post.rb', line 12

def initialize(args)
  @args = args

  long_to_short_options

  # input wikitext file
  raise WikiFileError unless @input_file = args.find{|f| File.exist? f}

  site = input_file.match(/\.(.*)\.wiki/)&.[](1)
  raise(WikiFileNameError, "Use the Input file name convention <article_name>.<site>.wiki") unless site

  @baseurl = "https://#{site}/w/api.php"

  @header = {}

  @username = ENV['WIKI_USER']

  @debug = (args & %w(-d)).length > 0 ? true : false
rescue => e
  puts "#{e.class} in #{__FILE__}", e.message
  exit
end

Instance Attribute Details

#argsObject

Returns the value of attribute args.



8
9
10
# File 'lib/wikian/post.rb', line 8

def args
  @args
end

#baseurlObject

Returns the value of attribute baseurl.



8
9
10
# File 'lib/wikian/post.rb', line 8

def baseurl
  @baseurl
end

#body_textObject

Returns the value of attribute body_text.



8
9
10
# File 'lib/wikian/post.rb', line 8

def body_text
  @body_text
end

Returns the value of attribute csrf_cookie.



8
9
10
# File 'lib/wikian/post.rb', line 8

def csrf_cookie
  @csrf_cookie
end

#csrf_tokenObject

Returns the value of attribute csrf_token.



8
9
10
# File 'lib/wikian/post.rb', line 8

def csrf_token
  @csrf_token
end

#debugObject

Returns the value of attribute debug.



8
9
10
# File 'lib/wikian/post.rb', line 8

def debug
  @debug
end

#headerObject

Returns the value of attribute header.



8
9
10
# File 'lib/wikian/post.rb', line 8

def header
  @header
end

#input_fileObject

Returns the value of attribute input_file.



8
9
10
# File 'lib/wikian/post.rb', line 8

def input_file
  @input_file
end

#latest_contentObject

Returns the value of attribute latest_content.



8
9
10
# File 'lib/wikian/post.rb', line 8

def latest_content
  @latest_content
end

#latest_revisionObject

Returns the value of attribute latest_revision.



8
9
10
# File 'lib/wikian/post.rb', line 8

def latest_revision
  @latest_revision
end

Returns the value of attribute login_cookie.



8
9
10
# File 'lib/wikian/post.rb', line 8

def 
  @login_cookie
end

#login_tokenObject

Returns the value of attribute login_token.



8
9
10
# File 'lib/wikian/post.rb', line 8

def 
  @login_token
end

#metadataObject

Returns the value of attribute metadata.



8
9
10
# File 'lib/wikian/post.rb', line 8

def 
  
end

#paramsObject

Returns the value of attribute params.



8
9
10
# File 'lib/wikian/post.rb', line 8

def params
  @params
end

#queryObject

Returns the value of attribute query.



8
9
10
# File 'lib/wikian/post.rb', line 8

def query
  @query
end

#usernameObject

Returns the value of attribute username.



8
9
10
# File 'lib/wikian/post.rb', line 8

def username
  @username
end

Instance Method Details

#build_query_stringObject



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/wikian/post.rb', line 123

def build_query_string
  @params={}
  params['action'] = 'edit'
  params['format'] = Wikian::RESPONSE_FORMAT
  params['title'] = input_file.sub(/\..*/,'')
   = File.exist?(Wikian.meta_file) ? YAML.load(File.open(Wikian.meta_file)) : {}
  params['starttimestamp'] =
    if timestamp = .dig('meta', params['title'], 'timestamp')
      timestamp
    else
      FileUtils.mkdir_p(Wikian.meta_dir)
       = {'meta' => {params['title']  => {'timestamp' => File.mtime(input_file).utc.iso8601}}}
      File.write(Wikian.meta_file, YAML.dump())
    end
  wikitext = File.read(input_file)
  if args.have?(%w(-a))
    params['appendtext'] = wikitext
  elsif args.have?(%w(-p))
    params['prependtext'] = wikitext
  else
    # pass the wikitext in request body
    @body_text = wikitext
  end
  if args.have?(%w(-c))
    params['captchaid'], params['captchaword'] = args[args.index('-c')+1].split(':')
  end
  if args.have?(%w(-m))
    params['summary'] = args[args.index('-m')+1]
  end
  if args.have?(%w(-s))
    params['section'] = args[args.index('-s')+1]
  end
end


73
74
75
# File 'lib/wikian/post.rb', line 73

def csrf_cookie_file
  'csrf_cookie'
end

#expired_cookie?Boolean

check if the cookie is expired (older than an hour)

Returns:

  • (Boolean)


69
70
71
# File 'lib/wikian/post.rb', line 69

def expired_cookie?
  File.exist?(csrf_cookie_file) && ((Time.now - File.open(csrf_cookie_file).stat.ctime)/3600 > 1)
end


91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/wikian/post.rb', line 91

def get_csrf_cookie
  puts("\nGetting csrf cookie using token #{login_token}") if debug
  url = URI("#{baseurl}?action=login&lgname=#{username}&format=json")
  req = Net::HTTP::Post.new(url, header.merge('cookie' => , 'content-type' => 'application/x-www-form-urlencoded'))
  req.set_form_data('lgpassword' => ENV['SECRET_WIKI_PASS'], 'lgtoken' => )
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  res=http.request(req)
  @csrf_cookie = (res['set-cookie'])
  File.write(csrf_cookie_file, @csrf_cookie)
  puts(res.body) if debug
end

#get_csrf_tokenObject



114
115
116
117
118
119
120
121
# File 'lib/wikian/post.rb', line 114

def get_csrf_token
  puts("\nGetting csrf token using csrf cookies") if debug
  url = URI("#{baseurl}?action=query&meta=tokens&format=json&prop=info|revisions&rvprop=timestamp")
  res = URI.open(url, header.merge('cookie' => csrf_cookie))
  json = JSON.parse(res.read)
  @csrf_token = json.dig('query','tokens','csrftoken')
  puts(json) if debug
end

#get_latest_revisionObject



104
105
106
107
108
109
110
111
112
# File 'lib/wikian/post.rb', line 104

def get_latest_revision
  res = URI.open("#{baseurl}?action=query&prop=revisions&titles=#{params['title']}&rvslots=main&rvprop=content|timestamp&format=json")
  @latest_revision = JSON.parse(res.read).dig('query', 'pages').values.first.dig('revisions').first
  params['basetimestamp'] = latest_revision['timestamp']
  @latest_content = latest_revision.dig('slots', 'main', 'content') ||
                    latest_revision.dig('slots', 'main', '*') ||
                    latest_revision.dig('slots', '*') ||
                    latest_revision.dig('*')
end

#get_login_tokenObject



81
82
83
84
85
86
87
88
89
# File 'lib/wikian/post.rb', line 81

def 
  puts("Getting login token/cookie") if debug
  url = URI("#{baseurl}?action=query&meta=tokens&format=json&type=login")
  res = URI.open(url)
  json = JSON.parse(res.read)
  @login_token = json.dig('query','tokens','logintoken')
  @login_cookie = (res.meta['set-cookie'])
  puts(json) if debug
end

#long_to_short_optionsObject

transform long options like ‘–message’ to short options like ‘-m’



36
37
38
# File 'lib/wikian/post.rb', line 36

def long_to_short_options
  args.map! {|opt| opt[0,2] == '--' ? opt[1,2] : opt}
end

#merge_versions(content_one, content_two) ⇒ Object

merge two version with diff. TODO the merge command is ADDING differences, but it should MERGE differences.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/wikian/post.rb', line 159

def merge_versions(content_one, content_two)
  tmp_local = Tempfile.open {|f| f.write content_one; f}
  tmp_latest = Tempfile.open {|f| f.write content_two; f}

  diff_cmd = "diff #{tmp_local.path} #{tmp_latest.path}"
  system("#{diff_cmd}")

  # raise error until the above TODO is solved
  raise WikiMergeError, "Please merge with latest version and try again"

  merge_cmd = "diff --line-format %L #{tmp_local.path} #{tmp_latest.path}"
  @body_text = %x(#{merge_cmd})
rescue => e
  puts "#{e.class} in #{__FILE__}", e.message
  exit
end

#postObject



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/wikian/post.rb', line 40

def post
  # remove expired cookie
  if expired_cookie? || args.have?(%w(-r))
    FileUtils.rm_f(csrf_cookie_file)
  end

  # csrf_cookie can be reused among multiple requests. But csrf_token must be updated on each request
  if File.exist?(csrf_cookie_file)
    @csrf_cookie = File.read(csrf_cookie_file)
  else
    

    get_csrf_cookie
  end
  build_query_string

  get_latest_revision

  if !args.include?('-f') && @body_text &&  Time.parse(params['starttimestamp']) < Time.parse(params['basetimestamp'])
    puts "Edit conflict detected"
    merge_versions(@body_text, latest_content)
  end

  get_csrf_token

  upload_article
end


77
78
79
# File 'lib/wikian/post.rb', line 77

def (cookie)
  cookie.gsub(/secure;|path=.*?[,;]|httponly[;,]|samesite=.*?[;,]|expires=.....*?[,;]|domain=.*?;|max-age=.*?[;,]/i,'').squeeze(' ')
end

#update_metadataObject



176
177
178
179
# File 'lib/wikian/post.rb', line 176

def 
  ['meta'].merge!(params['title'] => {'timestamp' => Time.now.utc.iso8601})
  File.write(Wikian.meta_file, YAML.dump())
end

#upload_articleObject



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/wikian/post.rb', line 181

def upload_article
  @query = URI.encode_www_form(params)
  puts("\nUploading the wiki article using csrf token #{csrf_token}") if debug
  url = URI("#{baseurl}?#{query}")
  req = Net::HTTP::Post.new(url, header.merge('cookie' => csrf_cookie, 'content-type' => 'application/x-www-form-urlencoded'))
  http = Net::HTTP.new(url.host, url.port)
  req_body = body_text.nil? ? {token: csrf_token} : {token: csrf_token, text: body_text}
  req.set_form_data(req_body)
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  res=http.request(req)
  json = JSON.parse(res.body)
  puts(json) if debug
  if json.dig('error')
    puts "An error occurred while uploding the file",
         "Try pasing the '-r' option to remove '#{csrf_cookie_file}'",
         "Or pass '-d' option for debugging"
  else
    
    puts "Article uploaded"
  end
end