amigojapan

new version of top3 from other contributor

Mar 31st, 2016
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 9.49 KB | None | 0 0
  1. # encoding: utf-8
  2. # This plugin was developted for the K5 project by amigojapan
  3. # See files README.md and COPYING for copyright and licensing information.
  4.  
  5. # Top3 plugin
  6.  
  7. require 'set'
  8. require 'uri'
  9. require 'date'
  10. require 'net/http'
  11.  
  12. require_relative '../../IRCPlugin'
  13.  
  14. class Top3 < IRCPlugin
  15.   Description = 'top3 gives the top 3 Japanese writers of the month (made by amigojapan)'
  16.   Commands = {
  17.     :top3 => 'displays the top 3 Japanese writers of the month. optional .top3 exclude user1 user2... (made by amigojapan)',
  18.     :rank => 'displays the rank of the user(made by amigojapan)',
  19.     :chart => 'shows a chart of user progress. Usage: .chart or .chart user1 user2 etc (made by amigojapan)',
  20.     #:chart_top3 => 'shows a chart of you and the top3 users of this month, usage .chart_top3 exclude user1 user2... (made by amigojapan)',
  21.     :opt_out => 'Takes away permision for people to see your data (made by amigojapan)',
  22.     :reopt_in => 'Regives permision of people to see your data (made by amigojapan)',
  23.     :mlist => 'shows the rank list for this month (made by amigojapan)'
  24.   }
  25.   Dependencies = [ :StorageYAML ]
  26.  
  27.   CHART_COLORS = %w(ff0000 0000ff 00ff00 ff00ff ffff00 00ffff)
  28.  
  29.   def afterLoad
  30.     @storage = @plugin_manager.plugins[:StorageYAML]
  31.     @top3 = @storage.read('Top3') || {}
  32.     @opt_outs = @storage.read('Optouts') || {}
  33.   end
  34.  
  35.   def beforeUnload
  36.     @storage = nil
  37.     @top3 = nil
  38.     @opt_outs = nil
  39.  
  40.     nil
  41.   end
  42.  
  43.   def on_privmsg(msg)
  44.     if msg.bot_command == :top3
  45.       top3(msg)
  46.     elsif msg.bot_command == :rank
  47.       rank(msg)
  48.     elsif msg.bot_command == :chart
  49.       chart(msg)
  50.     elsif msg.bot_command == :opt_out
  51.       opt_out(msg)
  52.     elsif msg.bot_command == :reopt_in
  53.       reopt_in(msg)
  54.     elsif msg.bot_command == :chart_top3
  55.       chart_top3(msg)
  56.     elsif msg.bot_command == :mlist
  57.       mlist(msg)
  58.     elsif !msg.private? and !msg.bot_command
  59.       count(msg)
  60.     end
  61.   end
  62.  
  63.   def opt_out(msg)
  64.     @opt_outs[msg.nick]='opted-out'
  65.     @storage.write('Optouts', @opt_outs)
  66.     msg.reply 'you have opted out'
  67.   end
  68.  
  69.   def reopt_in(msg)
  70.     @opt_outs[msg.nick]='reopted-in'
  71.     @storage.write('Optouts', @opt_outs)
  72.     msg.reply 'you have re-opted in'
  73.   end
  74.  
  75.   def chart_top3(msg)
  76.   end
  77.  
  78.   def chart(msg)
  79.     nicks = msg.tail ? msg.tail.split : []
  80.     nicks = [msg.nick] if nicks.empty?
  81.  
  82.     if nicks.size > CHART_COLORS.size
  83.       msg.reply "Too many nicks specified (at most #{CHART_COLORS.size} supported)."
  84.       return
  85.     end
  86.  
  87.     person_data = nicks.map do |person|
  88.       if @opt_outs[person] == 'opted-out'
  89.         msg.reply "Sorry, #{person} has opted out."
  90.         return
  91.       end
  92.       data = @top3[person] && JSON.parse(@top3[person])
  93.       unless data && !data.empty?
  94.         msg.reply "Sorry, we have no data for #{person}, check spelling."
  95.         return
  96.       end
  97.  
  98.       [person, data]
  99.     end
  100.  
  101.     person_data = person_data.map do |person, data|
  102.       data = data.flat_map do |year, months|
  103.         year = year.to_i
  104.         months.map do |month, counter|
  105.           [[year, month.to_i], counter.to_i]
  106.         end
  107.       end.sort
  108.  
  109.       [person, Hash[data]]
  110.     end
  111.  
  112.     person_data = Hash[person_data]
  113.  
  114.     timeline = person_data.values.flat_map(&:keys).sort.uniq
  115.     max = person_data.values.flat_map(&:values).max
  116.  
  117.     prev_year = 0
  118.     timeline_labels = timeline.map do |year, month|
  119.       if year == prev_year
  120.         "|#{month}"
  121.       else
  122.         prev_year = year
  123.         "|#{year} #{month}"
  124.       end
  125.     end.join
  126.  
  127.     counter_labels = "|0|#{max/4}|mid #{max/2}|#{(max*3)/4}|max #{max}"
  128.  
  129.     data_points = person_data.map do |_, data|
  130.       timeline.map do |time|
  131.         data[time] ? ((100 * data[time].to_f) / max).to_i : -1
  132.       end.join(',')
  133.     end.join('|')
  134.  
  135.     charturl = URI('https://chart.googleapis.com/chart')
  136.     params = {
  137.         :cht => 'lc',
  138.         :chs => 500,
  139.         :chxt => 'x,y',
  140.         :chdl => nicks.join('|'), #TODO: escape vertical bars in nicks somehow
  141.         :chco => CHART_COLORS[0, nicks.size].join(','),
  142.         :chxl => "0:#{timeline_labels}|1:#{counter_labels}",
  143.         :chd => "t:#{data_points}",
  144.     }
  145.     charturl.query = URI.encode_www_form(params)
  146.  
  147.     tiny_url = tinyurlify(charturl)
  148.  
  149.     msg.reply("Chart: #{tiny_url}")
  150.   end
  151.  
  152.   def get_exclude_array(args = '')
  153.     splitmsg=args.split #we need this later to get the people to exclude
  154.     if splitmsg.include?('exclude')
  155.       exclude_array = splitmsg.drop(splitmsg.index('exclude')+1) #make exclude list
  156.     else
  157.       exclude_array = []
  158.     end
  159.  
  160.     exclude_array = Set.new(exclude_array)
  161.  
  162.     exclude_array + @opt_outs.find_all do |_, v|
  163.       v == 'opted-out'
  164.     end.map(&:first)
  165.   end
  166.  
  167.   def get_top_list(exclude_array = [])
  168.     date_now = Date.today
  169.     year_now = date_now.year.to_s
  170.     month_now = date_now.mon.to_s
  171.  
  172.     unsorted = []
  173.     @top3.each do |nick, data|
  174.       next if exclude_array.include?(nick)
  175.       years = JSON.parse(data)
  176.       next unless years[year_now] #year not found
  177.       next unless years[year_now][month_now] #display only the entries for the current month
  178.  
  179.       unsorted << [years[year_now][month_now], nick]
  180.     end
  181.  
  182.     unsorted.sort.reverse
  183.   end
  184.  
  185.   def mlist(msg)
  186.     exclude_array = get_exclude_array(msg.tail || '')
  187.     sorted = get_top_list(exclude_array)
  188.  
  189.     out = sorted.each_with_index.map do |(cnt, nick), rank|
  190.       " ##{rank+1} #{nick} CJK chars: #{cnt}\n"
  191.     end.join
  192.  
  193.     gist_reply = gistify(out)
  194.     tiny_url = tinyurlify(gist_reply['files']['rank.txt']['raw_url'])
  195.  
  196.     msg.reply("Ranked list: #{tiny_url}")
  197.   end
  198.  
  199.   GIST_URI = URI('https://api.github.com/gists')
  200.  
  201.   def gistify(out)
  202.     payload = {
  203.         'description' => "Ranked list of users for #{Time.now} server time\n",
  204.         'public' => false,
  205.         'files' => {
  206.             'rank.txt' => {
  207.                 'content' => out
  208.             }
  209.         }
  210.     }
  211.  
  212.     req = Net::HTTP::Post.new(GIST_URI.path)
  213.     req.body = payload.to_json
  214.  
  215.     # GitHub API is strictly via HTTPS, so SSL is mandatory
  216.     res = Net::HTTP.start(GIST_URI.hostname, GIST_URI.port, :use_ssl => true) do |http|
  217.       http.request(req)
  218.     end
  219.  
  220.     JSON.parse(res.body)
  221.   end
  222.  
  223.   def top3(msg)
  224.     exclude_array = get_exclude_array(msg.tail || '')
  225.     sorted = get_top_list(exclude_array)
  226.  
  227.     sorted_take = sorted.take(3)
  228.     out = sorted_take.each_with_index.map do |(cnt, nick), rank|
  229.       " ##{rank+1} #{nick} CJK chars: #{cnt}"
  230.     end.join
  231.  
  232.     unless sorted_take.any? {|_, nick| nick == msg.nick}
  233.       out += ' | ' + format_user_stats(msg.nick, sorted, exclude_array)
  234.     end
  235.  
  236.     msg.reply(out)
  237.   end
  238.  
  239.   def rank(msg)
  240.     exclude_array = get_exclude_array
  241.     sorted = get_top_list(exclude_array)
  242.  
  243.     person = msg.tail ? msg.tail.split : []
  244.     person = person.first || msg.nick
  245.  
  246.     out = format_user_stats(person, sorted, exclude_array)
  247.  
  248.     msg.reply(out)
  249.   end
  250.  
  251.   def format_user_stats(person, sorted, exclude_array)
  252.     if exclude_array.include?(person)
  253.       "#{person}'s data cannot be displayed; he opted out or was excluded."
  254.     else
  255.       rank = sorted.index do |_, nick|
  256.         nick == person
  257.       end
  258.  
  259.       if rank
  260.         current_user, _ = sorted[rank]
  261.         "#{person}'s CJK count is: #{current_user}, currently ranked ##{rank+1} of #{sorted.size}"
  262.       else
  263.         "#{person} has not typed any Japanese this month :("
  264.       end
  265.     end
  266.   end
  267.  
  268.   def contains_cjk?(s)
  269.     !!(s =~ /\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}/)
  270.   end
  271.  
  272.   def count(msg)
  273.     s2=msg.message.split(//)
  274.     chars=0
  275.     s2.each do |s|
  276.       if contains_cjk?(s)
  277.         chars=chars+1
  278.       end
  279.     end
  280.     if @top3[msg.nick].nil? #no data add yearly and monthly arrays
  281.       years={}
  282.       years[Date.today.year.to_s]={}
  283.       years[Date.today.year.to_s][Date.today.mon.to_s]=chars
  284.       @top3[msg.nick] =years.to_json
  285.       @storage.write('Top3', @top3)
  286.       return
  287.     end
  288.     if chars > 0
  289.       years=JSON.parse(@top3[msg.nick])
  290.       if years[Date.today.year.to_s].nil?
  291.         years[Date.today.year.to_s]={}
  292.       end
  293.       if years[Date.today.year.to_s][Date.today.mon.to_s].nil?
  294.         years[Date.today.year.to_s][Date.today.mon.to_s]=0
  295.       end
  296.       years[Date.today.year.to_s][Date.today.mon.to_s]+=chars
  297.       @top3[msg.nick] =years.to_json
  298.       @storage.write('Top3', @top3)
  299.     end
  300.   end
  301.  
  302.   TINYURL_URL = URI('http://tinyurl.com/api-create.php')
  303.   def tinyurlify(url)
  304.     t = TINYURL_URL.dup
  305.     t.query = URI.encode_www_form(:url => url)
  306.     Net::HTTP.get(t)
  307.   end
  308. end
  309. #(done)Add year tracking
  310. #add anual top3
  311. #(done)Add .rank command so we can see what rank other people have
  312. #request to keep track of nicks even if they change nick
  313. #nick = msg.tail || msg.nick
  314. #user = msg.bot.find_user_by_nick(nick)
  315.  
  316. #(done)futoshi: .top3 without futoshi みたいのはどう?w
  317. #corelax: btw how about this command -> .top3at 201507
  318. #(done)fadd opt-out for chart, because it can be used to spy on people like "how did you type 1million CJK characters over every month for the past 3 years?"
  319. #add https to addresses. note: difficulty in ruby to do this easily because of the complex url
  320. #(done)add an error message when the user does not exist in the charting functions
  321. #(done)SteveTheTribble: I would like to know who is above me and behind me, and in which distance. I may add an option to make a list that displays on the web for people that dont opt out... would that be ok? https://developer.github.com/v3/gists/#create-a-gist
Add Comment
Please, Sign In to add comment