Updated to include my mega important warning message at the start of the config file...
[six.git] / plugins / topic.rb
blobcc4fd64d0079ee5b11679def9e656c030b4597a3
2 # A topic plugin to allow for multi-item topics.
3 # Complete rip-off of supybot.
6 class Topic < PluginBase
8   # Load/save database.
9   def load
10     begin
11       @topics = YAML.load_file(file_name('topics.db'))
12     rescue
13       @topics = {}
14     end
15   end
16   def save
17     open_file('topics.db', 'w') do |f|
18       f.puts '# CyBot topic plugin: Topic database.'
19       YAML.dump(@topics, f)
20       f.puts ''
21     end
22   end
24   def initialize
25     @brief_help = 'Manipulates channel topic.'
26     super
27     @topics = {}
28     tp = {
29       :help => 'Makes the topic plugin save topic on exit and restore it on join. If not set, the current channel topic is read on join.',
30       :type => Boolean
31     }
32     tf = {
33       :help => 'Makes the topic plugin automatically follow all topic changes.',
34       :type => Boolean
35     }
36     tcap = {
37       :help => "Allows user to use the 'topic' command to change the channel topic.",
38       :type => Boolean
39     }
40     $config.merge(
41       'servers' => {
42         :dir => true,
43         :skel => {
44           :dir => true,
45           'topic-preserve' => tp,
46           'topic-follow' => tf,
47           'defaults' => {
48             :dir => true,
49             'topic' => tcap
50           },
51           'topic' => tcap,
52           'channels' => {
53             :dir => true,
54             :skel => {
55               :dir => true,
56               'topic-preserve' => tp,
57               'topic-follow' => tf,
58               'defaults' => {
59                 :dir => true,
60                 'topic' => tcap
61               }
62             }
63           },
64           'users' => {
65             :dir => true,
66             :skel => {
67               :dir => true,
68               'topic' => tcap,
69               'channels' => {
70                 :dir => true,
71                 :skel => {
72                   :dir => true,
73                   'topic' => tcap
74                 }
75               }
76             }
77           }
78         }
79       },
80       'plugins' => {
81         :dir => true,
82         'topic' => {
83           :help => 'Settings for the topic plugin.',
84           :dir => true,
85           'separator' => "Topic item separator. Defaults to '|'",
86           'maxlength' => {
87             :help => 'The maximum length of the topic line.',
88             :type => Integer
89           },
90           'preserve' => tp,
91           'follow' => tf
92         }
93       }
94     )
95   end
97   # On-join hook. Read topic, if enabled, or set our own.
98   def hook_init_chan(irc)
99     c = irc.channel
100     t = @topics[cn = c.name] || (@topics[cn] = [nil, nil])
101     if setting_cs(irc, 'topic-preserve') { $config['plugins/topic/preserve'] }
102       t[1] = read_topic(c)
103       topic(c, t[0])
104     else
105       t[1] = t[0]
106       t[0] = read_topic(c)
107     end
108   end
110   # On topic change. Read if enabled.
111   def hook_topic_chan(irc, topic)
112     return if !irc.from or irc.from.nick == irc.server.nick
113     if setting_cs(irc, 'topic-follow') { $config['plugins/topic/follow'] }
114       t = @topics[cn = irc.channel.name] || (@topics[cn] = [nil, nil])
115       sep = $config['plugins/topic/separator', '|']
116       t[0] = t[1] = topic.split(" #{sep} ")
117     end
118   end
120   # Internal helper to avoid repetition.
121   def topic(chan, topic)
122     sep = $config['plugins/topic/separator', '|']
123     chan.server.cmd('TOPIC', chan.name, topic.join(" #{sep} "))
124   end
126   # Split into an integer list. Raises ArgumentError.
127   def int_list(data)
128     raise ArgumentError unless data
129     data.split.map do |e|
130       raise ArgumentError if e.nil?
131       Integer(e)
132     end
133   end
135   # Check if list is a permutation of the numbers 1...n. Raises RuntimeError.
136   def check_permute(list, n)
137     raise ArgumentError unless list.length == n
138     a = Array.new(n)
139     list.each do |e|
140       raise ArgumentError if e < 1 or e > n or a[e-1]
141       a[e-1] = true
142     end
143     raise ArgumentError if a.any? { |e| e.nil? }
144   end
146   # Perform topic reorder.
147   def reorder(chan, topics, list)
148     c = topics[0]
149     topics[1] = (u = c.dup)
150     list.length.times { |i| c[i] = u[list[i] - 1] }
151     topic(chan, c)
152   end
154   # Silly test command.
155   def chan_list(irc, chan, line)
156     irc.reply "Da topic list!!!"
157   end
159   # Read topic from channel.
160   def read_topic(chan)
161     sep = $config['plugins/topic/separator', '|']
162     (topic = chan.topic) ? topic.split(" #{sep} ") : []
163   end
165   # Common stuff.
166   def common(irc, chan, line)
167     if !$user.caps(irc, 'topic', 'op', 'owner').any?
168       irc.reply "I'm afraid you're not allowed to do that!"
169       return false
170     end
171     unless chan.me.op?
172       irc.reply 'For now, I must be operator to manipulate the topic.'
173       return false
174     end
175     t = @topics[chan.name] || (@topics[chan.name] = [[], []])
176     current, undo = t
177     line = nil if line and line.empty?
178     [line, t, current, undo]
179   end
181   def chan_set(irc, chan, line)
182     stuff = common(irc, chan, line) or return
183     data, t, current, undo = stuff
184     if data
185       t[1], t[0] = current, (c = [data])
186       topic(chan, c)
187     else irc.reply 'USAGE: topic set <topic string>' end
188   end
189   help :set, 'Sets the channel topic to the line provided.'
191   def chan_add(irc, chan, line)
192     stuff = common(irc, chan, line) or return
193     data, t, current, undo = stuff
194     if data
195       t[1] = current.dup
196       current << data
197       topic(chan, current)
198     else irc.reply 'USAGE: topic add <topic item>' end
199   end
200   help :add, 'Adds the given item to the list of topics in the channel.'
202   def chan_del(irc, chan, line)
203     stuff = common(irc, chan, line) or return
204     data, t, current, undo = stuff
205     begin
206       raise ArgumentError unless data
207       i = Integer(data)
208       if i <= (l = current.length) and i >= 1
209         undo = current.dup
210         current.delete_at(i - 1)
211         topic(chan, current)
212       else
213         irc.reply "Item number is out of range. There are #{l} items in the topic."
214       end
215     rescue ArgumentError
216       irc.reply 'USAGE: topic del <item number>'
217     end
218   end
219   help :del, 'Deletes the item number (1-base) from the channel topic list.'
221   def chan_reorder(irc, chan, line)
222     stuff = common(irc, chan, line) or return
223     data, t, current, undo = stuff
224     begin
225       l = int_list(data)
226       check_permute(l, current.length)
227       reorder(chan, t, l)
228     rescue ArgumentError
229       irc.reply 'USAGE: topic reorder <position 1> ... <position n>'
230     end
231   end
232   help :reorder, 'Reorders the channel topic list. You must provide a permutation of the numbers 1-n (both included), where n is the number of topic items. Example: topic reorder 2 4 1 3, if there are four items.'
234   def chan_swap(irc, chan, line)
235     stuff = common(irc, chan, line) or return
236     data, t, current, undo = stuff
237     begin
238       l = int_list(data)
239       raise ArgumentError unless l.length == 2
240       i, j = l
241       l = current.length
242       if i >= 1 and i <= l and j >= 1 and j <= l
243         if i == j: irc.reply "That seems rather silly, doesn't it?"
244         else
245           undo = current.dup
246           current[i-1], current[j-1] = current[j-1], current[i-1]
247           topic(chan, current)
248         end
249       else
250         irc.reply "Item numbers are out of range. There are #{l} items in the topic."
251       end
252     rescue ArgumentError
253       irc.reply 'USAGE: topic swap <item number 1> <item number 2>'
254     end
255   end
256   help :swap, 'Swaps the two topic items (numbered from 1) in the channel topic list.'
258   def chan_shuffle(irc, chan, line)
259     stuff = common(irc, chan, line) or return
260     data, t, current, undo = stuff
261     l = current.length
262     a = (1..l).to_a
263     b = []
264     l.times { |i| b << a.delete_at(rand(l - i)) }
265     reorder(chan, t, b)
266   end
267   help :shuffle, 'Randomly permutes the channel topic list.'
269   def chan_undo(irc, chan, line)
270     stuff = common(irc, chan, line) or return
271     data, t, current, undo = stuff
272     t[1], t[0] = current, undo
273     topic(chan, undo)
274   end
275   help :undo, 'Undoes the last channel topic change (if caused by one of the topic commands).'
277   def chan_restore(irc, chan, line)
278     stuff = common(irc, chan, line) or return
279     data, t, current, undo = stuff
280     list = read_topic(chan)
281     if list != current
282       t[1] = list
283       topic(chan, current)
284     end
285   end
286   help :restore, 'Restores the last channel topic set with one of the topic commands.'
288   def chan_read(irc, chan, line)
289     stuff = common(irc, chan, line) or return
290     data, t, current, undo = stuff
291     list = read_topic(chan)
292     t[0], t[1] = list, t[0]
293     irc.reply "Ok. Found #{list.length} topic items."
294   end
295   help :read, 'Reads and parses the currently set channel topic line. Use this if someone else changes the topic and you want the bot to grab it.'
297   def chan_replace(irc, chan, line)
298     stuff = common(irc, chan, line) or return
299     data, t, current, undo = stuff
300     begin
301       raise ArgumentError unless data
302       i, txt = data.split(' ', 2)
303       raise ArgumentError unless txt
304       i = Integer(i)
305       if i <= (l = current.length) and i >= 1
306         current[i - 1] = txt
307         topic(chan, current)
308       else
309         irc.reply "Item number is out of range. There are #{l} items in the topic."
310       end
311     rescue ArgumentError
312       irc.reply 'USAGE: topic replace <item number> <new topic item>'
313     end
314   end
315   help :replace, 'Replaces the given topic item (numbered from 1) in the channel topic list.'