pidgin2adium 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|