mitten 0.0.1

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.
@@ -0,0 +1,63 @@
1
+ require 'mechanize'
2
+ require 'nokogiri'
3
+
4
+ class CodePad < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+
8
+ @agent = Mechanize.new
9
+ proxy = ENV['https_proxy'] || ENV['http_proxy']
10
+ if proxy
11
+ proxy = URI.parse(proxy)
12
+ @agent.set_proxy(proxy.host, proxy.port)
13
+ end
14
+ @prefixes = ['C', 'Haskell', 'Lua', 'OCaml', 'PHP', 'Perl', 'Python', 'Ruby', 'Scheme']
15
+ end
16
+
17
+ def on_privmsg(prefix, channel, message)
18
+ @language, @code = nil, nil
19
+ @prefixes.select do |item|
20
+ if message =~ Regexp.new("^#{item}:(.+)$")
21
+ @language = item
22
+ @code = $1.gsub('\\', '').strip
23
+ end
24
+ end
25
+
26
+ unless @language == nil
27
+ notice(channel, @language)
28
+ notice(channel, run_code)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def run_code
35
+ output = nil
36
+
37
+ begin
38
+ @agent.get('http://codepad.org/') do |run_page|
39
+ result = run_page.form_with(:action => '/') do |f|
40
+ f.radiobutton_with(:value => @language).check
41
+ f.code = code_prefix(@language) + @code
42
+ end.submit
43
+ doc = Nokogiri::HTML(result.body)
44
+ output = (doc/'pre').last.text
45
+ end
46
+ output
47
+ rescue Exception
48
+ 'なんかだめー/(^o^)\'
49
+ end
50
+ end
51
+
52
+ def code_prefix(language)
53
+ prefix = ''
54
+ case language
55
+ when 'C'
56
+ prefix = "#include <stdio.h>\n"
57
+ when 'PHP'
58
+ prefix = "<?php \n"
59
+ else
60
+ prefix
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+
4
+ class Fortune < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+
8
+ @sex = {
9
+ :male => 'http://legacy.fortune.yahoo.co.jp/fortune/bin/omikuji?sex=m',
10
+ :female => 'http://legacy.fortune.yahoo.co.jp/fortune/bin/omikuji?sex=f'
11
+ }
12
+ @prefix = @config['prefix'] || 'おみくじ'
13
+ end
14
+
15
+ def on_privmsg(prefix, channel, message)
16
+ case message
17
+ when /^#{@prefix}/
18
+ notice(channel, read_fortune)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def read_fortune
25
+ html = Nokogiri::HTML(open(@sex.values.choice).read)
26
+ fortunes = {}
27
+ message = ''
28
+
29
+ (html/'table/tr/td').each do |content|
30
+ if content.inner_text =~ /^今日のあなたの(.+?)は(.+?)。/
31
+ fortunes[$1] = $2
32
+ end
33
+ end
34
+
35
+ fortunes.reverse_each do |genre, fortune|
36
+ message << "#{genre}:#{fortune} "
37
+ end
38
+ "今日のあなたは #{message}こんな感じだよ♪"
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+
4
+ class Gasoline < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+
8
+ @suffix = @config['suffix'] || 'ガソリンいくら'
9
+ @base_uri = 'http://gogo.gs'
10
+ @rank_uri = @base_uri + '/rank/result/?pref=47&ws=&p_mode=0&mm=0&service%5B3%5D=3&desd=0&x=24&y=17'
11
+ end
12
+
13
+ def on_privmsg(prefix, channel, message)
14
+ case message
15
+ when /^#{@suffix}$/
16
+ gasoline_ranking.each do |gs|
17
+ notice(channel, gs)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def gasoline_ranking
25
+ ranking = []
26
+
27
+ begin
28
+ lists = Nokogiri::HTML(open(@rank_uri).read, nil, 'EUC-JP')/'.tableType02'/'tr'
29
+ lists[2..6].each do |line|
30
+ next unless line.at('td[@colspan="5"]').nil?
31
+
32
+ rank = line.at('.crownBig001').text
33
+ price = line.at('.priceLevelBig5').text
34
+ station = line.at('strong').text
35
+ address = line.at('small').text
36
+ detail = URI.short(@base_uri + line.at('strong/a').attributes['href'])
37
+
38
+ ranking << "#{rank}位 #{price}円 #{station} #{address} (#{detail})"
39
+ end
40
+ rescue Exception => e
41
+ ranking << 'こわれたっ/(^o^)\'
42
+ end
43
+
44
+ ranking
45
+ end
46
+ end
data/plugins/gmail.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'net/https'
2
+ require 'nokogiri'
3
+
4
+ =begin
5
+
6
+ ex. environment.yaml
7
+
8
+ Gmail:
9
+ sleep: 60
10
+ channel: '#Mitten@freenode'
11
+ account: 'account'
12
+ password: 'password'
13
+
14
+ =end
15
+ class Gmail < Mitten::Plugin
16
+ def initialize(*args)
17
+ super
18
+
19
+ @account = @config['account']
20
+ @password = @config['password']
21
+
22
+ proxy = ENV['https_proxy'] || ENV['http_proxy']
23
+ if proxy
24
+ @https = Net::HTTP::Proxy(URI.parse(proxy).host, URI.parse(proxy).port).new('mail.google.com', 443)
25
+ else
26
+ @https = Net::HTTP.new('mail.google.com', 443)
27
+ end
28
+ @mail_cache = {}
29
+ end
30
+
31
+ def before_hook
32
+ login
33
+ end
34
+
35
+ def login
36
+ @https.use_ssl = true
37
+ @https.verify_mode = OpenSSL::SSL::VERIFY_NONE
38
+ @request = Net::HTTP::Get.new('/mail/feed/atom')
39
+ @request.basic_auth(@account, @password)
40
+ end
41
+
42
+ def main
43
+ mail_list = Nokogiri::XML(@https.request(@request).body)
44
+ (mail_list/'entry').each do |entry|
45
+ id = entry.at('id').content
46
+ unless @mail_cache.key? id
47
+ @mail_cache[id] = true
48
+ title = entry.at('title').text
49
+ name = entry.at('name').text
50
+ link = entry.at('link')['href']
51
+
52
+ @channels.each do |channel|
53
+ notice(channel, "Gmail: (#{name}) #{title} #{URI.short(link)}")
54
+ sleep 5
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ require 'nokogiri'
2
+
3
+ class GoogleProfile < Mitten::Plugin
4
+ def initialize(*args)
5
+ super
6
+
7
+ @suffix = @config['suffix'] || 'のプロフィールが知りたい'
8
+ @limit = @config['limit'] || 3
9
+ end
10
+
11
+ def on_privmsg(prefix, channel, message)
12
+ case message
13
+ when /^(.+?)#{@suffix}/
14
+ get_profile($1).each { |profile| notice(channel, profile) }
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def get_profile(target)
21
+ profiles = []
22
+ uri = URI.escape("http://www.google.com/profiles?q=#{target}")
23
+ result = Nokogiri::HTML(Kconv.toutf8(open(uri).read))/'div.profile-result'
24
+
25
+ unless result.count == 0
26
+ result[0...@limit].each do |profile|
27
+ detail = profile.at('div.profile-result-text-block').text
28
+ uri = URI.short("http://www.google.com#{profile.at('a')['href']}")
29
+
30
+ profiles << "#{detail} (#{uri})"
31
+ end
32
+ else
33
+ profiles << 'そんな人いにゃい o(>_<)o'
34
+ end
35
+ profiles
36
+ end
37
+ end
@@ -0,0 +1,56 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+
4
+ class GoogleTransit < Mitten::Plugin
5
+ def on_privmsg(prefix, channel, message)
6
+ case message
7
+ when /^(.+)から(.+)(へ|まで|に)行.+/
8
+ search_route($1, $2).each { |route| notice(channel, route) }
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def search_route(search_from, search_to)
15
+ from = URI.escape(search_from.toutf8)
16
+ to = URI.escape(search_to.toutf8)
17
+ query = "http://maps.google.co.jp/maps?f=q&source=s_q&hl=ja&geocode=&q=from%3A+#{from}+to%3A+#{to}"
18
+
19
+ begin
20
+ html = Nokogiri::HTML(open(query).read)
21
+ route = html/'#transit_route_0'
22
+
23
+ unless route.empty?
24
+ start_place = (html/'#ddw_addr_area_0/#sxaddr/div.sa').text.toutf8
25
+ end_place = (html/'#ddw_addr_area_1/#sxaddr/div.sa').text.toutf8
26
+ time = (html/'#transit_route_0').search('span.ts_jtime').text.toutf8
27
+ cost = (html/'#transit_route_0').search('span.ts_routecost').text.toutf8
28
+
29
+ messages = ["#{start_place} から #{end_place} へ行くにはね,"]
30
+ messages << "だいたい #{time} で #{cost} ぐらいかかるみたい!"
31
+
32
+ (route/'table.ts_step').each_with_index do |step, counter|
33
+ counter += 1
34
+ longline = (step/'span.longline').text.toutf8
35
+ action = (step/'span.action').text.toutf8
36
+
37
+ unless longline.empty?
38
+ locations = step/'span.location'
39
+
40
+ messages << "#{counter} : #{action} で「#{longline}」に乗る"
41
+ messages << "  [発] #{locations[0].text.toutf8}"
42
+ messages << "  [着] #{locations[1].text.toutf8}"
43
+ else
44
+ location = (step/'span.location').text.toutf8
45
+ messages << "#{counter} : #{location} #{action}"
46
+ end
47
+ end
48
+ messages << "詳しくはここ見てね♪ (#{URI.short(query)})"
49
+ else
50
+ '/(^o^)\ 迷子!'
51
+ end
52
+ rescue Exception
53
+ '/(^o^)\ 迷子!'
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ require 'nokogiri'
2
+
3
+ class GoogleWeather < Mitten::Plugin
4
+ def initialize(*args)
5
+ super
6
+
7
+ @suffix = @config['suffix'] || 'の天気教えて'
8
+ @indexes = {'今日の' => 0, '明日の' => 1, '明後日の' => 2, '明々後日の' => 3}
9
+ end
10
+
11
+ def on_privmsg(prefix, channel, message)
12
+ case message
13
+ when /^(.+?の|)(.+?)#{@suffix}/
14
+ option = '今日の'
15
+ option = $1 unless $1 == ''
16
+ keyword = $2
17
+
18
+ notice(channel, get_weather(keyword, option))
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def get_weather(keyword, option)
25
+ uri = URI.escape("http://www.google.co.jp/search?q=#{keyword}+週間天気")
26
+ html = Nokogiri::HTML(Kconv.toutf8(open(uri).read))
27
+
28
+ city = (html/'h3.r/b').first.text
29
+ weather = ((html/'div[@align="center"]')/'img')[@indexes[option]]['title']
30
+ temperature = ((html/'div[@align="center"]')/'nobr')[@indexes[option]].text
31
+
32
+ "#{option}#{city}の天気は #{weather} だよっ [#{temperature}] (#{URI.short(uri)})"
33
+ end
34
+ end
@@ -0,0 +1,139 @@
1
+ require 'uri'
2
+ require 'mechanize'
3
+ require 'nokogiri'
4
+
5
+ =begin
6
+
7
+ ex. environment.yaml
8
+
9
+ MixiVoice:
10
+ sleep: 60
11
+ channel: '#Mitten@freenode'
12
+ email: 'email'
13
+ password: 'password'
14
+
15
+ =end
16
+ class MixiVoice < Mitten::Plugin
17
+ MIXI_LOGIN_URI = 'http://mixi.jp'
18
+ RECENT_VOICE_URI = MIXI_LOGIN_URI + '/recent_echo.pl'
19
+
20
+ def initialize(*args)
21
+ super
22
+
23
+ @email = @config['email']
24
+ @password = @config['password']
25
+ @nickname = @config['nickname']
26
+
27
+ @agent = Mechanize.new
28
+ if ENV['http_proxy']
29
+ proxy = URI.parse(ENV['http_proxy'])
30
+ @agent.set_proxy(proxy.host, proxy.port)
31
+ end
32
+
33
+ @caches = []
34
+ end
35
+
36
+ def before_hook
37
+ login
38
+ @identity = get_identity
39
+ end
40
+
41
+ def login
42
+ @agent.get MIXI_LOGIN_URI do |login_page|
43
+ login_page.form 'login_form' do |form|
44
+ form.email = @email
45
+ form.password = @password
46
+ end.submit
47
+ end
48
+ end
49
+
50
+ def on_privmsg(prefix, channel, message)
51
+ if prefix =~ Regexp.new(@nickname)
52
+ case message
53
+ when /^re ([0-9]+) (.+)/
54
+ reply(channel, $1, $2)
55
+ when /^rm ([0-9]+)/
56
+ delete($1)
57
+ when /^add (.+)/
58
+ add($1)
59
+ end
60
+ end
61
+ end
62
+
63
+ def main
64
+ get
65
+ end
66
+
67
+ def get
68
+ voices = crawl_recent_voice
69
+ voices.sort.each do |key, voice|
70
+ if @caches.empty? or !@caches.has_key? key
71
+ @channels.each do |channel|
72
+ notice(channel, "mixi voice: [#{voice[:nickname]}]#{voice[:reply]} #{voice[:comment]} (#{key})")
73
+ sleep 5
74
+ end
75
+ end
76
+ end
77
+ @caches = voices
78
+ end
79
+
80
+ def get_identity
81
+ recent_page = @agent.get RECENT_VOICE_URI
82
+ identity = (Nokogiri::HTML(recent_page.body)/'input#post_key').first['value']
83
+ end
84
+
85
+ def reply(channel, key, voice)
86
+ if @caches.has_key? key
87
+ member_id = @caches[key][:member_id]
88
+ post_time = @caches[key][:post_time]
89
+
90
+ @agent.get RECENT_VOICE_URI do |post_page|
91
+ post_page.form_with(:action => '/add_echo.pl') do |form|
92
+ form.body = voice
93
+ form.parent_member_id = member_id
94
+ form.parent_post_time = post_time
95
+ end.submit
96
+ end
97
+ else
98
+ notice(channel, '指定された返信先が見つかりません')
99
+ end
100
+ end
101
+
102
+ def delete(post_time)
103
+ @agent.post "http://mixi.jp/delete_echo.pl?post_time=#{post_time}&post_key=#{@identity}&redirect=recent_echo"
104
+ @caches = crawl_recent_voice
105
+ end
106
+
107
+ def add(voice)
108
+ @agent.get RECENT_VOICE_URI do |post_page|
109
+ post_page.form_with(:action => 'add_echo.pl') do |form|
110
+ form.body = voice
111
+ end.submit
112
+ end
113
+ end
114
+
115
+ def crawl_recent_voice
116
+ recent_page = @agent.get RECENT_VOICE_URI
117
+ voices = {}
118
+
119
+ (Nokogiri::HTML(recent_page.body)/'td.comment').each do |comment|
120
+ key = timestamp(comment)
121
+ voices[key] = build_voice(comment)
122
+ end
123
+ voices
124
+ end
125
+
126
+ def timestamp(comment)
127
+ comment.at('div.echo_post_time').text
128
+ end
129
+
130
+ def build_voice(comment)
131
+ {
132
+ :member_id => comment.at('div.echo_member_id').text,
133
+ :post_time => comment.at('div.echo_post_time').text,
134
+ :nickname => comment.at('div.echo_nickname').text,
135
+ :reply => ((' ' + comment.at('a').text) if comment.at('a').text =~ /^>/),
136
+ :comment => comment.at('div.echo_body').text
137
+ }
138
+ end
139
+ end
data/plugins/nanapi.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+
4
+ class Nanapi < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+ @suffix = @config['suffix'] || 'テクニック教えて'
8
+ end
9
+
10
+ def on_privmsg(prefix, channel, message)
11
+ case message
12
+ when /^(.+)#{@suffix}$/
13
+ result = search_technique($1)
14
+
15
+ if result == nil
16
+ notice(channel, '/(^o^)\ わかにゃいっ')
17
+ else
18
+ notice(channel, result.to_s)
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def search_technique(keyword)
26
+ doc = Nokogiri::XML(open("http://nanapi.jp/search/keyword:#{URI.encode(keyword)}/feed.rss").read)
27
+ item = (doc/'item').to_a.choice
28
+
29
+ "#{item.at('title').text} - #{URI.short(item.at('link').text)}" if item
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ require 'open-uri'
2
+ require 'rss'
3
+
4
+ class NewspaperHeadlines < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+
8
+ @publishers = {
9
+ '琉球新報' => 'http://rss.ryukyushimpo.jp/rss/ryukyushimpo/index.rdf',
10
+ '沖縄タイムス' => 'http://www.okinawatimes.co.jp/rss/20/index.xml',
11
+ '読売新聞' => 'http://rss.yomiuri.co.jp/rss/yol/topstories',
12
+ '毎日新聞' => 'http://mainichi.jp/rss/etc/flash.rss',
13
+ '日経新聞' => 'http://www.nikkeibp.co.jp/rss/index.rdf',
14
+ 'CNN' => 'http://headlines.yahoo.co.jp/rss/cnn_c_int.xml',
15
+ '映画ニュース' => 'http://headlines.yahoo.co.jp/rss/cine.xml',
16
+ 'ITMedia' => 'http://headlines.yahoo.co.jp/rss/itmedia_n.xml',
17
+ 'dankogai' => 'http://blog.livedoor.jp/dankogai/index.rdf',
18
+ 'インフルエンザ' => 'http://www3.asahi.com/rss/pandemicflu.rdf'
19
+ }
20
+ @limit = @config['limit'] || 3
21
+ end
22
+
23
+ def on_privmsg(prefix, channel, publisher_name)
24
+ if publisher_name == 'ニュースリスト'
25
+ notice(channel, @publishers.keys.join(' / '))
26
+ elsif @publishers.key? publisher_name
27
+ get_headlines(@publishers[publisher_name]).each do |headline|
28
+ notice(channel, headline.to_s)
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def get_headlines(publisher_uri)
36
+ rss = RSS::Parser.parse(open(publisher_uri).read, false)
37
+ rss.items.delete_if { |item| item.title =~ /(AD|PR)/ }
38
+
39
+ headlines = [rss.channel.title + ' のニュースだよ!']
40
+ rss.items[0...@limit].each do |item|
41
+ title = item.title
42
+ url = URI.short(item.link)
43
+ date = item.date.strftime("%d日 %H:%M")
44
+ headlines << "[#{date}] #{title} (#{url})"
45
+ end
46
+ headlines
47
+ end
48
+ end
@@ -0,0 +1,60 @@
1
+ require 'mechanize'
2
+ require 'nokogiri'
3
+
4
+ =begin
5
+
6
+ ex. environment.yaml
7
+
8
+ OpenPNENewDiaryCheck:
9
+ sleep: 60
10
+ channel: '#Mitten@freenode'
11
+ uri : 'http://openpne.example.com'
12
+ username: 'username'
13
+ password: 'password'
14
+
15
+ =end
16
+ class OpenPNENewDiaryCheck < Mitten::Plugin
17
+ def initialize(*args)
18
+ super
19
+
20
+ @uri = @config['uri']
21
+ @username = @config['username']
22
+ @password = @config['password']
23
+ @diaries = {}
24
+ end
25
+
26
+ def before_hook
27
+ login
28
+ end
29
+
30
+ def login
31
+ @agent = Mechanize.new
32
+ @agent.get(@uri) do |login_page|
33
+ login_page.form_with(:name => 'login') do |f|
34
+ f.username = @username
35
+ f.password = @password
36
+ end.submit
37
+ end
38
+ end
39
+
40
+ def main
41
+ diary_page = @agent.get "#{@uri}/?m=pc&a=page_h_diary_list_all"
42
+ diaries = Nokogiri::HTML(diary_page.body)/'div.item'
43
+
44
+ diaries[1...diaries.size].each do |diary|
45
+ uri = "#{@uri}/#{(diary/'td.photo/a').first.attributes['href']}"
46
+ redo if uri == nil or uri == ''
47
+
48
+ unless @diaries.has_key? uri
49
+ @diaries[uri] = true
50
+ nick = ((diary/'td').to_a)[1].text.gsub(/ \(.*\)$/, '')
51
+ title = ((diary/'td').to_a)[2].text.gsub(/ \([0-9].?\)/, '')
52
+ message = "#{nick}さんが「#{title}」を投稿しました! (#{uri})"
53
+
54
+ @channels.each do |channel|
55
+ notice(channel, message) if @diaries.size > 20
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
data/plugins/ramen.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+
4
+ class Ramen < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+
8
+ @suffix = @config['suffix'] || 'ラーメン食わせろ'
9
+ @base_uri = 'http://ramen.tedaco.net/'
10
+ @rank_uri = @base_uri + 'index.php'
11
+ end
12
+
13
+ def on_privmsg(prefix, channel, message)
14
+ case message
15
+ when /^#{@suffix}/
16
+ notice(channel, shop_choice)
17
+ end
18
+ end
19
+
20
+ def shop_choice
21
+ begin
22
+ list = Nokogiri::HTML(open(@rank_uri).read)/'.access_rank'
23
+
24
+ shop = list.to_a.choice
25
+ name = shop.at('a').text
26
+ detail = URI.short(@base_uri + shop.at('a').attributes['href'])
27
+
28
+ "#{name} に行けば? (#{detail})"
29
+ rescue Exception =>e
30
+ ranking << 'こわれたっ/(^o^)\'
31
+ end
32
+ end
33
+ end
data/plugins/rate.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+
4
+ class Rate < Mitten::Plugin
5
+ def initialize(*args)
6
+ super
7
+ @suffix = @config['suffix'] || '度判定して'
8
+ end
9
+
10
+ def on_privmsg(prefix, channel, message)
11
+ case message
12
+ when /^(.+)#{@suffix}$/
13
+ result = rating($1, prefix.nick)
14
+
15
+ if result == nil
16
+ notice(channel, '/(^o^)\ わかにゃいっ')
17
+ else
18
+ notice(channel, result.to_s)
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def rating(genre, nick)
26
+ uri = "http://kistools.appspot.com/r/#{URI.encode(genre)}/#{nick}"
27
+ doc = Nokogiri::XML(open(uri).read)
28
+
29
+ (doc/'table.input_form').first.at('td').text.match(/^(.+)?です。/).to_s.strip + " (#{URI.short(uri)})"
30
+ end
31
+ end