pidgin2adium 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- '&amp;' => '&',
30
- '&lt;' => '<',
31
- '&gt;' => '>',
32
- # escape quotes for shell quoting in tell -e 'blah' below
33
- '&quot;' => '\"',
34
- '&apos;' => "\\'",
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 "&amp;" first because sometimes the entities are
62
- # like "&amp;gt;"
63
- unescaped_str.gsub!('&amp;', '&')
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