pidgin2adium 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +10 -0
- data/README.rdoc +106 -0
- data/Rakefile.rb +26 -0
- data/bin/pidgin2adium +72 -0
- data/lib/pidgin2adium.rb +120 -0
- data/lib/pidgin2adium/{balance-tags.rb → balance_tags.rb} +32 -29
- data/lib/pidgin2adium/log_converter.rb +68 -0
- data/lib/pidgin2adium/log_file.rb +101 -0
- data/lib/pidgin2adium/log_parser.rb +590 -0
- metadata +39 -19
- data/bin/pidgin2adium_logs +0 -67
- data/bin/pidgin2adium_status +0 -15
- data/lib/pidgin2adium/ChatFileGenerator.rb +0 -59
- data/lib/pidgin2adium/SrcFileParse.rb +0 -485
- data/lib/pidgin2adium/logs.rb +0 -250
- data/lib/pidgin2adium/status.rb +0 -113
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:
|
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-
|
12
|
+
date: 2009-11-24 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
-
|
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
|
-
-
|
27
|
-
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
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://
|
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:
|
78
|
+
summary: Pidgin2Adium is a fast, easy way to convert Pidgin (formerly gaim) logs to the Adium format
|
59
79
|
test_files: []
|
60
80
|
|
data/bin/pidgin2adium_logs
DELETED
@@ -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
|
data/bin/pidgin2adium_status
DELETED
@@ -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: "<AUTO-REPLY>" or nil
|
333
|
-
# 3: message body
|
334
|
-
# <span style='color: #000000;'>test sms</span>
|
335
|
-
@lineRegex = /#{@timestampRegexStr} ?<b>(.*?) ?(<AUTO-REPLY>)?:?<\/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 '&' only if it's not followed by an entity.
|
445
|
-
@body.gsub!(/&(?!lt|gt|amp|quot|apos)/, '&')
|
446
|
-
# replace single quotes with ''' 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
|