Class: CamperVan::Channel

Inherits:
Object
  • Object
show all
Includes:
Logger, Utils
Defined in:
lib/camper_van/channel.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logger

#logger

Methods included from Utils

#irc_name, #stringify_keys

Constructor Details

#initialize(channel, client, room) ⇒ Channel

Public: create a new campfire channel

channel - name of the channel we’re joining client - the EM::Connection representing the irc client room - the campfire room we’re joining



34
35
36
37
# File 'lib/camper_van/channel.rb', line 34

def initialize(channel, client, room)
  @channel, @client, @room = channel, client, room
  @users = {}
end

Instance Attribute Details

#channelObject (readonly)

The irc channel name this channel instance is for



9
10
11
# File 'lib/camper_van/channel.rb', line 9

def channel
  @channel
end

#clientObject (readonly)

The connected irc client connection



12
13
14
# File 'lib/camper_van/channel.rb', line 12

def client
  @client
end

#roomObject (readonly)

The campfire room to which this channel is connected



15
16
17
# File 'lib/camper_van/channel.rb', line 15

def room
  @room
end

#streamObject (readonly)

Accessor for the EM http request representing the live stream from the campfire api



19
20
21
# File 'lib/camper_van/channel.rb', line 19

def stream
  @stream
end

#usersObject (readonly)

Accessor for hash of known users in the room/channel Kept up to date by update_users command, as well as Join/Leave campfire events.



24
25
26
# File 'lib/camper_van/channel.rb', line 24

def users
  @users
end

Instance Method Details

#current_modeObject

Public: sends the current channel mode to the client



138
139
140
141
# File 'lib/camper_van/channel.rb', line 138

def current_mode
  n = room.membership_limit
  client.numeric_reply :rpl_channelmodeis, channel, current_mode_string, n
end

#current_mode_stringObject

Returns the current mode string



177
178
179
180
181
182
# File 'lib/camper_van/channel.rb', line 177

def current_mode_string
  n = room.membership_limit
  s = room.open_to_guests? ? "" : "s"
  i = room.locked? ? "i" : ""
  "+#{i}l#{s}"
end

#current_topicObject

Public: returns the current topic of the campfire room



185
186
187
# File 'lib/camper_van/channel.rb', line 185

def current_topic
  client.numeric_reply :rpl_topic, channel, ':' + room.topic
end

#joinObject

Public: Joins a campfire room and sends the necessary topic and name list messages back to the IRC client.

Returns true if the join was successful,

false if the room was full or locked.


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/camper_van/channel.rb', line 44

def join
  if room.locked?
    client.numeric_reply :err_inviteonlychan, "Cannot join #{channel} (locked)"
    return false

  elsif room.full?
    client.numeric_reply :err_channelisfull, "Cannot join #{channel} (full)"
    return false

  else
    update_users do
      # join channel
      client.user_reply :join, ":#{channel}"

      # current topic
      client.numeric_reply :rpl_topic, channel, ':' + (room.topic || "")

      # List the current users, which must always include myself
      # (race condition, server may not realize the user has joined yet)
      nicks = users.values.map { |u| u.nick }
      nicks.unshift client.nick unless nicks.include? client.nick

      nicks.each_slice(10) do |list|
        client.numeric_reply :rpl_namereply, "=", channel, ":#{list.join ' '}"
      end
      client.numeric_reply :rpl_endofnames, channel, "End of /NAMES list."

      # begin streaming the channel events (joins room implicitly)
      stream_campfire_to_channel
    end
  end

  true
end

#list_usersObject

Public: replies to a WHO command with a list of users for a campfire room, including their nicks, names, and status.

For WHO response: www.mircscripts.org/forums.php?cid=3&id=159227 In short, H = here, G = away, append @ for chanops (admins)



101
102
103
104
105
106
107
108
109
110
# File 'lib/camper_van/channel.rb', line 101

def list_users
  update_users(:include_joins_and_parts) do
    users.values.each do |user|
      status = (user.idle? ? "G" : "H") + (user.admin? ? "@" : "")
      client.numeric_reply :rpl_whoreply, channel, user., user.server,
        "camper_van", user.nick, status, ":0 #{user.name}"
    end
    client.numeric_reply :rpl_endofwho, channel, "End of WHO list"
  end
end

#map_message_to_irc(message) ⇒ Object

Map a campfire message to one or more IRC commands for the client

message - the campfire message to map to IRC

Returns nothing, but responds according to the message



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/camper_van/channel.rb', line 261

def map_message_to_irc(message)
  user_for_message(message) do |message, user|

    # needed in most cases
    name = user ? irc_name(user.name) : nil

    # strip Message off the type to simplify readability
    type = message.type.sub(/Message$/, '')

    if %w(Text Tweet Sound Paste Upload).include?(type) && name == client.nick
        logger.debug "skipping message from myself: #{message.type} #{message.body}"
      next
    end

    case type
    when "Timestamp", "Advertisement"
      # ignore these

    when "Lock"
      client.campfire_reply :mode, name, channel, "+i"

    when "Unlock"
      client.campfire_reply :mode, name, channel, "-i"

    when "DisallowGuests"
      name = irc_name(user.name)
      client.campfire_reply :mode, name, channel, "+s"

    when "AllowGuests"
      name = irc_name(user.name)
      client.campfire_reply :mode, name, channel, "-s"

    when "Idle"
      if u = users[user.id]
        u.idle = true
      end

    when "Unidle"
      if u = users[user.id]
        u.idle = false
      end

    when "Enter"
      unless users[user.id]
        client.campfire_reply :join, name, channel
        users[user.id] = User.new(user)
      end

    when "Leave", "Kick" # kick is used for idle timeouts
      client.campfire_reply :part, name, channel, "Leaving..."
      users.delete user.id

    when "Paste"
      lines = message.body.split(/\n|\r\n|\r/)

      lines[0..2].each do |line|
        client.campfire_reply :privmsg, name, channel, ":> " + line
      end

      if lines.size > 3
        client.campfire_reply :privmsg, name, channel, ":> more: " +
          "https://#{client.subdomain}.campfirenow.com/room/#{room.id}/paste/#{message.id}"
      end

    when "Sound"
      text = case message.body
      when "crickets"
        "hears crickets chirping"
      when "rimshot"
        "plays a rimshot"
      when "trombone"
        "plays a sad trombone"
      when "vuvuzela"
        "======<() ~ ♪ ~♫"
      else
        "played a #{message.body} sound"
      end

      client.campfire_reply :privmsg, name, channel, "\x01ACTION #{text}\x01"

    # when "System"
    #   # NOTICE from :camper_van to channel?

    when "Text"
      if message.body =~ /^\*.*\*$/
        client.campfire_reply :privmsg, name, channel, ":\01ACTION " + message.body[1..-2] + "\01"
      else
        matched = users.values.detect do |user|
          message.body =~ /^#{Regexp.escape(user.name)}(\W+(\s|$)|$)/
        end

        if matched
          body = message.body.sub(/^#{matched.name}/, matched.nick)
        else
          body = message.body
        end
        body.split(/[\r\n]+/).each do |line|
          client.campfire_reply :privmsg, name, channel, ":" + line
        end
      end

    when "TopicChange"
      client.campfire_reply :topic, name, channel, message.body
      room.topic = message.body
      # client.numeric_reply :rpl_topic, channel, ':' + message.body

    when "Upload"
      room.connection.http(:get, "/room/#{message.room_id}/messages/#{message.id}/upload.json") do |data|
        client.campfire_reply :privmsg, name, channel, ":\01ACTION uploaded " + data[:upload][:full_url]
      end

    when "Tweet"
      message.body.split(/\n|\r\n|\r/).each do |line|
        client.campfire_reply :privmsg, name, channel, line
      end

    else
      logger.warn "unknown message #{message.type}: #{message.body}"
    end
  end
end

#partObject

Public: “leaves” a campfire room, per the PART irc command. Confirms with the connected client to PART the channel.

Does not actually leave the campfire room, just closes out the campfire connections, so the server can idle the connection out. This behavior was chosen so periodic joins/parts wouldn’t spam the campfire rooms unnecessarily, and also to reflect how Propane et. al. treat open connections: allowing them to time out rather than leaving explicitly.



87
88
89
90
91
92
93
94
# File 'lib/camper_van/channel.rb', line 87

def part
  client.user_reply :part, channel
  if stream
    stream.close if stream.respond_to?('close') # EM/em-http-request gem is installed and uses .close
    stream.close_connection if stream.respond_to?('close_connection')
  end
  # room.leave # let the timeout do it rather than being explicit!
end

#privmsg(msg) ⇒ Object

Public: accepts an IRC PRIVMSG and converts it to an appropriate campfire text message for the room.

msg - the IRC PRIVMSG message contents



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/camper_van/channel.rb', line 117

def privmsg(msg)

  # convert twitter urls to tweets
  if msg =~ %r(^https://twitter.com/(\w+)/status/(\d+)$)
    room.tweet(msg) { } # async, no-op callback
  else
    # convert ACTIONs
    msg.sub! /^\01ACTION (.*)\01$/, '*\1*'

    matched = users.values.detect do |user|
      msg =~ /^#{Regexp.escape(user.nick)}($|\W+(\s|$))/
    end

    msg = msg.sub(/^#{matched.nick}/, matched.name) if matched

    room.text(msg) { } # async, no-op callback
  end

end

#set_mode(mode) ⇒ Object

Public: set the mode on the campfire channel, mapping from the provided IRC chanmode to the campfire setting.

mode - the IRC mode flag change. Must be one of:

"+i" - lock room
"-i" - unlock room

TODO support these when the firering client does:

"+s" - disable guest access
"-s" - enable guest access

Returns nothing, but lets the client know the results of the call. Sends

an error to the client for an invalid mode string.


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/camper_van/channel.rb', line 156

def set_mode(mode)
  case mode
  # when "+s"
  # when "-s"
  when "+i"
    room.lock
    room.locked = true
    client.user_reply :mode, channel,
      current_mode_string, room.membership_limit
  when "-i"
    room.unlock
    room.locked = false
    client.user_reply :mode, channel,
      current_mode_string, room.membership_limit
  else
    client.numeric_reply :err_unknownmode,
      "is unknown mode char to me for #{channel}"
  end
end

#set_topic(topic) ⇒ Object

Public: set the topic of the campfire room to the given string and lets the irc client know about the change

topic - the new topic



193
194
195
196
197
198
# File 'lib/camper_van/channel.rb', line 193

def set_topic(topic)
  room.update("topic" => topic) do
    room.topic = topic
    client.numeric_reply :rpl_topic, channel, ':' + room.topic
  end
end

#stream_campfire_to_channelObject

Stream messages from campfire and map them to IRC commands for the connected client.

Only starts the stream once.



250
251
252
253
254
# File 'lib/camper_van/channel.rb', line 250

def stream_campfire_to_channel
  @stream ||= room.stream do |message|
    map_message_to_irc message
  end
end

#update_users(include_joins_and_parts = false, &callback) ⇒ Object

Get the list of users from a room, and update the internal tracking state as well as the connected client. If the user list is out of sync, the irc client may receive the associated JOIN/PART commands.

include_joins_and_parts - whether or not to include JOIN/PART commands if

the user list has changed since the last update
(defaults to false)

callback - optional callback after the users have been

updated

Returns nothing, but keeps the users list updated



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/camper_van/channel.rb', line 212

def update_users(include_joins_and_parts=false, &callback)
  room.users do |user_list|
    before = users.dup
    present = {}

    user_list.each do |user|
      if before[user.id]
        present[user.id] = before.delete user.id
        # if present[user.id].nick != nick
        #   # NICK CHANGE
        #   present[user.id].nick = nick
        # end
      else
        new_user = present[user.id] = User.new(user)
        if include_joins_and_parts
          client.campfire_reply :join, new_user.nick, channel
        end
      end
    end

    # Now that the list of users is updated, the remaining users
    # in 'before' have left. Let the irc client know.
    before.each do |id, user|
      if include_joins_and_parts
        client.campfire_reply :part, user.nick, channel
      end
    end

    @users = present

    callback.call if callback
  end
end

#user_for_message(message) ⇒ Object

Retrieve the user from a message, either by finding it in the current list of known users, or by asking campfire for the user.

message - the message for which to look up the user

Yields the message and the user associated with the message



389
390
391
392
393
394
395
396
397
# File 'lib/camper_van/channel.rb', line 389

def user_for_message(message)
  if user = users[message.user_id]
    yield message, user
  else
    message.user do |user|
      yield message, user
    end
  end
end