Date Determinator

Aus C3D2
Zur Navigation springen Zur Suche springen

Der Date Determinator ist ein MediaWiki-Bot, welcher Terminfindungen vereinfachen sollen. Die Eingabe passiert als YAML, die Ausgabe kann vom Bot periodisch als hybsche Tabelle erstellt werden.


Beispiele

CodingNight zu dritt

Eingabe:

BEGIN DATA "Test"
 Cool Hacker: 
  24.5.: nein
  23.5.: ja
 Astro: 
  23.5.: ja
  22.5.: weiss noch nicht
 1337 Cracker:
  24.5.: jo
  23.5.: yezz!
END DATA

Ausgabe:

22.5. 23.5. 24.5.
Cool Hacker ja nein
1337 Cracker yezz! jo
Astro weiss noch nicht ja
sum(ja) 0 3 1

Realitätsabgleich 2

16.6. 17.6. 18.6. A 18.6. N 19.6. N 19.6. A 20.6. 21.6. 22.6. 23.6. 24.6. 25.6.
matthias j n j j j j j j n j j j
fukami n j n n n n n n n n n n
Caldrin n j n n j n n n n n j n
nulli n j j j j n n n n n n n
toidinamai j j j j j j j j j j j j
Astro j j n j j j j n n j j j
sum(ja) 3 5 3 4 5 3 3 2 1 3 4 3

Source

Open-source, yada-yada!

date_determinator.rb

WIKI_USER = 'AstRobot'
WIKI_PASSWORD = '...'
PAGE = 'Benutzer:Astro/Date_Determinator'
WIKI = 'http://wiki.c3d2.de/wikipgedia'
HTTP_USER = 'eris'
HTTP_PASSWORD = '...'

require 'yaml'
require 'mediawiki'

include MediaWiki

class DateUser
  attr_reader :name

  def initialize(name, hsh)
    @name = name
    @hsh = hsh
  end

  def dates
    @hsh.keys
  end

  def day(d)
    @hsh[d]
  end

  def day_class(d)
    if day(d) == nil
      nil
    elsif day(d) =~ /^[yj]/i
      true
    elsif day(d) =~ /^n/i
      false
    else
      ""
    end
  end

  def day_style(d)
    case day_class(d)
      when nil then ""
      when true then 'bgcolor="#7fff7f"'
      when false then 'bgcolor="#ff7f7f"'
      else 'bgcolor="#bfbfbf"'
    end
  end
end

class DateData
  def initialize(yaml)
    @users = []
    @error = nil
    begin
      YAML::load(yaml).each { |user,dates|
        @users << DateUser.new(user, dates)
      }
    rescue Exception => e
      puts "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
      @error = e.to_s
    end
  end

  def table
    return @error if @error
    
    s = ''

    ##
    # Collect dates
    ##
    dates = []
    dates_yes = {}
    @users.each { |user| dates += user.dates }

    ##
    # Sort dates
    ##
    dates.uniq!
    dates.sort! { |a,b|
      if a =~ /^(\d+)\.(\d+)\./
        a_day = $1
        a_month = $2
        if b =~ /^(\d+)\.(\d+)\./
          b_day = $1
          b_month = $2
          if a_month.to_i == b_month.to_i
            a_day.to_i <=> b_day.to_i
          else
            a_month.to_i <=> b_month.to_i
          end
        else
          a <=> b
        end
      else
        a <=> b
      end
    }

    ##
    # Construct header
    ##
    s += "{| border=\"1\" cellpadding=\"2\" cellspacing=\"0\" style=\"border-collapse:collapse;\"\n|-\n! \n"
    dates.each { |date|
      s += "!#{date}\n"
      dates_yes[date] = 0
    }

    ##
    # Construct rows
    ##
    @users.each { |user|
      s += "|-\n|[[User:#{user.name}|#{user.name}]]\n"
      dates.each { |date|
        s += "| #{user.day_style(date)} | #{user.day(date)}\n"
        dates_yes[date] += 1 if user.day_class(date) == true
      }
    }

    ##
    # Build summary
    ##
    s += "|-\n|'''sum(ja)'''\n"
    dates.each { |date|
      s += "|'''#{dates_yes[date]}'''\n"
    }
    s += "|}"
  end
end



wiki = Wiki.new(WIKI, HTTP_USER, HTTP_PASSWORD)
wiki.login(WIKI_USER, WIKI_PASSWORD)
page = wiki.article(PAGE)

datasets = {}
current_data_name = nil
current_data_yaml = ''
page.text.split(/\n/).each { |line|
  if line =~ /BEGIN DATA "(.+?)"/
    current_data_name = $1
    current_data_yaml = ''
  elsif line =~ /END DATA/ and current_data_name
    datasets[current_data_name] = DateData.new(current_data_yaml)
    current_data_name = nil
    current_data_yaml = ''
  elsif current_data_name
    current_data_yaml += "#{line}\n"
  end
}


text_old = page.text.dup

signature = /(<!-- BEGIN TABLE ")(.+?)(" -->)(.+?)(<!-- END TABLE -->)/m
page.text.gsub!(signature) { |part|
  begin1,name,begin2,obsolete,end1 = part.match(signature).to_a[1..-1]
  table = datasets[name] ? datasets[name].table : "DATA #{name} not found!"
  "#{begin1}#{name}#{begin2}\n#{table}\n#{end1}"
}

page.submit('Date Determinator run') if page.text != text_old

mediawiki.rb

require 'http-access2'
require 'cgi'
require 'rexml/document'

module MediaWiki
  class Wiki
    ##
    # HTTPAccess2 object, must be accessible by the Wiki's
    # Article children
    attr_accessor :http

    def initialize(url, auth_name=nil, auth_password=nil)
      @url = (url =~ /\/$/) ? url : "#{url}/"

      @http = HTTPAccess2::Client.new(nil, 'Ruby-MediaWiki::Wiki/0.1')

      add_auth(url, auth_name, auth_password) if auth_name and auth_password
    end

    def add_auth(uri, auth_name, auth_password)
      @http.set_basic_auth(uri, auth_name, auth_password)
    end

    def login(username, password)
      postdata = "wpName=#{CGI::escape(username)}&wpPassword=#{CGI::escape(password)}&wpLoginattempt="
      result = @http.post_content(article_url('Special:Userlogin'), postdata)
      if result =~ /<p class='error'>/
        raise "Unable to authenticate as #{username}"
      end
    end

    ##
    # Return a new article with the given name
    # name:: [String] Article name
    # result:: [Article]
    def article(name)
      Article.new(self, name)
    end

    ##
    # Return
    def article_url(name)
      "#{@url}index.php?title=#{CGI::escape(name)}"
    end

    ##
    # Return a hash of special pages:
    # 'Link title' => 'Article name'
    def specialpages
      result = {}
      Article.new(self, 'Special:Specialpages', false).xhtml.each_element('ul/li/a') { |a|
        result[a.attributes['title']] = a.text
      }
      result
    end
  end

  class Article
    attr_accessor :name, :text
    
    def initialize(wiki, name, load_text=true)
      @wiki = wiki
      @name = name

      @text = nil
      @xhtml = nil
      @xhtml_cached = false
      @wp_edittoken = nil
      @wp_edittime = nil

      reload if load_text
    end

    def xhtml
      unless @xhtml_cached
        xhtml_reload
      end
      @xhtml
    end

    def xhtml_reload
      html = @wiki.http.get_content("#{@wiki.article_url(@name)}")
      html.scan(/<!-- start content -->(.+)<!-- end content -->/m) { |content,|
        @xhtml = REXML::Document.new("<xhtml>#{content}</xhtml>").root
      }
      
      @xhtml_cached = true
    end

    def reload
      puts "Loading #{@wiki.article_url(@name)}&action=edit"
      doc = REXML::Document.new(@wiki.http.get_content("#{@wiki.article_url(@name)}&action=edit")).root
      @name = doc.elements['//span[@class="editHelp"]/a'].attributes['title']
      form = doc.elements['//form[@name="editform"]']
      @text = form.elements['textarea[@name="wpTextbox1"]'].text
      begin
        @wp_edittoken = form.elements['input[@name="wpEditToken"]'].attributes['value']
        @wp_edittime = form.elements['input[@name="wpEdittime"]'].attributes['value']
      rescue NoMethodError
        # wpEditToken might be missing, that's ok
      end
    end

    ##
    # TODO: minor_edit, watch_this
    def submit(summary, minor_edit=false, watch_this=false)
      puts "Posting to #{@wiki.article_url(@name)}&action=submit"
      postdata = "wpTextbox1=#{CGI::escape(@text)}&wpSummary=#{CGI::escape(summary)}&wpSave=1&wpEditToken=#{@wp_edittoken}&wpEdittime=#{@wp_edittime}"
      result = @wiki.http.post_content("#{@wiki.article_url(@name)}&action=submit", postdata)
      # TODO: Was edit successful? (We received the document anyways)
    end
  end
end