pidgin2adium 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pidgin2adium
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabe B-W
@@ -9,32 +9,52 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-24 00:00:00 -04:00
12
+ date: 2009-11-24 00:00:00 -05:00
13
13
  default_executable:
14
- dependencies: []
15
-
16
- description: Converts Pidgin logs and statuses to Adium format and makes them available to Adium. Also installstwo shell scripts, pidgin2adium_logs and pidgin2adium_status.
17
- email: gbw@rubyforge.org
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.3
24
+ version:
25
+ description: |-
26
+ Pidgin2Adium is a fast, easy way to convert Pidgin (formerly gaim) logs to the
27
+ Adium format.
28
+ Note that it assumes a Mac OS X environment with Adium installed.
29
+ email:
30
+ - gbw@brandeis.edu
18
31
  executables:
19
- - pidgin2adium_logs
20
- - pidgin2adium_status
32
+ - pidgin2adium
21
33
  extensions: []
22
34
 
23
- extra_rdoc_files: []
24
-
35
+ extra_rdoc_files:
36
+ - History.txt
37
+ - Manifest.txt
38
+ - README.rdoc
25
39
  files:
26
- - lib/pidgin2adium/balance-tags.rb
27
- - lib/pidgin2adium/ChatFileGenerator.rb
28
- - lib/pidgin2adium/logs.rb
29
- - lib/pidgin2adium/SrcFileParse.rb
30
- - lib/pidgin2adium/status.rb
40
+ - History.txt
41
+ - Manifest.txt
42
+ - README.rdoc
43
+ - Rakefile.rb
44
+ - bin/pidgin2adium
45
+ - lib/pidgin2adium.rb
46
+ - lib/pidgin2adium/balance_tags.rb
47
+ - lib/pidgin2adium/log_converter.rb
48
+ - lib/pidgin2adium/log_file.rb
49
+ - lib/pidgin2adium/log_parser.rb
31
50
  has_rdoc: true
32
- homepage: http://pidgin2adium.rubyforge.org
51
+ homepage: http://rubyforge.org/projects/pidgin2adium/
33
52
  licenses: []
34
53
 
35
54
  post_install_message:
36
- rdoc_options: []
37
-
55
+ rdoc_options:
56
+ - --main
57
+ - README.rdoc
38
58
  require_paths:
39
59
  - lib
40
60
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -55,6 +75,6 @@ rubyforge_project: pidgin2adium
55
75
  rubygems_version: 1.3.5
56
76
  signing_key:
57
77
  specification_version: 3
58
- summary: Converts Pidgin logs and statuses to Adium format and makes them available to Adium.
78
+ summary: Pidgin2Adium is a fast, easy way to convert Pidgin (formerly gaim) logs to the Adium format
59
79
  test_files: []
60
80
 
@@ -1,67 +0,0 @@
1
- #!/usr/bin/ruby -w
2
-
3
- =begin
4
- Author: Gabe Berke-Williams, 2008
5
- This is the shell script, which is a wrapper around Pidgin2Adium::Logs.
6
- Call it like so:
7
- <tt>pidgin2adium_logs.rb -i ~/in_logs/ -o ~/out_logs/ -l AIM.myscreenname -a me,screenname,my_pidgin_alias,other_pidgin_alias</tt>
8
- For <tt>-a/--aliases</tt>, there is no need to use spaces or capitalization, since spaces will be stripped out and the aliases will
9
- be lowercased anyway.
10
- =end
11
-
12
- require 'pidgin2adium/logs'
13
- require 'optparse'
14
-
15
- options = {}
16
- OptionParser.new do |opts|
17
- opts.banner = "Usage: #{File.basename($0)} [options]"
18
- opts.on('-i IN_DIR', '--in IN_DIR', 'Specify directory where pidgin logs are stored') do |v|
19
- options[:in] = v
20
- end
21
- opts.on('-o', '--out OUT_DIR', 'Specify directory where Adium logs will be stored (not the Adium directory in ~/Library)') do |out|
22
- options[:out] = out
23
- end
24
- opts.on('-l', '--libdir LIBRARY_DIR',
25
- 'Specify dirname where Adium logs are stored (eg "AIM.<username>" for',
26
- '~/Library/Application Support/Adium 2.0/Users/Default/Logs/AIM.<username>)') do |ld|
27
- options[:libdir] = ld
28
- end
29
- opts.on('-d', '--debug', 'Turn debug on.') do |lf|
30
- options[:debug] = true
31
- end
32
- opts.on('-t', "--time-zone [TIME ZONE]",
33
- "Set time zone like \"EST\". Defaults to local time zone: #{Time.now.zone}") do |tz|
34
- options[:timezone] = tz
35
- end
36
- opts.on('-a', "--aliases MY_ALIASES_AND_SNs",
37
- "A comma-separated list of your aliases and screenname(s) so this script knows which person in a chat is you.",
38
- "Whitespace is removed and aliases are lowercased.") do |aliases|
39
- options[:aliases] = aliases.split(',')
40
- end
41
- opts.on_tail("-h", "--help", "Show this message") do
42
- puts opts
43
- exit
44
- end
45
- end.parse!
46
-
47
- need_opts = false
48
- required_opts = [[:i, :in], [:o, :out], [:l, :libdir], [:a, :aliases]]
49
- required_opts.each do |short, long|
50
- if options.has_key?(short) or options.has_key?(long)
51
- next
52
- else
53
- need_opts = true
54
- puts "Required option -#{short}/--#{long} missing."
55
- end
56
- end
57
- exit 1 if need_opts
58
-
59
- log_converter = Pidgin2Adium::Logs.new(options[:in],
60
- options[:out],
61
- options[:aliases],
62
- options[:libdir],
63
- options[:timezone],
64
- options[:debug]
65
- )
66
-
67
- log_converter.start
@@ -1,15 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- # Shell script wrapper for Pidgin2Adium::Status
4
-
5
- require 'pidgin2adium/status'
6
-
7
- xml_file = ARGV[0]
8
-
9
- unless File.readable?(xml_file)
10
- puts "XML file (#{xml_file}) is not readable. Exiting."
11
- exit 1
12
- end
13
-
14
- status_converter = Pidgin2Adium::Status.new(xml_file)
15
- status_converter.start
@@ -1,59 +0,0 @@
1
- # ADD DOCUMENTATION
2
- require 'pidgin2adium/balance-tags.rb'
3
-
4
- module Pidgin2Adium
5
- class ChatFileGenerator
6
- def initialize(service, userSN, partnerSN, adiumChatTimeStart, destDirBase)
7
- @service = service
8
- @userSN = userSN
9
- @partnerSN = partnerSN
10
- @adiumChatTimeStart = adiumChatTimeStart
11
- @destDirBase = destDirBase
12
-
13
- # @chatLines is an array of Message, Status, and Event objects
14
- @chatLines = []
15
- # key is for Pidgin, value is for Adium
16
- # Just used for <service>.<screenname> in directory structure
17
- @SERVICE_NAME_MAP = {'aim' => 'AIM',
18
- 'jabber' =>'jabber',
19
- 'gtalk'=> 'GTalk',
20
- 'icq' => 'ICQ',
21
- 'qq' => 'QQ',
22
- 'msn' => 'MSN',
23
- 'yahoo' => 'Yahoo'}
24
- end
25
-
26
- # Add a line to @chatLines.
27
- # It is its own method because attr_writer creates the method
28
- # 'chatMessage=', which doesn't help for chatMessage.push
29
- def appendLine(line)
30
- @chatLines.push(line)
31
- end
32
-
33
- # Returns path of output file
34
- def convert()
35
- serviceName = @SERVICE_NAME_MAP[@service.downcase]
36
- destDirReal = File.join(@destDirBase, "#{serviceName}.#{@userSN}", @partnerSN, "#{@partnerSN} (#{@adiumChatTimeStart}).chatlog")
37
- FileUtils.mkdir_p(destDirReal)
38
- destFilePath = destDirReal << '/' << "#{@partnerSN} (#{@adiumChatTimeStart}).xml"
39
- if File.exist?(destFilePath)
40
- return Pidgin2Adium::Logs::FILE_EXISTS
41
- end
42
-
43
- allMsgs = ""
44
- # TODO: inject?
45
- @chatLines.each { |obj| allMsgs << obj.getOutput() }
46
- # xml is done.
47
-
48
- # no \n before </chat> because allMsgs has it already
49
- ret = sprintf('<?xml version="1.0" encoding="UTF-8" ?>'<<"\n"+
50
- '<chat xmlns="http://purl.org/net/ulf/ns/0.4-02" account="%s" service="%s">'<<"\n"<<'%s</chat>', @userSN, serviceName, allMsgs)
51
-
52
- # we already checked to see if the file previously existed.
53
- outfile = File.new(destFilePath, 'w')
54
- outfile.puts(ret)
55
- outfile.close
56
- return destFilePath
57
- end
58
- end
59
- end
@@ -1,485 +0,0 @@
1
- # =SrcFileParse
2
- # The class +SrcFileParse+ has 2 subclasses, +SrcTxtFileParse+ and +SrcHtmlFileParse+
3
- # It parses the file passed into it and extracts the following
4
- # from each line in the chat: time, alias, and message and/or status.
5
-
6
- require 'parsedate'
7
-
8
- module Pidgin2Adium
9
- # The two subclasses of +SrcFileParse+,
10
- # +SrcTxtFileParse+ and +SrcHtmlFileParse+, only differ
11
- # in that they have their own @lineRegex, @lineRegexStatus,
12
- # and most importantly, createMsg and createStatusOrEventMsg, which take
13
- # the +MatchData+ objects from matching against @lineRegex or
14
- # @lineRegexStatus, respectively and return object instances.
15
- # +createMsg+ returns a +Message+ instance (or one of its subclasses).
16
- # +createStatusOrEventMsg+ returns a +Status+ or +Event+ instance.
17
- class SrcFileParse
18
- def initialize(srcPath, destDirBase, userAliases, userTZ, userTZOffset)
19
- @srcPath = srcPath
20
- # these two are to pass to chatFG in parseFile
21
- @destDirBase = destDirBase
22
- @userAliases = userAliases
23
- @userTZ = userTZ
24
- @userTZOffset = userTZOffset
25
- @tzOffset = getTimeZoneOffset()
26
-
27
- # Used in @lineRegex{,Status}. Only one group: the entire timestamp.
28
- @timestampRegexStr = '\(((?:\d{4}-\d{2}-\d{2} )?\d{1,2}:\d{1,2}:\d{1,2}(?: .{1,2})?)\)'
29
- # the first line is special: it tells us
30
- # 1) who we're talking to
31
- # 2) what time/date
32
- # 3) what SN we used
33
- # 4) what protocol (AIM, icq, jabber...)
34
- @firstLineRegex = /Conversation with (.+?) at (.+?) on (.+?) \((.+?)\)/
35
-
36
- # Possible formats for timestamps:
37
- # "2007-04-17 12:33:13" => %w{2007, 04, 17, 12, 33, 13}
38
- @timeRegexOne = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/
39
- # "4/18/2007 11:02:00 AM" => %w{4, 18, 2007, 11, 02, 00, AM}
40
- @timeRegexTwo = %r{(\d{1,2})/(\d{1,2})/(\d{4}) (\d{1,2}):(\d{2}):(\d{2}) ([AP]M)}
41
- # sometimes a line in a chat doesn't have a full timestamp
42
- # "04:22:05 AM" => %w{04 22 05 AM}
43
- @minimalTimeRegex = /(\d{1,2}):(\d{2}):(\d{2}) ?([AP]M)?/
44
-
45
- # {user,partner}SN set in parseFile() after reading the first line
46
- @userSN = nil
47
- @partnerSN = nil
48
-
49
- # @basicTimeInfo is for files that only have the full timestamp at
50
- # the top; we can use it to fill in the minimal per-line timestamps.
51
- # It has only 3 elements (year, month, dayofmonth) because
52
- # you should be able to fill everything else in.
53
- # If you can't, something's wrong.
54
- @basicTimeInfo = []
55
-
56
- # @userAlias is set each time getSenderByAlias is called. Set an
57
- # initial value just in case the first message doesn't give us an
58
- # alias.
59
- @userAlias = @userAliases[0]
60
-
61
- # @statusMap, @libPurpleEvents, and @events are used in
62
- # createStatusOrEventMessage.
63
- @statusMap = {
64
- /(.+) logged in\.$/ => 'online',
65
- /(.+) logged out\.$/ => 'offline',
66
- /(.+) has signed on\.$/ => 'online',
67
- /(.+) has signed off\.$/ => 'offline',
68
- /(.+) has gone away\.$/ => 'away',
69
- /(.+) is no longer away\.$/ => 'available',
70
- /(.+) has become idle\.$/ => 'idle',
71
- /(.+) is no longer idle\.$/ => 'available'
72
- }
73
-
74
- # libPurpleEvents are all of eventType libPurple
75
- @libPurpleEvents = [
76
- # file transfer
77
- /Starting transfer of .+ from (.+)/,
78
- /^Offering to send .+ to (.+)$/,
79
- /(.+) is offering to send file/,
80
- /^Transfer of file .+ complete$/,
81
- /Error reading|writing|accessing .+: .+/,
82
- /You cancelled the transfer of/,
83
- /File transfer cancelled/,
84
- /(.+) cancelled the transfer of/,
85
- /(.+) cancelled the file transfer/,
86
- # Direct IM - actual (dis)connect events are their own types
87
- /^Attempting to connect to (.+) at .+ for Direct IM\./,
88
- /^Asking (.+) to connect to us at .+ for Direct IM\./,
89
- /^Attempting to connect via proxy server\.$/,
90
- /^Direct IM with (.+) failed/,
91
- # encryption
92
- /Received message encrypted with wrong key/,
93
- /^Requesting key\.\.\.$/,
94
- /^Outgoing message lost\.$/,
95
- /^Conflicting Key Received!$/,
96
- /^Error in decryption- asking for resend\.\.\.$/,
97
- /^Making new key pair\.\.\.$/,
98
- # file transfer - these are in this (non-used) list because you can't get the alias out of matchData[1]
99
- /^You canceled the transfer of .+$/,
100
- # sending errors
101
- /^Last outgoing message not received properly- resetting$/,
102
- /'Resending\.\.\./,
103
- # connection errors
104
- /Lost connection with the remote user:.+/,
105
- # chats
106
- /^.+ entered the room\.$/,
107
- /^.+ left the room\.$/
108
- ]
109
-
110
- # non-libpurple events
111
- # Each key maps to an eventType string. The keys will be matched against a line of chat
112
- # and the partner's alias will be in regex group 1, IF the alias is matched.
113
- @eventMap = {
114
- # .+ is not an alias, it's a proxy server so no grouping
115
- /^Attempting to connect to .+\.$/ => 'direct-im-connect',
116
- # NB: pidgin doesn't track when Direct IM is disconnected, AFAIK
117
- /^Direct IM established$/ => 'directIMConnected',
118
- /Unable to send message. The message is too large./ => 'chat-error',
119
- /You missed .+ messages from (.+) because they were too large./ => 'chat-error'
120
- }
121
- end
122
-
123
- def getTimeZoneOffset()
124
- tzMatch = /([-\+]\d+)[A-Z]{3}\.txt|html?/.match(@srcPath)
125
- tzOffset = tzMatch[1] rescue @userTZOffset
126
- return tzOffset
127
- end
128
-
129
- # Adium time format: YYYY-MM-DD\THH.MM.SS[+-]TZ_HRS like:
130
- # 2008-10-05T22.26.20-0800
131
- def createAdiumTime(time)
132
- # parsedDate = [year, month, day, hour, min, sec]
133
- parsedDate = case time
134
- when @timeRegexOne
135
- [$~[1].to_i, # year
136
- $~[2].to_i, # month
137
- $~[3].to_i, # day
138
- $~[4].to_i, # hour
139
- $~[5].to_i, # minute
140
- $~[6].to_i] # seconds
141
- when @timeRegexTwo
142
- hours = $~[4].to_i
143
- if $~[7] == 'PM' and hours != 12
144
- hours += 12
145
- end
146
- [$~[3].to_i, # year
147
- $~[1].to_i, # month
148
- $~[2].to_i, # day
149
- hours,
150
- $~[5].to_i, # minutes
151
- $~[6].to_i] # seconds
152
- when @minimalTimeRegex
153
- # "04:22:05" => %w{04 22 05}
154
- hours = $~[1].to_i
155
- if $~[4] == 'PM' and hours != 12
156
- hours += 12
157
- end
158
- @basicTimeInfo + # [year, month, day]
159
- [hours,
160
- $~[2].to_i, # minutes
161
- $~[3].to_i] # seconds
162
- else
163
- Pidgin2Adium.logMsg("You have found an odd timestamp.", true)
164
- Pidgin2Adium.logMsg("Please report it to the developer.")
165
- Pidgin2Adium.logMsg("The timestamp: #{time}")
166
- Pidgin2Adium.logMsg("Continuing...")
167
-
168
- ParseDate.parsedate(time)
169
- end
170
- return Time.local(*parsedDate).strftime("%Y-%m-%dT%H.%M.%S#{@tzOffset}")
171
- end
172
-
173
- # parseFile slurps up @srcPath into one big string and runs
174
- # SrcHtmlFileParse.cleanup if it's an HTML file.
175
- # It then uses regexes to break up the string, uses create(Status)Msg
176
- # to turn the regex MatchData into data hashes, and feeds it to
177
- # ChatFileGenerator, which creates the XML data string.
178
- # This method returns a ChatFileGenerator object.
179
- def parseFile()
180
- file = File.new(@srcPath, 'r')
181
- # Deal with first line.
182
- firstLine = file.readline()
183
- firstLineMatch = @firstLineRegex.match(firstLine)
184
- if firstLineMatch.nil?
185
- file.close()
186
- Pidgin2Adium.logMsg("Parsing of #{@srcPath} failed (could not find valid first line).", true)
187
- return false
188
- else
189
- # one big string, without the first line
190
- if self.class == SrcHtmlFileParse
191
- fileContent = self.cleanup(file.read())
192
- else
193
- fileContent = file.read()
194
- end
195
- file.close()
196
- end
197
-
198
- service = firstLineMatch[4]
199
- # userSN is standardized to avoid "AIM.name" and "AIM.na me" folders
200
- @userSN = firstLineMatch[3].downcase.gsub(' ', '')
201
- @partnerSN = firstLineMatch[1]
202
- pidginChatTimeStart = firstLineMatch[2]
203
- @basicTimeInfo = case firstLine
204
- when @timeRegexOne: [$1.to_i, $2.to_i, $3.to_i]
205
- when @timeRegexTwo: [$3.to_i, $1.to_i, $2.to_i]
206
- end
207
-
208
- chatFG = ChatFileGenerator.new(service,
209
- @userSN,
210
- @partnerSN,
211
- createAdiumTime(pidginChatTimeStart),
212
- @destDirBase)
213
- fileContent.each_line do |line|
214
- case line
215
- when @lineRegex
216
- chatFG.appendLine( createMsg($~.captures) )
217
- when @lineRegexStatus
218
- msg = createStatusOrEventMsg($~.captures)
219
- # msg is nil if we couldn't parse the status line
220
- chatFG.appendLine(msg) unless msg.nil?
221
- end
222
- end
223
- return chatFG
224
- end
225
-
226
- def getSenderByAlias(aliasName)
227
- if @userAliases.include? aliasName.downcase.sub(/^\*{3}/,'').gsub(/\s+/, '')
228
- # Set the current alias being used of the ones in @userAliases
229
- @userAlias = aliasName.sub(/^\*{3}/, '')
230
- return @userSN
231
- else
232
- return @partnerSN
233
- end
234
- end
235
-
236
- # createMsg takes an array of captures from matching against @lineRegex
237
- # and returns a Message object or one of its subclasses.
238
- # It can be used for SrcTxtFileParse and SrcHtmlFileParse because
239
- # both of them return data in the same indexes in the matches array.
240
- def createMsg(matches)
241
- msg = nil
242
- # Either a regular message line or an auto-reply/away message.
243
- time = createAdiumTime(matches[0])
244
- aliasStr = matches[1]
245
- sender = getSenderByAlias(aliasStr)
246
- body = matches[3]
247
- if matches[2] # auto-reply
248
- msg = AutoReplyMessage.new(sender, time, aliasStr, body)
249
- else
250
- # normal message
251
- msg = XMLMessage.new(sender, time, aliasStr, body)
252
- end
253
- return msg
254
- end
255
-
256
- # createStatusOrEventMsg takes an array of +MatchData+ captures from
257
- # matching against @lineRegexStatus and returns an Event or Status.
258
- def createStatusOrEventMsg(matches)
259
- # ["22:58:00", "BuddyName logged in."]
260
- # 0: time
261
- # 1: status message or event
262
- msg = nil
263
- time = createAdiumTime(matches[0])
264
- str = matches[1]
265
- regex, status = @statusMap.detect{|regex, status| str =~ regex}
266
- if regex and status
267
- # Status message
268
- aliasStr = regex.match(str)[1]
269
- sender = getSenderByAlias(aliasStr)
270
- msg = StatusMessage.new(sender, time, aliasStr, status)
271
- else
272
- # Test for event
273
- regex = @libPurpleEvents.detect{|regex| str =~ regex }
274
- eventType = 'libpurpleEvent' if regex
275
- unless regex and eventType
276
- # not a libpurple event, try others
277
- regexAndEventType = @eventMap.detect{|regex,eventType| str =~ regex}
278
- if regexAndEventType.nil?
279
- Pidgin2Adium.logMsg("You have found an odd status line. Please send this line to the developer.", true)
280
- Pidgin2Adium.logMsg("The line is: #{str}", true)
281
- return nil
282
- else
283
- regex = regexAndEventType[0]
284
- eventType = regexAndEventType[1]
285
- end
286
- end
287
- if regex and eventType
288
- regexMatches = regex.match(str)
289
- # Event message
290
- if regexMatches.size == 1
291
- # No alias - this means it's the user
292
- aliasStr = @userAlias
293
- sender = @userSN
294
- else
295
- aliasStr = regex.match(str)[1]
296
- sender = getSenderByAlias(aliasStr)
297
- end
298
- msg = Event.new(sender, time, aliasStr, str, eventType)
299
- end
300
- end
301
- return msg
302
- end
303
- end
304
-
305
- class SrcTxtFileParse < SrcFileParse
306
- def initialize(srcPath, destDirBase, userAliases, userTZ, userTZOffset)
307
- super(srcPath, destDirBase, userAliases, userTZ, userTZOffset)
308
- # @lineRegex matches a line in a TXT log file other than the first
309
- # @lineRegex matchdata:
310
- # 0: timestamp
311
- # 1: screen name or alias, if alias set
312
- # 2: "<AUTO-REPLY>" or nil
313
- # 3: message body
314
- @lineRegex = /#{@timestampRegexStr} (.*?) ?(<AUTO-REPLY>)?: (.*)$/o
315
- # @lineRegexStatus matches a status line
316
- # @lineRegexStatus matchdata:
317
- # 0: timestamp
318
- # 1: status message
319
- @lineRegexStatus = /#{@timestampRegexStr} ([^:]+?)[\r\n]/o
320
- end
321
-
322
- end
323
-
324
- class SrcHtmlFileParse < SrcFileParse
325
- def initialize(srcPath, destDirBase, userAliases, userTZ, userTZOffset)
326
- super(srcPath, destDirBase, userAliases, userTZ, userTZOffset)
327
- # @lineRegex matches a line in an HTML log file other than the first
328
- # time matches on either "2008-11-17 14:12" or "14:12"
329
- # @lineRegex match obj:
330
- # 0: timestamp, extended or not
331
- # 1: screen name or alias, if alias set
332
- # 2: "&lt;AUTO-REPLY&gt;" or nil
333
- # 3: message body
334
- # <span style='color: #000000;'>test sms</span>
335
- @lineRegex = /#{@timestampRegexStr} ?<b>(.*?) ?(&lt;AUTO-REPLY&gt;)?:?<\/b> ?(.*)<br ?\/>/o
336
- # @lineRegexStatus matches a status line
337
- # @lineRegexStatus match obj:
338
- # 0: timestamp
339
- # 1: status message
340
- @lineRegexStatus = /#{@timestampRegexStr} ?<b> (.*?)<\/b><br ?\/>/o
341
- end
342
-
343
- # Removes <font> tags, empty <a>s, and spans with either no color
344
- # information or color information that just turns the text black.
345
- # Returns a string.
346
- def cleanup(text)
347
- # Pidgin and Adium both show bold using
348
- # <span style="font-weight: bold;"> except Pidgin uses single quotes
349
- # and Adium uses double quotes
350
- text.gsub!(/<\/?(html|body|font).*?>/, '')
351
- # These empty links are sometimes appended to every line in a chat,
352
- # for some weird reason. Remove them.
353
- text.gsub!(%r{<a href='.+?'>\s*?</a>}, '')
354
- text.gsub!(%r{(.*?)<span.+style='(.+?)'>(.*?)</span>(.*)}) do |s|
355
- # before = text before match
356
- # style = style declaration
357
- # innertext = text inside <span>
358
- # after = text after match
359
- before, style, innertext, after = *($~[1..4])
360
- # TODO: remove after from string then see what balanceTags does
361
- # Remove empty spans.
362
- nil if innertext == ''
363
- # Only allow some style declarations
364
- # We keep:
365
- # font-weight: bold
366
- # color (except #000000)
367
- # text-decoration: underline
368
- styleparts = style.split(/; ?/)
369
- styleparts.map! do |p|
370
- # Short-circuit for common declaration
371
- # Yes, sometimes there's a ">" before the ";".
372
- if p == 'color: #000000;' or p == 'color: #000000>;'
373
- nil
374
- else
375
- case p
376
- when /font-family/: nil
377
- when /font-size/: nil
378
- when /background/: nil
379
- end
380
- end
381
- end
382
- styleparts.compact!
383
- if styleparts.empty?
384
- style = ''
385
- elsif styleparts.size == 1
386
- style = styleparts[0] << ';'
387
- else
388
- style = styleparts.join('; ') << ';'
389
- end
390
- if style != ''
391
- innertext = "<span style=\"#{style}\">#{innertext}</span>"
392
- end
393
- before + innertext + after
394
- end
395
- # Pidgin uses <em>, Adium uses <span>
396
- if text.gsub!('<em>', '<span style="italic">')
397
- text.gsub!('</em>', '</span>')
398
- end
399
- return text
400
- end
401
- end
402
-
403
- # A holding object for each line of the chat.
404
- # It is subclassed as appropriate (eg AutoReplyMessage).
405
- # All Messages have senders, times, and aliases.
406
- class Message
407
- def initialize(sender, time, aliasStr)
408
- @sender = sender
409
- @time = time
410
- @aliasStr = aliasStr
411
- end
412
- end
413
-
414
- # Basic message with body text (as opposed to pure status messages, which
415
- # have no body).
416
- class XMLMessage < Message
417
- def initialize(sender, time, aliasStr, body)
418
- super(sender, time, aliasStr)
419
- @body = body
420
- normalizeBody!()
421
- end
422
-
423
- def getOutput
424
- return sprintf('<message sender="%s" time="%s" alias="%s">%s</message>' << "\n",
425
- @sender, @time, @aliasStr, @body)
426
- end
427
-
428
- def normalizeBody!
429
- normalizeBodyEntities!()
430
- # Fix mismatched tags. Yes, it's faster to do it per-message
431
- # than all at once.
432
- @body = Pidgin2Adium.balanceTags(@body)
433
- if @aliasStr[0,3] == '***'
434
- # "***<alias>" is what pidgin sets as the alias for a /me action
435
- @aliasStr.slice!(0,3)
436
- @body = '*' << @body << '*'
437
- end
438
- @body = '<div><span style="font-family: Helvetica; font-size: 12pt;">' <<
439
- @body <<
440
- '</span></div>'
441
- end
442
-
443
- def normalizeBodyEntities!
444
- # Convert '&' to '&amp;' only if it's not followed by an entity.
445
- @body.gsub!(/&(?!lt|gt|amp|quot|apos)/, '&amp;')
446
- # replace single quotes with '&apos;' but only outside <span>s.
447
- @body.gsub!(/(.*?)(<span.*?>.*?<\/span>)(.*?)/) do
448
- before, span, after = $1, ($2||''), $3||''
449
- before.gsub("'", '&aquot;') <<
450
- span <<
451
- after.gsub("'", '&aquot;')
452
- end
453
- end
454
- end
455
-
456
- # An auto reply message, meaning it has a body.
457
- class AutoReplyMessage < XMLMessage
458
- def getOutput
459
- return sprintf('<message sender="%s" time="%s" auto="true" alias="%s">%s</message>' << "\n", @sender, @time, @aliasStr, @body)
460
- end
461
- end
462
-
463
- # A message saying e.g. "Blahblah has gone away."
464
- class StatusMessage < Message
465
- def initialize(sender, time, aliasStr, status)
466
- super(sender, time, aliasStr)
467
- @status = status
468
- end
469
- def getOutput
470
- return sprintf('<status type="%s" sender="%s" time="%s" alias="%s"/>' << "\n", @status, @sender, @time, @aliasStr)
471
- end
472
- end
473
-
474
- # An <event> line of the chat
475
- class Event < XMLMessage
476
- def initialize(sender, time, aliasStr, body, type="libpurpleMessage")
477
- super(sender, time, aliasStr, body)
478
- @type = type
479
- end
480
-
481
- def getOutput
482
- return sprintf('<event type="%s" sender="%s" time="%s" alias="%s">%s</event>', @type, @sender, @time, @aliasStr, @body)
483
- end
484
- end
485
- end # end module