pidgin2adium 3.0.1 → 3.1.0

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.
data/lib/pidgin2adium.rb CHANGED
@@ -3,8 +3,6 @@
3
3
  # A ruby program to convert Pidgin log files to Adium log files, then place
4
4
  # them in the Adium log directory.
5
5
 
6
- $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
7
-
8
6
  require 'fileutils'
9
7
  require 'pidgin2adium/log_parser'
10
8
 
@@ -14,120 +12,122 @@ module Pidgin2Adium
14
12
  ADIUM_LOG_DIR = File.expand_path('~/Library/Application Support/Adium 2.0/Users/Default/Logs/') << '/'
15
13
  # These files/directories show up in Dir.entries()
16
14
  BAD_DIRS = %w{. .. .DS_Store Thumbs.db .system}
17
- VERSION = "3.0.1"
15
+ VERSION = "3.1.0"
18
16
  # For displaying after we finish converting
19
17
  @@oops_messages = []
20
18
  @@error_messages = []
21
19
 
22
- def log_msg(str) #:nodoc:
23
- puts str.to_s
24
- end
20
+ def log_msg(str) #:nodoc:
21
+ puts str.to_s
22
+ end
25
23
 
26
- def oops(str) #:nodoc:
27
- @@oops_messages << str
28
- warn("Oops: #{str}")
29
- end
24
+ def oops(str) #:nodoc:
25
+ @@oops_messages << str
26
+ warn("Oops: #{str}")
27
+ end
30
28
 
31
- def error(str) #:nodoc:
32
- @@error_messages << str
33
- warn("Error: #{str}")
34
- end
29
+ def error(str) #:nodoc:
30
+ @@error_messages << str
31
+ warn("Error: #{str}")
32
+ end
35
33
 
36
- #######################
37
- #So that we can use log_msg when calling delete_search_indexes() by itself
38
- module_function :log_msg, :oops, :error
39
- #######################
34
+ #######################
35
+ #So that we can use log_msg when calling delete_search_indexes() by itself
36
+ module_function :log_msg, :oops, :error
37
+ #######################
40
38
 
41
- # Parses the provided log.
42
- # Returns a LogFile instance or false if an error occurred.
43
- def parse(logfile_path, my_aliases)
44
- logfile_path = File.expand_path(logfile_path)
45
- ext = File.extname(logfile_path).sub('.', '').downcase
39
+ # Parses the provided log.
40
+ # Returns a LogFile instance or false if an error occurred.
41
+ def parse(logfile_path, my_aliases)
42
+ logfile_path = File.expand_path(logfile_path)
43
+ ext = File.extname(logfile_path).sub('.', '').downcase
44
+
45
+ if(ext == "html" || ext == "htm")
46
+ parser = HtmlLogParser.new(logfile_path, my_aliases)
47
+ elsif(ext == "txt")
48
+ parser = TextLogParser.new(logfile_path, my_aliases)
49
+ else
50
+ error("Doing nothing, logfile is not a text or html file. Path: #{logfile_path}.")
51
+ return false
52
+ end
46
53
 
47
- if(ext == "html" || ext == "htm")
48
- parser = HtmlLogParser.new(logfile_path, my_aliases)
49
- elsif(ext == "txt")
50
- parser = TextLogParser.new(logfile_path, my_aliases)
51
- else
52
- error("Doing nothing, logfile is not a text or html file. Path: #{logfile_path}.")
53
- return false
54
+ return parser.parse()
54
55
  end
55
56
 
56
- return parser.parse()
57
- end
57
+ # Parses the provided log and writes out the log in Adium format.
58
+ # Returns:
59
+ # * true if it successfully converted and wrote out the log,
60
+ # * false if an error occurred, or
61
+ # * Pidgin2Adium::FILE_EXISTS if file already exists AND
62
+ # opts[:overwrite] = false.
63
+ #
64
+ # You can add options using the _opts_ hash, which can have the following
65
+ # keys, all of which are optional:
66
+ # * *overwrite*: If true, then overwrite even if log is found.
67
+ # Defaults to false.
68
+ # * *output_dir*: The top-level dir to put the logs in.
69
+ # Logs under output_dir are still each in their own folders, etc.
70
+ # Defaults to Pidgin2Adium::ADIUM_LOG_DIR
71
+ def parse_and_generate(logfile_path, my_aliases, opts = {})
72
+ opts = {} unless opts.is_a?(Hash)
73
+ overwrite = !!opts[:overwrite]
74
+ if opts.key?(:output_dir)
75
+ output_dir = opts[:output_dir]
76
+ else
77
+ output_dir = ADIUM_LOG_DIR
78
+ end
58
79
 
59
- # Parses the provided log and writes out the log in Adium format.
60
- # Returns the path to the converted log, false if an error occurred, or
61
- # Pidgin2Adium::FILE_EXISTS if file already exists AND opts[:overwrite] =
62
- # false.
63
- #
64
- # You can add options using the _opts_ hash, which can have the following
65
- # keys, all of which are optional:
66
- # * *overwrite*: If true, then overwrite even if log is found.
67
- # Defaults to false.
68
- # * *output_dir*: The top-level dir to put the logs in.
69
- # Logs under output_dir are still each in their own folders, etc.
70
- # Defaults to Pidgin2Adium::ADIUM_LOG_DIR
71
- def parse_and_generate(logfile_path, my_aliases, opts = {})
72
- opts = {} unless opts.is_a?(Hash)
73
- overwrite = !!opts[:overwrite]
74
- if opts.key?(:output_dir)
75
- output_dir = opts[:output_dir]
76
- else
77
- output_dir = ADIUM_LOG_DIR
78
- end
80
+ unless File.directory?(output_dir)
81
+ puts "Output log directory (#{output_dir}) does not exist or is not a directory."
82
+ begin
83
+ FileUtils.mkdir_p(output_dir)
84
+ rescue Errno::EACCES
85
+ puts "Permission denied, could not create output directory (#{output_dir})"
86
+ return false
87
+ end
88
+ end
79
89
 
80
- unless File.directory?(output_dir)
81
- puts "Output log directory (#{output_dir}) does not exist or is not a directory."
82
- begin
83
- FileUtils.mkdir_p(output_dir)
84
- rescue Errno::EACCES
85
- puts "Permission denied, could not create output directory (#{output_dir})"
90
+ logfile_obj = parse(logfile_path, my_aliases)
91
+ return false if logfile_obj == false
92
+ dest_file_path = logfile_obj.write_out(overwrite, output_dir)
93
+ if dest_file_path == false
94
+ error("Successfully parsed file, but failed to write it out. Path: #{logfile_path}.")
86
95
  return false
96
+ elsif dest_file_path == FILE_EXISTS
97
+ log_msg("File already exists.")
98
+ return FILE_EXISTS
99
+ else
100
+ log_msg("Output to: #{dest_file_path}")
101
+ return true
87
102
  end
88
103
  end
89
104
 
90
- logfile_obj = parse(logfile_path, my_aliases)
91
- return false if logfile_obj == false
92
- dest_file_path = logfile_obj.write_out(overwrite, output_dir)
93
- if dest_file_path == false
94
- error("Successfully parsed file, but failed to write it out. Path: #{logfile_path}.")
95
- return false
96
- elsif dest_file_path == FILE_EXISTS
97
- log_msg("File already exists.")
98
- return FILE_EXISTS
99
- else
100
- log_msg("Output to: #{dest_file_path}")
101
- return true
102
- end
103
- end
104
-
105
- # Newly-converted logs are viewable in the Adium Chat Transcript
106
- # Viewer, but are not indexed, so a search of the logs doesn't give
107
- # results from the converted logs. To fix this, we delete the cached log
108
- # indexes, which forces Adium to re-index.
109
- #
110
- # Note: This function is run by LogConverter after converting all of its
111
- # files. LogFile.write_out intentionally does _not_ run it in order to
112
- # allow for batch-processing of files. Thus, you will probably want to run
113
- # Pidgin2Adium.delete_search_indexes after running LogFile.write_out in
114
- # your own scripts.
115
- def delete_search_indexes()
116
- log_msg "Deleting log search indexes in order to force re-indexing of imported logs..."
117
- dirty_file = File.expand_path("~/Library/Caches/Adium/Default/DirtyLogs.plist")
118
- log_index_file = File.expand_path("~/Library/Caches/Adium/Default/Logs.index")
119
- [dirty_file, log_index_file].each do |f|
120
- if File.exist?(f)
121
- if File.writable?(f)
122
- File.delete(f)
123
- else
124
- error("File exists but is not writable. Please delete it yourself: #{f}")
105
+ # Newly-converted logs are viewable in the Adium Chat Transcript
106
+ # Viewer, but are not indexed, so a search of the logs doesn't give
107
+ # results from the converted logs. To fix this, we delete the cached log
108
+ # indexes, which forces Adium to re-index.
109
+ #
110
+ # Note: This function is run by LogConverter after converting all of its
111
+ # files. LogFile.write_out intentionally does _not_ run it in order to
112
+ # allow for batch-processing of files. Thus, you will probably want to run
113
+ # Pidgin2Adium.delete_search_indexes after running LogFile.write_out in
114
+ # your own scripts.
115
+ def delete_search_indexes()
116
+ log_msg "Deleting log search indexes in order to force re-indexing of imported logs..."
117
+ dirty_file = File.expand_path("~/Library/Caches/Adium/Default/DirtyLogs.plist")
118
+ log_index_file = File.expand_path("~/Library/Caches/Adium/Default/Logs.index")
119
+ [dirty_file, log_index_file].each do |f|
120
+ if File.exist?(f)
121
+ if File.writable?(f)
122
+ File.delete(f)
123
+ else
124
+ error("File exists but is not writable. Please delete it yourself: #{f}")
125
+ end
125
126
  end
126
127
  end
128
+ log_msg "...done."
129
+ log_msg "When you next start the Adium Chat Transcript Viewer, it will re-index the logs, which may take a while."
127
130
  end
128
- log_msg "...done."
129
- log_msg "When you next start the Adium Chat Transcript Viewer, it will re-index the logs, which may take a while."
130
- end
131
131
 
132
- module_function :parse, :parse_and_generate, :delete_search_indexes
132
+ module_function :parse, :parse_and_generate, :delete_search_indexes
133
133
  end
@@ -20,8 +20,8 @@ module Pidgin2Adium
20
20
  # 2: attributes
21
21
  tag_regex = /<(\/?\w*)\s*([^>]*)>/
22
22
 
23
- # WP bug fix for comments - in case you REALLY meant to type '< !--'
24
- text.gsub!('< !--', '< !--')
23
+ # WP bug fix for comments - in case you REALLY meant to type '< !--'
24
+ text.gsub!('< !--', '< !--')
25
25
 
26
26
  # WP bug fix for LOVE <3 (and other situations with '<' before a number)
27
27
  text.gsub!(/<([0-9]{1})/, '&lt;\1')
@@ -0,0 +1,412 @@
1
+ # Contains the BasicParser class.
2
+ # For its subclasses, see html_log_parser.rb and text_log_parser.rb.
3
+ # The subclasses parse the file passed into it and return a LogFile object.
4
+ # The BasicParser class just provides some common functionality.
5
+ #
6
+ # Please use Pidgin2Adium.parse or Pidgin2Adium.parse_and_generate instead of
7
+ # using these classes directly.
8
+
9
+ require 'date'
10
+ require 'time'
11
+
12
+ require 'pidgin2adium/log_file'
13
+ require 'pidgin2adium/message'
14
+
15
+ module Pidgin2Adium
16
+ # Empty class. Raise'd by LogParser if the first line of a log is not
17
+ # parseable.
18
+ class InvalidFirstLineError < StandardError; end
19
+
20
+ # BasicParser is a base class. Its subclasses are TextLogParser and
21
+ # HtmlLogParser.
22
+ #
23
+ # Please use Pidgin2Adium.parse or Pidgin2Adium.parse_and_generate instead of
24
+ # using this class directly.
25
+ class BasicParser
26
+ include Pidgin2Adium
27
+ def initialize(src_path, user_aliases)
28
+ @src_path = src_path
29
+ # Whitespace is removed for easy matching later on.
30
+ @user_aliases = user_aliases.split(',').map!{|x| x.downcase.gsub(/\s+/,'') }.uniq
31
+ # @user_alias is set each time get_sender_by_alias is called. It is a non-normalized
32
+ # alias.
33
+ # Set an initial value just in case the first message doesn't give
34
+ # us an alias.
35
+ @user_alias = user_aliases.split(',')[0]
36
+
37
+ @tz_offset = get_time_zone_offset()
38
+
39
+ @log_file_is_valid = true
40
+ begin
41
+ file = File.new(@src_path, 'r')
42
+ @first_line = file.readline
43
+ @file_content = file.read
44
+ file.close
45
+ rescue Errno::ENOENT
46
+ oops("#{@src_path} doesn't exist! Continuing...")
47
+ @log_file_is_valid = false
48
+ return nil
49
+ end
50
+
51
+ # Time regexes must be set before pre_parse().
52
+ # "4/18/2007 11:02:00 AM" => %w{4, 18, 2007, 11, 02, 00, AM}
53
+ # ONLY used (if at all) in first line of chat ("Conversation with...at...")
54
+ @time_regex_first_line = %r{^(\d{1,2})/(\d{1,2})/(\d{4}) (\d{1,2}):(\d{2}):(\d{2}) ([AP]M)$}
55
+ # "2007-04-17 12:33:13" => %w{2007, 04, 17, 12, 33, 13}
56
+ @time_regex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/
57
+
58
+ begin
59
+ @service,
60
+ @user_SN,
61
+ @partner_SN,
62
+ # @basic_time_info is for files that only have the full
63
+ # timestamp at the top; we can use it to fill in the minimal
64
+ # per-line timestamps. It is a hash with 3 keys:
65
+ # * :year
66
+ # * :mon
67
+ # * :mday (day of month)
68
+ # You should be able to fill everything else in. If you can't,
69
+ # something's wrong.
70
+ @basic_time_info,
71
+ # When the chat started, in Adium's format
72
+ @adium_chat_time_start = pre_parse()
73
+ rescue InvalidFirstLineError
74
+ # The first line isn't parseable
75
+ @log_file_is_valid = false
76
+ error("Failed to parse, invalid first line: #{@src_path}")
77
+ return # stop processing
78
+ end
79
+
80
+ # @status_map, @lib_purple_events, and @events are used in
81
+ # create_status_or_event_msg
82
+ @status_map = {
83
+ /(.+) logged in\.$/ => 'online',
84
+ /(.+) logged out\.$/ => 'offline',
85
+ /(.+) has signed on\.$/ => 'online',
86
+ /(.+) has signed off\.$/ => 'offline',
87
+ /(.+) has gone away\.$/ => 'away',
88
+ /(.+) is no longer away\.$/ => 'available',
89
+ /(.+) has become idle\.$/ => 'idle',
90
+ /(.+) is no longer idle\.$/ => 'available'
91
+ }
92
+
93
+ # lib_purple_events are all of event_type libPurple
94
+ @lib_purple_events = [
95
+ # file transfer
96
+ /Starting transfer of .+ from (.+)/,
97
+ /^Offering to send .+ to (.+)$/,
98
+ /(.+) is offering to send file/,
99
+ /^Transfer of file .+ complete$/,
100
+ /Error reading|writing|accessing .+: .+/,
101
+ /You cancell?ed the transfer of/,
102
+ /File transfer cancelled/,
103
+ /(.+?) cancell?ed the transfer of/,
104
+ /(.+?) cancelled the file transfer/,
105
+ # Direct IM - actual (dis)connect events are their own types
106
+ /^Attempting to connect to (.+) at .+ for Direct IM\./,
107
+ /^Asking (.+) to connect to us at .+ for Direct IM\./,
108
+ /^Attempting to connect via proxy server\.$/,
109
+ /^Direct IM with (.+) failed/,
110
+ # encryption
111
+ /Received message encrypted with wrong key/,
112
+ /^Requesting key\.\.\.$/,
113
+ /^Outgoing message lost\.$/,
114
+ /^Conflicting Key Received!$/,
115
+ /^Error in decryption- asking for resend\.\.\.$/,
116
+ /^Making new key pair\.\.\.$/,
117
+ # sending errors
118
+ /^Last outgoing message not received properly- resetting$/,
119
+ /Resending\.\.\./,
120
+ # connection errors
121
+ /Lost connection with the remote user:.+/,
122
+ # chats
123
+ /^.+ entered the room\.$/,
124
+ /^.+ left the room\.$/
125
+ ]
126
+
127
+ # non-libpurple events
128
+ # Each key maps to an event_type string. The keys will be matched against a line of chat
129
+ # and the partner's alias will be in regex group 1, IF the alias is matched.
130
+ @event_map = {
131
+ # .+ is not an alias, it's a proxy server so no grouping
132
+ /^Attempting to connect to .+\.$/ => 'direct-im-connect',
133
+ # NB: pidgin doesn't track when Direct IM is disconnected, AFAIK
134
+ /^Direct IM established$/ => 'directIMConnected',
135
+ /Unable to send message/ => 'chat-error',
136
+ /You missed .+ messages from (.+) because they were too large/ => 'chat-error',
137
+ /User information not available/ => 'chat-error'
138
+ }
139
+
140
+ @ignore_events = [
141
+ # Adium ignores SN/alias changes.
142
+ /^.+? is now known as .+?\.<br\/?>$/
143
+ ]
144
+ end
145
+
146
+ # This method returns a LogFile instance, or false if an error occurred.
147
+ def parse
148
+ # Prevent parse from being called directly from BasicParser, since
149
+ # it uses subclassing magic.
150
+ if self.class == BasicParser
151
+ oops("Please don't call parse directly from BasicParser. Use a subclass :)")
152
+ return false
153
+ end
154
+ return false unless @log_file_is_valid
155
+ @file_content = cleanup(@file_content).split("\n")
156
+
157
+ @file_content.map! do |line|
158
+ # "next" returns nil which is removed by compact
159
+ next if line =~ /^\s+$/
160
+ if line =~ @line_regex
161
+ create_msg($~.captures)
162
+ elsif line =~ @line_regex_status
163
+ msg = create_status_or_event_msg($~.captures)
164
+ # Error occurred while parsing
165
+ return false if msg == false
166
+ else
167
+ error "Could not parse line:"
168
+ p line
169
+ return false
170
+ end
171
+ end
172
+ @file_content.compact!
173
+ return LogFile.new(@file_content, @service, @user_SN, @partner_SN, @adium_chat_time_start)
174
+ end
175
+
176
+ def get_time_zone_offset()
177
+ # We must have a tz_offset or else the Adium Chat Log viewer
178
+ # doesn't read the date correctly and then:
179
+ # 1) the log has an empty start date column in the viewer
180
+ # 2) The timestamps are all the same for the whole log
181
+ tz_match = /([-\+]\d+)[A-Z]{3}\.(?:txt|htm|html)/.match(@src_path)
182
+ if tz_match and tz_match[1]
183
+ tz_offset = tz_match[1]
184
+ else
185
+ # "-0500" (3d rather than 2d to allow for "+")
186
+ tz_offset = sprintf('%+03d00', Time.zone_offset(Time.now.zone) / 3600)
187
+ end
188
+ return tz_offset
189
+ end
190
+
191
+ def try_to_parse_first_line_time(first_line_time)
192
+ formats = [
193
+ "%m/%d/%Y %I:%M:%S %P", # 01/22/2008 03:01:45 PM
194
+ "%Y-%m-%d %H:%M:%S" # 2008-01-22 23:08:24
195
+ ]
196
+ parsed = nil
197
+ formats.each do |format|
198
+ begin
199
+ parsed = Time.strptime(first_line_time, format)
200
+ break
201
+ rescue ArgumentError
202
+ end
203
+ end
204
+ parsed
205
+ end
206
+
207
+ def try_to_parse_time(time)
208
+ formats = [
209
+ "%Y/%m/%d %H:%M:%S", # 2008/01/22 04:01:45
210
+ "%Y-%m-%d %H:%M:%S" # 2008-01-22 04:01:45
211
+ ]
212
+ parsed = nil
213
+ formats.each do |format|
214
+ begin
215
+ parsed = Time.strptime(time, format)
216
+ break
217
+ rescue ArgumentError
218
+ end
219
+ end
220
+ parsed
221
+ end
222
+
223
+ def try_to_parse_minimal_time(minimal_time)
224
+ # 04:01:45 AM
225
+ minimal_format_with_ampm = "%I:%M:%S %P"
226
+ # 23:01:45
227
+ minimal_format_without_ampm = "%H:%M:%S"
228
+
229
+ time_hash = nil
230
+
231
+ # Use Date._strptime to allow filling in the blanks on minimal
232
+ # timestamps
233
+ if minimal_time =~ /[AP]M$/
234
+ time_hash = Date._strptime(minimal_time, minimal_format_with_ampm)
235
+ else
236
+ time_hash = Date._strptime(minimal_time, minimal_format_without_ampm)
237
+ end
238
+ if time_hash.nil?
239
+ # Date._strptime returns nil on failure
240
+ return nil
241
+ end
242
+ # Fill in the blanks
243
+ time_hash[:year] = @basic_time_info[:year]
244
+ time_hash[:mon] = @basic_time_info[:mon]
245
+ time_hash[:mday] = @basic_time_info[:mday]
246
+ new_time = Time.local(time_hash[:year],
247
+ time_hash[:mon],
248
+ time_hash[:mday],
249
+ time_hash[:hour],
250
+ time_hash[:min],
251
+ time_hash[:sec])
252
+ new_time
253
+ end
254
+
255
+
256
+ #--
257
+ # Adium time format: YYYY-MM-DD\THH:MM:SS[+-]TZ_HRS like:
258
+ # 2008-10-05T22:26:20-0800
259
+ # HOWEVER:
260
+ # If it's the first line, then return it like this (note periods):
261
+ # 2008-10-05T22.26.20-0800
262
+ # because it will be used in the filename.
263
+ #++
264
+ # Converts a pidgin datestamp to an Adium one.
265
+ # Returns a string representation of _time_ or
266
+ # nil if it couldn't parse the provided _time_.
267
+ def create_adium_time(time, is_first_line = false)
268
+ return nil if time.nil?
269
+ if is_first_line
270
+ new_time = try_to_parse_first_line_time(time)
271
+ else
272
+ new_time = try_to_parse_time(time)
273
+ if new_time.nil?
274
+ new_time = try_to_parse_minimal_time(time)
275
+ end
276
+ end
277
+
278
+ return nil if new_time.nil?
279
+
280
+ if is_first_line
281
+ adium_time = new_time.strftime("%Y-%m-%dT%H.%M.%S#{@tz_offset}")
282
+ else
283
+ adium_time = new_time.strftime("%Y-%m-%dT%H:%M:%S#{@tz_offset}")
284
+ end
285
+ adium_time
286
+ end
287
+
288
+ # Extract required data from the file. Run by parse.
289
+ def pre_parse
290
+ # Deal with first line.
291
+
292
+ # the first line is special. It tells us (in order of regex groups):
293
+ # 1) who we're talking to
294
+ # 2) what time/date
295
+ # 3) what SN we used
296
+ # 4) what protocol (AIM, icq, jabber...)
297
+ first_line_match = /Conversation with (.+?) at (.+?) on (.+?) \((.+?)\)/.match(@first_line)
298
+ if first_line_match.nil?
299
+ raise InvalidFirstLineError
300
+ else
301
+ service = first_line_match[4]
302
+ # @user_SN is normalized to avoid "AIM.name" and "AIM.na me" folders
303
+ user_SN = first_line_match[3].downcase.tr(' ', '')
304
+ partner_SN = first_line_match[1]
305
+ pidgin_chat_time_start = first_line_match[2]
306
+ basic_time_info = case pidgin_chat_time_start
307
+ when @time_regex
308
+ {:year => $1.to_i,
309
+ :mon => $2.to_i,
310
+ :mday => $3.to_i}
311
+ when @time_regex_first_line
312
+ {:year => $3.to_i,
313
+ :mon => $1.to_i,
314
+ :mday => $2.to_i}
315
+ end
316
+ adium_chat_time_start = create_adium_time(pidgin_chat_time_start, true)
317
+ return [service,
318
+ user_SN,
319
+ partner_SN,
320
+ basic_time_info,
321
+ adium_chat_time_start]
322
+ end
323
+ end
324
+
325
+ def get_sender_by_alias(alias_name)
326
+ no_action = alias_name.sub(/^\*{3}/, '')
327
+ if @user_aliases.include? no_action.downcase.gsub(/\s+/, '')
328
+ # Set the current alias being used of the ones in @user_aliases
329
+ @user_alias = no_action
330
+ return @user_SN
331
+ else
332
+ return @partner_SN
333
+ end
334
+ end
335
+
336
+ #--
337
+ # create_msg takes an array of captures from matching against
338
+ # @line_regex and returns a Message object or one of its subclasses.
339
+ # It can be used for TextLogParser and HtmlLogParser because both of
340
+ # they return data in the same indexes in the matches array.
341
+ #++
342
+ def create_msg(matches)
343
+ msg = nil
344
+ # Either a regular message line or an auto-reply/away message.
345
+ time = create_adium_time(matches[0])
346
+ return nil if time.nil?
347
+ buddy_alias = matches[1]
348
+ sender = get_sender_by_alias(buddy_alias)
349
+ body = matches[3]
350
+ if matches[2] # auto-reply
351
+ msg = AutoReplyMessage.new(sender, time, buddy_alias, body)
352
+ else
353
+ # normal message
354
+ msg = XMLMessage.new(sender, time, buddy_alias, body)
355
+ end
356
+ return msg
357
+ end
358
+
359
+ #--
360
+ # create_status_or_event_msg takes an array of +MatchData+ captures from
361
+ # matching against @line_regex_status and returns an Event or Status.
362
+ # Returns nil if it's a message that should be ignored, or false if an
363
+ # error occurred.
364
+ #++
365
+ def create_status_or_event_msg(matches)
366
+ # ["22:58:00", "BuddyName logged in."]
367
+ # 0: time
368
+ # 1: status message or event
369
+ msg = nil
370
+ time = create_adium_time(matches[0])
371
+ return nil if time.nil?
372
+ str = matches[1]
373
+ # Return nil, which will get compact'ed out
374
+ return nil if @ignore_events.detect{|regex| str =~ regex }
375
+
376
+ regex, status = @status_map.detect{|regex, status| str =~ regex}
377
+ if regex and status
378
+ # Status message
379
+ buddy_alias = regex.match(str)[1]
380
+ sender = get_sender_by_alias(buddy_alias)
381
+ msg = StatusMessage.new(sender, time, buddy_alias, status)
382
+ else
383
+ # Test for event
384
+ regex = @lib_purple_events.detect{|regex| str =~ regex }
385
+ event_type = 'libpurpleEvent' if regex
386
+ unless regex and event_type
387
+ # not a libpurple event, try others
388
+ regex, event_type = @event_map.detect{|regex,event_type| str =~ regex}
389
+ unless regex and event_type
390
+ error(sprintf("Error parsing status or event message, no status or event found: %p", str))
391
+ return false
392
+ end
393
+ end
394
+
395
+ if regex and event_type
396
+ regex_matches = regex.match(str)
397
+ # Event message
398
+ if regex_matches.size == 1
399
+ # No alias - this means it's the user
400
+ buddy_alias = @user_alias
401
+ sender = @user_SN
402
+ else
403
+ buddy_alias = regex_matches[1]
404
+ sender = get_sender_by_alias(buddy_alias)
405
+ end
406
+ msg = Event.new(sender, time, buddy_alias, str, event_type)
407
+ end
408
+ end
409
+ return msg
410
+ end
411
+ end # END BasicParser class
412
+ end