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.
@@ -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