pipio 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.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/.simplecov +5 -0
- data/.travis.yml +12 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/NEWS.md +10 -0
- data/README.md +88 -0
- data/Rakefile +13 -0
- data/lib/pipio.rb +34 -0
- data/lib/pipio/alias_registry.rb +26 -0
- data/lib/pipio/chat.rb +39 -0
- data/lib/pipio/cleaners/html_cleaner.rb +95 -0
- data/lib/pipio/cleaners/text_cleaner.rb +15 -0
- data/lib/pipio/file_reader.rb +29 -0
- data/lib/pipio/message_creators/auto_or_xml_message_creator.rb +25 -0
- data/lib/pipio/message_creators/event_message_creator.rb +47 -0
- data/lib/pipio/message_creators/status_message_creator.rb +19 -0
- data/lib/pipio/messages/auto_reply_message.rb +7 -0
- data/lib/pipio/messages/event.rb +67 -0
- data/lib/pipio/messages/message.rb +23 -0
- data/lib/pipio/messages/status_message.rb +26 -0
- data/lib/pipio/messages/xml_message.rb +43 -0
- data/lib/pipio/metadata.rb +34 -0
- data/lib/pipio/metadata_parser.rb +55 -0
- data/lib/pipio/parser_factory.rb +32 -0
- data/lib/pipio/parsers/basic_parser.rb +83 -0
- data/lib/pipio/parsers/html_log_parser.rb +22 -0
- data/lib/pipio/parsers/null_parser.rb +9 -0
- data/lib/pipio/parsers/text_log_parser.rb +21 -0
- data/lib/pipio/tag_balancer.rb +163 -0
- data/lib/pipio/time_parser.rb +36 -0
- data/lib/pipio/version.rb +3 -0
- data/pipio.gemspec +27 -0
- data/spec/pipio/alias_registry_spec.rb +37 -0
- data/spec/pipio/chat_spec.rb +66 -0
- data/spec/pipio/cleaners/html_cleaner_spec.rb +102 -0
- data/spec/pipio/cleaners/text_cleaner_spec.rb +29 -0
- data/spec/pipio/file_reader_spec.rb +130 -0
- data/spec/pipio/messages/auto_reply_message_spec.rb +40 -0
- data/spec/pipio/messages/event_spec.rb +41 -0
- data/spec/pipio/messages/status_message_spec.rb +43 -0
- data/spec/pipio/messages/xml_message_spec.rb +55 -0
- data/spec/pipio/metadata_parser_spec.rb +81 -0
- data/spec/pipio/metadata_spec.rb +72 -0
- data/spec/pipio/parser_factory_spec.rb +31 -0
- data/spec/pipio/parsers/html_log_parser_spec.rb +160 -0
- data/spec/pipio/parsers/null_parser_spec.rb +13 -0
- data/spec/pipio/parsers/text_log_parser_spec.rb +37 -0
- data/spec/pipio/tag_balancer_spec.rb +16 -0
- data/spec/pipio/time_parser_spec.rb +66 -0
- data/spec/pipio_spec.rb +63 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/chat_builder.rb +29 -0
- data/spec/support/chat_builder_helpers.rb +41 -0
- data/spec/support/file_builder.rb +22 -0
- data/spec/support/html_chat_builder.rb +67 -0
- data/spec/support/logfiles/2006-12-21.223606.txt +3 -0
- data/spec/support/logfiles/2008-01-15.071445-0500PST.htm +5 -0
- data/spec/support/logfiles/2008-01-15.071445-0500PST.html +5 -0
- data/spec/support/text_chat_builder.rb +21 -0
- data/spec/test-output/README.md +1 -0
- data/spec/test-output/html_log_output.xml +6 -0
- data/spec/test-output/text_log_output.xml +4 -0
- metadata +193 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pipio
|
2
|
+
class AutoOrXmlMessageCreator
|
3
|
+
def initialize(text, time, sender_screen_name, sender_alias, is_auto_reply)
|
4
|
+
@text = text
|
5
|
+
@time = time
|
6
|
+
@sender_screen_name = sender_screen_name
|
7
|
+
@sender_alias = sender_alias
|
8
|
+
@is_auto_reply = is_auto_reply
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
if auto_reply?
|
13
|
+
AutoReplyMessage.new(@sender_screen_name, @time, @sender_alias, @text)
|
14
|
+
else
|
15
|
+
XMLMessage.new(@sender_screen_name, @time, @sender_alias, @text)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def auto_reply?
|
22
|
+
!! @is_auto_reply
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Pipio
|
2
|
+
class EventMessageCreator
|
3
|
+
def initialize(text, time, sender_alias, sender_screen_name, alias_registry)
|
4
|
+
@text = text
|
5
|
+
@time = time
|
6
|
+
@sender_alias = sender_alias
|
7
|
+
@sender_screen_name = sender_screen_name
|
8
|
+
@alias_registry = alias_registry
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
create_lib_purple_event_message ||
|
13
|
+
create_non_lib_purple_event_message
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def create_lib_purple_event_message
|
19
|
+
regex = Event::LIB_PURPLE.detect { |rxp| @text =~ rxp }
|
20
|
+
if regex
|
21
|
+
event_type = 'libpurpleEvent'
|
22
|
+
create_event_message_from(regex, event_type)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_non_lib_purple_event_message
|
27
|
+
regex, event_type = Event::MAP.detect { |rxp,ev_type| @text =~ rxp }
|
28
|
+
if regex && event_type
|
29
|
+
create_event_message_from(regex, event_type)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_event_message_from(regex, event_type)
|
34
|
+
regex_matches = regex.match(@text)
|
35
|
+
if regex_matches.size == 1
|
36
|
+
# No alias - this means it's the user
|
37
|
+
sender_alias = @sender_alias
|
38
|
+
sender_screen_name = @sender_screen_name
|
39
|
+
else
|
40
|
+
sender_alias = regex_matches[1]
|
41
|
+
sender_screen_name = @alias_registry[sender_alias]
|
42
|
+
end
|
43
|
+
|
44
|
+
Event.new(sender_screen_name, @time, sender_alias, @text, event_type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Pipio
|
2
|
+
class StatusMessageCreator
|
3
|
+
def initialize(text, time, alias_registry)
|
4
|
+
@text = text
|
5
|
+
@time = time
|
6
|
+
@alias_registry = alias_registry
|
7
|
+
end
|
8
|
+
|
9
|
+
def create
|
10
|
+
regex, status = StatusMessage::MAP.detect { |rxp, stat| @text =~ rxp }
|
11
|
+
|
12
|
+
if regex && status
|
13
|
+
sender_alias = regex.match(@text)[1]
|
14
|
+
sender_screen_name = @alias_registry[sender_alias]
|
15
|
+
StatusMessage.new(sender_screen_name, @time, sender_alias, status)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Pipio
|
2
|
+
# Pidgin does not have Events, but Adium does. Pidgin mostly uses system
|
3
|
+
# messages to display what Adium calls events. These include sending a file,
|
4
|
+
# starting a Direct IM connection, or an error in chat.
|
5
|
+
class Event < XMLMessage
|
6
|
+
# All of event_type libPurple.
|
7
|
+
LIB_PURPLE = [
|
8
|
+
# file transfer
|
9
|
+
/Starting transfer of .+ from (.+)/,
|
10
|
+
/^Offering to send .+ to (.+)$/,
|
11
|
+
/(.+) is offering to send file/,
|
12
|
+
/^Transfer of file .+ complete$/,
|
13
|
+
/Error reading|writing|accessing .+: .+/,
|
14
|
+
/You cancell?ed the transfer of/,
|
15
|
+
/File transfer cancelled/,
|
16
|
+
/(.+?) cancell?ed the transfer of/,
|
17
|
+
/(.+?) cancelled the file transfer/,
|
18
|
+
# Direct IM - actual (dis)connect events are their own types
|
19
|
+
/^Attempting to connect to (.+) at .+ for Direct IM\./,
|
20
|
+
/^Asking (.+) to connect to us at .+ for Direct IM\./,
|
21
|
+
/^Attempting to connect via proxy server\.$/,
|
22
|
+
/^Direct IM with (.+) failed/,
|
23
|
+
# encryption
|
24
|
+
/Received message encrypted with wrong key/,
|
25
|
+
/^Requesting key\.\.\.$/,
|
26
|
+
/^Outgoing message lost\.$/,
|
27
|
+
/^Conflicting Key Received!$/,
|
28
|
+
/^Error in decryption- asking for resend\.\.\.$/,
|
29
|
+
/^Making new key pair\.\.\.$/,
|
30
|
+
# sending errors
|
31
|
+
/^Last outgoing message not received properly- resetting$/,
|
32
|
+
/Resending\.\.\./,
|
33
|
+
# connection errors
|
34
|
+
/Lost connection with the remote user:.+/,
|
35
|
+
# chats
|
36
|
+
/^.+ entered the room\.$/,
|
37
|
+
/^.+ left the room\.$/
|
38
|
+
]
|
39
|
+
|
40
|
+
# Adium ignores SN/alias changes.
|
41
|
+
IGNORE = [/^.+? is now known as .+?\.<br\/?>$/]
|
42
|
+
|
43
|
+
# Each key maps to an event_type string. The keys will be matched against
|
44
|
+
# a line of chat and the partner's alias will be in regex group 1, IF the
|
45
|
+
# alias is matched.
|
46
|
+
MAP = {
|
47
|
+
# .+ is not an alias, it's a proxy server so no grouping
|
48
|
+
/^Attempting to connect to .+\.$/ => 'direct-im-connect',
|
49
|
+
# NB: pidgin doesn't track when Direct IM is disconnected, AFAIK
|
50
|
+
/^Direct IM established$/ => 'directIMConnected',
|
51
|
+
/Unable to send message/ => 'chat-error',
|
52
|
+
/You missed .+ messages from (.+) because they were too large/ => 'chat-error',
|
53
|
+
/User information not available/ => 'chat-error'
|
54
|
+
}
|
55
|
+
|
56
|
+
def initialize(sender_screen_name, time, sender_alias, body, event_type)
|
57
|
+
super(sender_screen_name, time, sender_alias, body)
|
58
|
+
@event_type = event_type
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :event_type
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
%(<event type="#{@event_type}" sender="#{@sender_screen_name}" time="#{adium_formatted_time}" alias="#{@sender_alias}">#{@styled_body}</event>)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pipio
|
2
|
+
# A holding object for each line of the chat. It is subclassed as
|
3
|
+
# appropriate (eg AutoReplyMessage). Each subclass (but not Message
|
4
|
+
# itself) has its own to_s which prints out its information in a format
|
5
|
+
# appropriate for putting in an Adium log file.
|
6
|
+
class Message
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
def initialize(sender_screen_name, time, sender_alias)
|
10
|
+
@sender_screen_name = sender_screen_name
|
11
|
+
@time = time
|
12
|
+
@sender_alias = sender_alias
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :sender_screen_name, :time, :sender_alias
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def adium_formatted_time
|
20
|
+
@time.xmlschema
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Pipio
|
2
|
+
# A message saying e.g. "Blahblah has gone away."
|
3
|
+
class StatusMessage < Message
|
4
|
+
MAP = {
|
5
|
+
/(.+) logged in\.$/ => 'online',
|
6
|
+
/(.+) logged out\.$/ => 'offline',
|
7
|
+
/(.+) has signed on\.$/ => 'online',
|
8
|
+
/(.+) has signed off\.$/ => 'offline',
|
9
|
+
/(.+) has gone away\.$/ => 'away',
|
10
|
+
/(.+) is no longer away\.$/ => 'available',
|
11
|
+
/(.+) has become idle\.$/ => 'idle',
|
12
|
+
/(.+) is no longer idle\.$/ => 'available'
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(sender_screen_name, time, sender_alias, status)
|
16
|
+
super(sender_screen_name, time, sender_alias)
|
17
|
+
@status = status
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :status
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
%(<status type="#{@status}" sender="#{@sender_screen_name}" time="#{adium_formatted_time}" alias="#{@sender_alias}"/>\n)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Pipio
|
2
|
+
# Basic message with body text (as opposed to pure status messages, which
|
3
|
+
# have no body).
|
4
|
+
class XMLMessage < Message
|
5
|
+
def initialize(sender_screen_name, time, sender_alias, body)
|
6
|
+
super(sender_screen_name, time, sender_alias)
|
7
|
+
@body = normalize(body)
|
8
|
+
@styled_body = %(<div><span style="font-family: Helvetica; font-size: 12pt;">#{@body}</span></div>)
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :body
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
%(<message sender="#{@sender_screen_name}" time="#{adium_formatted_time}" alias="#{@sender_alias}">#{@styled_body}</message>\n)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Balances mismatched tags, normalizes body style, and fixes actions
|
20
|
+
# so they are in Adium style (Pidgin uses "***Buddy waves at you", Adium uses
|
21
|
+
# "*Buddy waves at you*").
|
22
|
+
def normalize(string)
|
23
|
+
new_body = normalize_entities(string)
|
24
|
+
# Fix mismatched tags. Yes, it's faster to do it per-message
|
25
|
+
# than all at once.
|
26
|
+
new_body = Pipio::TagBalancer.new(new_body).balance
|
27
|
+
if @sender_alias[0,3] == '***'
|
28
|
+
# "***<alias>" is what pidgin sets as the alias for a /me action
|
29
|
+
@sender_alias.slice!(0,3)
|
30
|
+
new_body = "*#{new_body}*"
|
31
|
+
end
|
32
|
+
|
33
|
+
new_body
|
34
|
+
end
|
35
|
+
|
36
|
+
# Escapes all entities in string except for "<", ">", "&", """,
|
37
|
+
# and "'".
|
38
|
+
def normalize_entities(string)
|
39
|
+
# Convert '&' to '&' only if it's not followed by an entity.
|
40
|
+
string.gsub(/&(?!lt|gt|amp|quot|apos)/, '&')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Pipio
|
2
|
+
class Metadata
|
3
|
+
def initialize(metadata_hash)
|
4
|
+
@service = metadata_hash[:service]
|
5
|
+
@my_screen_name = normalize_screen_name(metadata_hash[:my_screen_name])
|
6
|
+
@their_screen_name = metadata_hash[:their_screen_name]
|
7
|
+
@start_time = metadata_hash[:start_time]
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :my_screen_name, :their_screen_name, :start_time, :service
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
[@their_screen_name, @my_screen_name, @start_time, @service].all?
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_year
|
17
|
+
@start_time.year
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_month
|
21
|
+
@start_time.mon
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_mday
|
25
|
+
@start_time.mday
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def normalize_screen_name(screen_name)
|
31
|
+
screen_name && screen_name.downcase.gsub(' ', '')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Pipio
|
2
|
+
class MetadataParser
|
3
|
+
def initialize(first_line)
|
4
|
+
@first_line = first_line || ''
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse
|
8
|
+
{
|
9
|
+
my_screen_name: my_screen_name,
|
10
|
+
their_screen_name: their_screen_name,
|
11
|
+
start_time: start_time,
|
12
|
+
service: service
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def service
|
19
|
+
match = @first_line.match(/\(([a-z]+)\)/)
|
20
|
+
if match
|
21
|
+
match[1]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def their_screen_name
|
26
|
+
match = @first_line.match(/Conversation with (.+?) at/)
|
27
|
+
if match
|
28
|
+
match[1]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def my_screen_name
|
33
|
+
match = @first_line.match(/ on ([^()]+) /)
|
34
|
+
if match
|
35
|
+
match[1]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_time
|
40
|
+
match = @first_line.match(%r{ at ([-\d/APM: ]+) on})
|
41
|
+
if match
|
42
|
+
timestamp = match[1]
|
43
|
+
parse_time(timestamp)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_time(timestamp)
|
48
|
+
begin
|
49
|
+
Time.parse(timestamp)
|
50
|
+
rescue ArgumentError
|
51
|
+
TimeParser.new(nil, nil, nil).parse(timestamp)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pipio
|
2
|
+
class ParserFactory
|
3
|
+
PARSER_FOR_EXTENSION = {
|
4
|
+
"html" => HtmlLogParser,
|
5
|
+
"htm" => HtmlLogParser,
|
6
|
+
"txt" => TextLogParser
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize(logfile_path, aliases)
|
10
|
+
@logfile_path = logfile_path
|
11
|
+
@aliases = aliases
|
12
|
+
end
|
13
|
+
|
14
|
+
def parser
|
15
|
+
parser_class.new(@logfile_path, @aliases)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def parser_class
|
21
|
+
PARSER_FOR_EXTENSION.fetch(extension, NullParser)
|
22
|
+
end
|
23
|
+
|
24
|
+
def extension
|
25
|
+
extension_with_leading_period[1..-1]
|
26
|
+
end
|
27
|
+
|
28
|
+
def extension_with_leading_period
|
29
|
+
File.extname(@logfile_path).downcase
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Pipio
|
2
|
+
class BasicParser
|
3
|
+
def initialize(source_file_path, my_aliases, line_regex, line_regex_status, cleaner)
|
4
|
+
@my_aliases = my_aliases.split(',')
|
5
|
+
@line_regex = line_regex
|
6
|
+
@line_regex_status = line_regex_status
|
7
|
+
@my_alias = @my_aliases.first
|
8
|
+
|
9
|
+
@file_reader = FileReader.new(source_file_path, cleaner)
|
10
|
+
end
|
11
|
+
|
12
|
+
# This method returns a Chat instance, or false if it could not parse the
|
13
|
+
# file.
|
14
|
+
def parse
|
15
|
+
if pre_parse
|
16
|
+
messages = @file_reader.other_lines.map do |line|
|
17
|
+
basic_message_match = @line_regex.match(line)
|
18
|
+
meta_message_match = @line_regex_status.match(line)
|
19
|
+
if basic_message_match
|
20
|
+
create_message(basic_message_match)
|
21
|
+
elsif meta_message_match
|
22
|
+
create_status_or_event_message(meta_message_match)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Chat.new(messages, @metadata)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Extract required data from the file. Run by parse.
|
31
|
+
def pre_parse
|
32
|
+
@file_reader.read
|
33
|
+
metadata = Metadata.new(MetadataParser.new(@file_reader.first_line).parse)
|
34
|
+
if metadata.valid?
|
35
|
+
@metadata = metadata
|
36
|
+
@alias_registry = AliasRegistry.new(@metadata.their_screen_name)
|
37
|
+
@my_aliases.each do |my_alias|
|
38
|
+
@alias_registry[my_alias] = @metadata.my_screen_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_message(match_data)
|
44
|
+
# Either a regular message line or an auto-reply/away message.
|
45
|
+
time = time_parser.parse(match_data[:timestamp])
|
46
|
+
if time
|
47
|
+
my_alias = match_data[:sn_or_alias]
|
48
|
+
my_screen_name = @alias_registry[my_alias]
|
49
|
+
body = match_data[:body]
|
50
|
+
is_auto_reply = match_data[:auto_reply]
|
51
|
+
|
52
|
+
AutoOrXmlMessageCreator.new(body, time, my_screen_name, my_alias, is_auto_reply).create
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_status_or_event_message(match_data)
|
57
|
+
time = time_parser.parse(match_data[:timestamp])
|
58
|
+
str = match_data[:body]
|
59
|
+
|
60
|
+
if time && event_we_care_about?(str)
|
61
|
+
create_status_message(str, time) || create_event_message(str, time)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def time_parser
|
66
|
+
@time_parser ||= TimeParser.new(@metadata.start_year, @metadata.start_month, @metadata.start_mday)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def event_we_care_about?(str)
|
72
|
+
Event::IGNORE.none? { |regex| str =~ regex }
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_event_message(text, time)
|
76
|
+
EventMessageCreator.new(text, time, @my_alias, @metadata.my_screen_name, @alias_registry).create
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_status_message(text, time)
|
80
|
+
StatusMessageCreator.new(text, time, @alias_registry).create
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|