]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/factoids.rb
factoids plugin: fact metadata editing
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / factoids.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Factoids pluing
5 #
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
7 # Copyright:: (C) 2007 Giuseppe Bilotta
8 # License:: GPLv2
9 #
10 # Store (and retrieve) unstructured one-sentence factoids
11
12 class FactoidsPlugin < Plugin
13
14   class Factoid
15     def initialize(hash)
16       @hash = hash.reject { |k, val| val.nil? or val.empty? rescue false }
17       raise ArgumentError, "no fact!" unless @hash[:fact]
18       if String === @hash[:when]
19         @hash[:when] = Time.parse @hash[:when]
20       end
21     end
22
23     def to_s
24       @hash[:fact]
25     end
26
27     def [](*args)
28       @hash[*args]
29     end
30
31     def []=(*args)
32       @hash.send(:[]=,*args)
33     end
34
35     def to_hsh
36       return @hash
37     end
38     alias :to_hash :to_hsh
39   end
40
41   class FactoidList < ArrayOf
42     def initialize(ar=[])
43       super(Factoid, ar)
44     end
45
46     def index(f)
47       fact = f.to_s
48       return if fact.empty?
49       self.map { |f| f[:fact] }.index(fact)
50     end
51
52     def delete(f)
53       idx = index(f)
54       return unless idx
55       self.delete_at(idx)
56     end
57
58     def grep(x)
59       self.find_all { |f|
60         x === f[:fact]
61       }
62     end
63   end
64
65   def initialize
66     super
67
68     # TODO config
69     @dir = File.join(@bot.botclass,"factoids")
70     @filename = "factoids.rbot"
71     @factoids = FactoidList.new
72     read_factfile
73     @changed = false
74   end
75
76   def read_factfile(name=@filename,dir=@dir)
77     fname = File.join(dir,name)
78     if File.exist?(fname)
79       factoids = File.readlines(fname)
80       return if factoids.empty?
81       firstline = factoids.shift
82       pattern = firstline.chomp.split(" | ")
83       if pattern.length == 1 and pattern.first != "fact"
84         factoids.unshift(firstline)
85         factoids.each { |f|
86           @factoids << Factoid.new( :fact => f.chomp )
87         }
88       else
89         pattern.map! { |p| p.intern }
90         raise ArgumentError, "fact must be the last field" unless pattern.last == :fact
91         factoids.each { |f|
92           ar = f.chomp.split(" | ", pattern.length)
93           @factoids << Factoid.new(Hash[*([pattern, ar].transpose.flatten)])
94         }
95       end
96     end
97   end
98
99   def save
100     return unless @changed
101     Dir.mkdir(@dir) unless FileTest.directory?(@dir)
102     fname = File.join(@dir,@filename)
103     ar = ["when | who | where | fact"]
104     @factoids.each { |f|
105       ar << "%s | %s | %s | %s" % [ f[:when], f[:who], f[:where], f[:fact]]
106     }
107     Utils.safe_save(fname) do |file|
108       file.puts ar
109     end
110     @changed = false
111   end
112
113   def help(plugin, topic="")
114     _("factoids plugin: learn that <factoid>, forget that <factoids>, facts about <words>")
115   end
116
117   def learn(m, params)
118     factoid = Factoid.new(
119       :fact => params[:stuff].to_s,
120       :when => Time.now,
121       :who => m.source.fullform,
122       :where => m.channel.to_s
123     )
124     if @factoids.index(factoid)
125       m.reply _("I already know that %{factoid}" % { :factoid => factoid })
126     else
127       @factoids << factoid
128       @changed = true
129       m.okay
130     end
131   end
132
133   def forget(m, params)
134     factoid = params[:stuff].to_s
135     if @factoids.delete(factoid)
136       @changed = true
137       m.okay
138     else
139       m.reply _("I didn't know that %{factoid}" % { :factoid => factoid })
140     end
141   end
142
143   def facts(m, params)
144     if params[:words].empty?
145       m.reply _("I know %{count} facts" % { :count => @factoids.length })
146     else
147       rx = Regexp.new(params[:words].to_s, true)
148       known = @factoids.grep(rx)
149       if known.empty?
150         m.reply _("I know nothing about %{words}" % params)
151       else
152         m.reply known.join(" | "), :split_at => /\s+\|\s+/
153       end
154     end
155   end
156
157   def fact(m, params)
158     fact = nil
159     idx = 0
160     total = @factoids.length
161     if params[:index]
162       idx = params[:index].scan(/\d+/).first.to_i
163       if idx <= 0 or idx > total
164         m.reply _("please select a fact number between 1 and %{total}" % { :total => total })
165         return
166       end
167       fact = @factoids[idx-1]
168     else
169       known = nil
170       if params[:words].empty?
171         if @factoids.empty?
172           m.reply _("I know nothing")
173           return
174         end
175         known = @factoids
176       else
177         rx = Regexp.new(params[:words].to_s, true)
178         known = @factoids.grep(rx)
179         if known.empty?
180           m.reply _("I know nothing about %{words}" % params)
181           return
182         end
183       end
184       fact = known.pick_one
185       idx = @factoids.index(fact)+1
186     end
187     meta = nil
188     metadata = []
189     if fact[:who]
190       metadata << _("from %{who}" % fact.to_hash)
191     end
192     if fact[:when]
193       metadata << _("on %{when}" % fact.to_hash)
194     end
195     if fact[:where]
196       metadata << _("in %{where}" % fact.to_hash)
197     end
198     unless metadata.empty?
199       meta = _(" [learnt %{data}]" % {:data => metadata.join(" ")})
200     end
201     m.reply _("fact #%{idx} of %{total}: %{fact}%{meta}" % {
202       :idx => idx,
203       :total => total,
204       :fact => fact,
205       :meta => meta
206     })
207   end
208
209   def edit_fact(m, params)
210     fact = nil
211     idx = 0
212     total = @factoids.length
213     idx = params[:index].scan(/\d+/).first.to_i
214     if idx <= 0 or idx > total
215       m.reply _("please select a fact number between 1 and %{total}" % { :total => total })
216       return
217     end
218     fact = @factoids[idx-1]
219     begin
220       if params[:who]
221         who = params[:who].to_s.sub(/^me$/, m.source.fullform)
222         debug who
223         fact[:who] = who
224       end
225       if params[:when]
226         fact[:when] = Time.parse(params[:when].to_s)
227       end
228       if params[:where]
229         fact[:where] = params[:where].to_s
230       end
231     rescue Exception
232       m.reply _("couldn't change learn data for fact %{fact}: %{err}" % {
233         :fact => fact,
234         :err => $!
235       })
236       return
237     end
238     m.okay
239   end
240
241 end
242
243 plugin = FactoidsPlugin.new
244
245 plugin.default_auth('edit', false)
246
247 plugin.map 'learn that *stuff'
248 plugin.map 'forget that *stuff', :auth_path => 'edit'
249 plugin.map 'facts [about *words]'
250 plugin.map 'fact [about *words]'
251 plugin.map 'fact :index', :requirements => { :index => /^#?\d+$/ }
252 plugin.map 'fact :index :learn from *who', :action => :edit_fact, :requirements => { :learn => /^((?:is|was)\s+)?learn(ed|t)$/, :index => /^#?\d+$/ }, :auth_path => 'edit'
253 plugin.map 'fact :index :learn on *when',  :action => :edit_fact, :requirements => { :learn => /^((?:is|was)\s+)?learn(ed|t)$/, :index => /^#?\d+$/ }, :auth_path => 'edit'
254 plugin.map 'fact :index :learn in *where', :action => :edit_fact, :requirements => { :learn => /^((?:is|was)\s+)?learn(ed|t)$/, :index => /^#?\d+$/ }, :auth_path => 'edit'