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
data/lib/pidgin2adium/logs.rb
DELETED
@@ -1,250 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby -w
|
2
|
-
|
3
|
-
#Author: Gabe Berke-Williams, 2008
|
4
|
-
#With thanks to Li Ma, whose blog post at
|
5
|
-
#http://li-ma.blogspot.com/2008/10/pidgin-log-file-to-adium-log-converter.html
|
6
|
-
#helped tremendously.
|
7
|
-
#
|
8
|
-
#A ruby program to convert Pidgin log files to Adium log files, then place
|
9
|
-
#them in the Adium log directory with allowances for time zone differences.
|
10
|
-
|
11
|
-
require 'pidgin2adium/SrcFileParse'
|
12
|
-
require 'pidgin2adium/ChatFileGenerator'
|
13
|
-
require 'fileutils'
|
14
|
-
|
15
|
-
class Time
|
16
|
-
ZoneOffset = {
|
17
|
-
'UTC' => 0,
|
18
|
-
# ISO 8601
|
19
|
-
'Z' => 0,
|
20
|
-
# RFC 822
|
21
|
-
'UT' => 0, 'GMT' => 0,
|
22
|
-
'EST' => -5, 'EDT' => -4,
|
23
|
-
'CST' => -6, 'CDT' => -5,
|
24
|
-
'MST' => -7, 'MDT' => -6,
|
25
|
-
'PST' => -8, 'PDT' => -7,
|
26
|
-
# Following definition of military zones is original one.
|
27
|
-
# See RFC 1123 and RFC 2822 for the error in RFC 822.
|
28
|
-
'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4, 'E' => +5, 'F' => +6,
|
29
|
-
'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10, 'L' => +11, 'M' => +12,
|
30
|
-
'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4, 'R' => -5, 'S' => -6,
|
31
|
-
'T' => -7, 'U' => -8, 'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12
|
32
|
-
}
|
33
|
-
# Returns offset in hours, e.g. '+0900'
|
34
|
-
def Time.zone_offset(zone, year=Time.now.year)
|
35
|
-
off = nil
|
36
|
-
zone = zone.upcase
|
37
|
-
if /\A([+-])(\d\d):?(\d\d)\z/ =~ zone
|
38
|
-
off = ($1 == '-' ? -1 : 1) * ($2.to_i * 60 + $3.to_i) * 60
|
39
|
-
elsif /\A[+-]\d\d\z/ =~ zone
|
40
|
-
off = zone.to_i
|
41
|
-
elsif ZoneOffset.include?(zone)
|
42
|
-
off = ZoneOffset[zone]
|
43
|
-
elsif ((t = Time.local(year, 1, 1)).zone.upcase == zone rescue false)
|
44
|
-
off = t.utc_offset / 3600
|
45
|
-
elsif ((t = Time.local(year, 7, 1)).zone.upcase == zone rescue false)
|
46
|
-
off = t.utc_offset / 3600
|
47
|
-
end
|
48
|
-
off
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
module Pidgin2Adium
|
53
|
-
# put's content. Also put's to @LOG_FILE_FH if @debug == true.
|
54
|
-
def Pidgin2Adium.logMsg(str, isError=false)
|
55
|
-
content = str.to_s
|
56
|
-
if isError == true
|
57
|
-
content= "ERROR: #{str}"
|
58
|
-
end
|
59
|
-
puts content
|
60
|
-
end
|
61
|
-
|
62
|
-
class Logs
|
63
|
-
# FILE_EXISTS is returned by ChatFileGenerator.buildDomAndOutput() if the output logfile already exists.
|
64
|
-
FILE_EXISTS = 42
|
65
|
-
def initialize(src, out, aliases, libdir, tz=nil, debug=false)
|
66
|
-
# These files/directories show up in Dir.entries(x)
|
67
|
-
@BAD_DIRS = %w{. .. .DS_Store Thumbs.db .system}
|
68
|
-
@src_dir = File.expand_path(src)
|
69
|
-
@out_dir = File.expand_path(out)
|
70
|
-
# Whitespace is removed for easy matching later on.
|
71
|
-
@my_aliases = aliases.map{|x| x.downcase.gsub(/\s+/,'') }.uniq
|
72
|
-
# @libdir is the directory in
|
73
|
-
# ~/Library/Application Support/Adium 2.0/Users/Default/Logs/.
|
74
|
-
# For AIM, it's like "AIM.<screenname>"
|
75
|
-
# FIXME: don't make the user pass in libdir - we can and SHOULD change it on a per-service/screenname basis
|
76
|
-
@libdir = libdir
|
77
|
-
@DEFAULT_TIME_ZONE = tz || Time.now.zone
|
78
|
-
@debug = debug
|
79
|
-
unless File.directory?(@src_dir)
|
80
|
-
puts "Source directory #{@src_dir} does not exist or is not a directory."
|
81
|
-
raise Errno::ENOENT
|
82
|
-
end
|
83
|
-
unless File.directory?(@out_dir)
|
84
|
-
begin
|
85
|
-
FileUtils.mkdir_p(@out_dir)
|
86
|
-
rescue
|
87
|
-
puts "Output directory #{@out_dir} does not exist or is not a directory and could not be created."
|
88
|
-
raise Errno::ENOENT
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# local offset, like "-0800" or "+1000"
|
93
|
-
@DEFAULT_TZ_OFFSET = '%+03d00'%Time.zone_offset(@DEFAULT_TIME_ZONE)
|
94
|
-
end
|
95
|
-
|
96
|
-
def start
|
97
|
-
Pidgin2Adium.logMsg "Begin converting."
|
98
|
-
begin
|
99
|
-
filesPath = getAllChatFiles(@src_dir)
|
100
|
-
rescue Errno::EACCES => bang
|
101
|
-
Pidgin2Adium.logMsg("Sorry, permission denied for getting Pidgin chat files from #{@src_dir}.", true)
|
102
|
-
Pidgin2Adium.logMsg("Details: #{bang.message}", true)
|
103
|
-
raise Errno::EACCES
|
104
|
-
end
|
105
|
-
|
106
|
-
Pidgin2Adium.logMsg("#{filesPath.length} files to convert.")
|
107
|
-
totalFiles = filesPath.size
|
108
|
-
filesPath.each_with_index do |fname, i|
|
109
|
-
Pidgin2Adium.logMsg(
|
110
|
-
sprintf("[%d/%d] Converting %s...",
|
111
|
-
(i+1), totalFiles, fname)
|
112
|
-
)
|
113
|
-
convert(fname)
|
114
|
-
end
|
115
|
-
|
116
|
-
copyLogs()
|
117
|
-
deleteSearchIndexes()
|
118
|
-
|
119
|
-
Pidgin2Adium.logMsg "Finished converting! Converted #{filesPath.length} files."
|
120
|
-
end
|
121
|
-
|
122
|
-
|
123
|
-
# Problem: imported logs are viewable in the Chat Transcript Viewer, but are not indexed,
|
124
|
-
# so a search of the logs doesn't give results from the imported logs.
|
125
|
-
# To fix this, we delete the cached log indexes, which forces Adium to re-index.
|
126
|
-
def deleteSearchIndexes()
|
127
|
-
Pidgin2Adium.logMsg "Deleting log search indexes in order to force re-indexing of imported logs..."
|
128
|
-
dirtyFile=File.expand_path("~/Library/Caches/Adium/Default/DirtyLogs.plist")
|
129
|
-
logIndexFile=File.expand_path("~/Library/Caches/Adium/Default/Logs.index")
|
130
|
-
[dirtyFile, logIndexFile].each do |f|
|
131
|
-
if File.exist?(f)
|
132
|
-
if File.writable?(f)
|
133
|
-
File.delete(f)
|
134
|
-
else
|
135
|
-
Pidgin2Adium.logMsg("#{f} exists but is not writable. Please delete it yourself.", true)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
Pidgin2Adium.logMsg "...done."
|
140
|
-
Pidgin2Adium.logMsg "When you next start the Adium Chat Transcript Viewer, it will re-index the logs, which may take a while."
|
141
|
-
end
|
142
|
-
|
143
|
-
# <tt>convert</tt> creates a new SrcHtmlFileParse or SrcTxtFileParse object,
|
144
|
-
# as appropriate, and calls its parse() method.
|
145
|
-
# Returns false if there was a problem, true otherwise
|
146
|
-
def convert(srcPath)
|
147
|
-
ext = File.extname(srcPath).sub('.', '').downcase
|
148
|
-
if(ext == "html" || ext == "htm")
|
149
|
-
srcFileParse = SrcHtmlFileParse.new(srcPath, @out_dir, @my_aliases, @DEFAULT_TIME_ZONE, @DEFAULT_TZ_OFFSET)
|
150
|
-
elsif(ext == "txt")
|
151
|
-
srcFileParse = SrcTxtFileParse.new(srcPath, @out_dir, @my_aliases, @DEFAULT_TIME_ZONE, @DEFAULT_TZ_OFFSET)
|
152
|
-
elsif(ext == "chatlog")
|
153
|
-
# chatlog FILE, not directory
|
154
|
-
Pidgin2Adium.logMsg("Found chatlog FILE - moving to chatlog DIRECTORY.")
|
155
|
-
# Create out_dir/log.chatlog/
|
156
|
-
begin
|
157
|
-
toCreate = "#{@out_dir}/#{srcPath}"
|
158
|
-
Dir.mkdir(toCreate)
|
159
|
-
rescue => bang
|
160
|
-
Pidgin2Adium.logMsg("Could not create #{toCreate}: #{bang.class} #{bang.message}", true)
|
161
|
-
return false
|
162
|
-
end
|
163
|
-
fileWithXmlExt = srcPath[0, srcPath.size-File.extname(srcPath).size] << ".xml"
|
164
|
-
# @src_dir/log.chatlog (file) -> @out_dir/log.chatlog/log.xml
|
165
|
-
File.cp(srcPath, File.join(@out_dir, srcPath, fileWithXmlExt))
|
166
|
-
Pidgin2Adium.logMsg("Copied #{srcPath} to " << File.join(@out_dir, srcPath, fileWithXmlExt))
|
167
|
-
return true
|
168
|
-
else
|
169
|
-
Pidgin2Adium.logMsg("srcPath (#{srcPath}) is not a txt, html, or chatlog file. Doing nothing.")
|
170
|
-
return false
|
171
|
-
end
|
172
|
-
|
173
|
-
chatFG = srcFileParse.parseFile()
|
174
|
-
return false if chatFG == false
|
175
|
-
|
176
|
-
destFilePath = chatFG.convert()
|
177
|
-
return \
|
178
|
-
case destFilePath
|
179
|
-
when false
|
180
|
-
Pidgin2Adium.logMsg("Converting #{srcPath} failed.", true);
|
181
|
-
false
|
182
|
-
when FILE_EXISTS
|
183
|
-
Pidgin2Adium.logMsg("File already exists.")
|
184
|
-
true
|
185
|
-
else
|
186
|
-
Pidgin2Adium.logMsg("Output to: #{destFilePath}")
|
187
|
-
true
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def getAllChatFiles(dir)
|
192
|
-
return [] if File.basename(dir) == ".system"
|
193
|
-
# recurse into each subdir
|
194
|
-
return (Dir.glob(File.join(@src_dir, '**', '*.{htm,html,txt}')) - @BAD_DIRS)
|
195
|
-
end
|
196
|
-
|
197
|
-
# Copies logs, accounting for timezone changes
|
198
|
-
def copyLogs
|
199
|
-
Pidgin2Adium.logMsg "Copying logs with accounting for different time zones..."
|
200
|
-
# FIXME: not all logs are AIM logs, libdir may change
|
201
|
-
realSrcDir = File.expand_path('~/Library/Application Support/Adium 2.0/Users/Default/Logs/') << "/#{@libdir}/"
|
202
|
-
realDestDir = File.join(@out_dir, @libdir) << '/'
|
203
|
-
|
204
|
-
src_entries = Dir.entries(realSrcDir)
|
205
|
-
dest_entries = Dir.entries(realDestDir)
|
206
|
-
both_entries = (src_entries & dest_entries) - @BAD_DIRS
|
207
|
-
|
208
|
-
both_entries.each do |name|
|
209
|
-
my_src_entries = Dir.entries(realSrcDir << name) - @BAD_DIRS
|
210
|
-
my_dest_entries = Dir.entries(realDestDir << name) - @BAD_DIRS
|
211
|
-
|
212
|
-
in_both = my_src_entries & my_dest_entries
|
213
|
-
in_both.each do |logdir|
|
214
|
-
FileUtils.cp(
|
215
|
-
File.join(realSrcDir, name, logdir, logdir.sub('chatlog', 'xml')),
|
216
|
-
File.join(realDestDir, name, logdir) << '/',
|
217
|
-
:verbose => false)
|
218
|
-
end
|
219
|
-
# The logs that are only in one of the dirs are not necessarily
|
220
|
-
# different logs than the dest. They might just have different
|
221
|
-
# timestamps. Thus, we use regexes.
|
222
|
-
only_in_src = my_src_entries - in_both
|
223
|
-
only_in_dest = my_dest_entries - in_both
|
224
|
-
# Move files from realSrcDir that are actually in both, but
|
225
|
-
# just have different time zones.
|
226
|
-
only_in_src.each do |srcLogDir|
|
227
|
-
# Match on everything except the timezone ("-0400.chatlog")
|
228
|
-
fileBeginRegex = Regexp.new('^'<<Regexp.escape(srcLogDir.sub(/-\d{4}.\.chatlog$/, '')) )
|
229
|
-
targetChatlogDir = only_in_dest.find{|d| d =~ fileBeginRegex}
|
230
|
-
if targetChatlogDir.nil?
|
231
|
-
# Only in source, so we can copy it without fear of
|
232
|
-
# overwriting.
|
233
|
-
targetChatlogDir = srcLogDir
|
234
|
-
FileUtils.mkdir_p(File.join(realDestDir, name, targetChatlogDir))
|
235
|
-
end
|
236
|
-
# Move to targetChatlogDir so we overwrite the destination
|
237
|
-
# file but still use its timestamp
|
238
|
-
# (if it exists; if it doesn't, then we're using our own
|
239
|
-
# timestamp).
|
240
|
-
FileUtils.cp(
|
241
|
-
File.join(realSrcDir, name, srcLogDir, srcLogDir.sub('chatlog', 'xml')),
|
242
|
-
File.join(realDestDir, name, targetChatlogDir, targetChatlogDir.sub('chatlog', 'xml')),
|
243
|
-
:verbose => false
|
244
|
-
)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
Pidgin2Adium.logMsg "Log files copied!"
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
data/lib/pidgin2adium/status.rb
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
# Author: Gabe Berke-Williams 2008-11-25
|
4
|
-
# Requires rubygems and hpricot (http://wiki.github.com/why/hpricot)
|
5
|
-
|
6
|
-
# Script to import pidgin logs into Adium. It uses Applescript to create new statuses in Adium.
|
7
|
-
# Stupid binary status file format. Thanks a lot, Adium.
|
8
|
-
# It doesn't work in Mac OSX 10.5 (Leopard).
|
9
|
-
# See: http://trac.adiumx.com/ticket/8863
|
10
|
-
# It should work in Mac OSX 10.4 (Tiger), but is untested.
|
11
|
-
#
|
12
|
-
# TODO: check adium version in
|
13
|
-
# /Applications/Adium.app/Contents
|
14
|
-
# with this:
|
15
|
-
# <key>CFBundleShortVersionString</key>
|
16
|
-
# <string>1.3.4</string>
|
17
|
-
# For Mac 10.5+, needs to be 1.4; should work for 10.4 with 1.3.x
|
18
|
-
|
19
|
-
require 'rubygems'
|
20
|
-
require 'hpricot'
|
21
|
-
|
22
|
-
module Pidgin2Adium
|
23
|
-
class Status
|
24
|
-
def initialize(xml_file)
|
25
|
-
@xml_file = File.expand_path(xml_file)
|
26
|
-
#xml_file=File.expand_path("~/Desktop/purple/status.xml")
|
27
|
-
# Unescape for Adium.
|
28
|
-
@TRANSLATIONS = {
|
29
|
-
'&' => '&',
|
30
|
-
'<' => '<',
|
31
|
-
'>' => '>',
|
32
|
-
# escape quotes for shell quoting in tell -e 'blah' below
|
33
|
-
'"' => '\"',
|
34
|
-
''' => "\\'",
|
35
|
-
"<br>" => "\n"
|
36
|
-
}
|
37
|
-
end
|
38
|
-
|
39
|
-
def start
|
40
|
-
# For some reason Hpricot doesn't like entities in attributes,
|
41
|
-
# but since that only affects the status name, which only we see,
|
42
|
-
# that's not really a problem.
|
43
|
-
doc = Hpricot( File.read(xml_file) )
|
44
|
-
$max_id = get_max_status_id
|
45
|
-
# remove <substatus>'s because sometimes their message is different
|
46
|
-
# from the actual message, and we don't want to grab them accidentally
|
47
|
-
doc.search('substatus').remove
|
48
|
-
|
49
|
-
doc.search('status').each do |status|
|
50
|
-
next if status.search('message').empty?
|
51
|
-
add_status_to_adium(status)
|
52
|
-
end
|
53
|
-
|
54
|
-
puts "All statuses have been migrated. Enjoy!"
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
def unescape(str)
|
59
|
-
unescaped_str = str.clone
|
60
|
-
# Unescape the escaped entities in Pidgin's XML.
|
61
|
-
# translate "&" first because sometimes the entities are
|
62
|
-
# like "&gt;"
|
63
|
-
unescaped_str.gsub!('&', '&')
|
64
|
-
TRANSLATIONS.each do |k,v|
|
65
|
-
unescaped_str.gsub!(k, v)
|
66
|
-
end
|
67
|
-
return unescaped_str
|
68
|
-
end
|
69
|
-
|
70
|
-
def get_max_status_id
|
71
|
-
# osascript line returns like so: "-1000, -8000, -1001, 24, -1002\n"
|
72
|
-
# Turn the single String into an array of Fixnums.
|
73
|
-
script = `osascript -e 'tell application "Adium" to get id of every status'`
|
74
|
-
id = script.split(',').map{ |x| x.to_i }.max
|
75
|
-
return id
|
76
|
-
end
|
77
|
-
|
78
|
-
def add_status_to_adium(elem)
|
79
|
-
# pass in <status> element
|
80
|
-
id = ($max_id += 1)
|
81
|
-
# status_type is invisible/available/away
|
82
|
-
status_type = elem.search('state').inner_html
|
83
|
-
title = unescape( elem[:name] )
|
84
|
-
status_message = unescape( elem.search(:message).inner_html )
|
85
|
-
puts '-' * 80
|
86
|
-
puts "status_type: #{status_type}"
|
87
|
-
puts "title: #{title}"
|
88
|
-
puts "status_message: #{status_message}"
|
89
|
-
puts '-' * 80
|
90
|
-
# TODO: when it actually works, remove this line
|
91
|
-
command="osascript -e 'tell application \"Adium\" to set myStat to (make new status with properties {id:#{id}, saved:true, status type:#{status_type}, title:\"#{title}\", message:\"#{status_message}\", autoreply:\"#{status_message}\"})'"
|
92
|
-
# TODO: popen[123]?
|
93
|
-
p `#{command}`
|
94
|
-
if $? != 0
|
95
|
-
puts "*" * 80
|
96
|
-
puts "command: #{command}"
|
97
|
-
puts "Uh-oh. Something went wrong."
|
98
|
-
puts "The command that failed is above."
|
99
|
-
# given 10.x.y, to_f leaves off y
|
100
|
-
if `sw_vers -productVersion`.to_f == 10.5
|
101
|
-
puts "You are running Mac OS X 10.5 (Leopard)."
|
102
|
-
puts "This script does not work for that version."
|
103
|
-
puts "It should work for Mac OS X 10.4 (Tiger),"
|
104
|
-
puts "but is untested."
|
105
|
-
puts "See: http://trac.adiumx.com/ticket/8863"
|
106
|
-
end
|
107
|
-
puts "Return status: #{$?}"
|
108
|
-
puts "Error, exiting."
|
109
|
-
raise "You need Mac OS X Tiger (10.4)"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|