pidgin2adium 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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