runoff 0.1.1
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.
- checksums.yaml +15 -0
- checksums.yaml.gz.sig +3 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +43 -0
- data/Rakefile +8 -0
- data/bin/runoff +7 -0
- data/gem-public_cert.pem +22 -0
- data/lib/runoff/composition.rb +161 -0
- data/lib/runoff/location.rb +58 -0
- data/lib/runoff/source.rb +149 -0
- data/lib/runoff/version.rb +3 -0
- data/lib/runoff.rb +4 -0
- data/runoff.gemspec +25 -0
- data/test/runoff_composition_test.rb +117 -0
- data/test/runoff_locations_test.rb +31 -0
- data/test/runoff_source_test.rb +13 -0
- data/test/test_db.sqlite +0 -0
- data.tar.gz.sig +2 -0
- metadata +140 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZjY2YzY5MjRhYzFkZTU4NDlhMjQyMzk0MzgxOTE2YzBiZThjNWMxZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDg2M2Q5NzA4ZTRhNWIzNTA2YmFjNzU0NGVhZmMwNjY5MWZjYmY4OQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Mzc2Nzk4Mjc2ZTFkM2JkYTdhYWJhYzNhZmY4NTU2NDJlNDMzM2I0ZmQxZWY2
|
10
|
+
MzhkNWU4MmMwNWJlZThhMjkwYzljNTFlMGYwNDljNzcyOGViYTVlNzNmMmI2
|
11
|
+
YmJiNTcwMzVhZDYzNmYzNWM4NmVkOWU3ODk3NDVjZTNhZTRhNzY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjIwMTEzY2FiM2ZjMWVjNDQzNDM5ZGVlY2E0ZmE1OTc4OThkYmE4N2NmN2Ew
|
14
|
+
YjYzZmIwZjUzNjMyZjAxZDhkZTRmNmE4NmJmOTFhNTcwZWIyZTM2NGRlZDg2
|
15
|
+
MDc2NWE1YjY2YTRhYWE3NjhlNDkwNDIzMWIxMWRlMmFhNzBiMTY=
|
checksums.yaml.gz.sig
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Aigars Dzerviniks
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# runoff
|
2
|
+
## About
|
3
|
+
|
4
|
+
A few years ago I had enough of loosing my Skype chat history every time I reinstalled the operating system, so I decided to write a small application that could export it as plain text files. The application is called [SDBR](https://github.com/arvislacis/SDBR) and it is an open source project that I do not maintain anymore. Why? I could say that I lost my interest in it, but the real reason probably is the implementation.
|
5
|
+
|
6
|
+
SDBR is written in C# using WPF, therefore it runs only on Windows. Moreover, it is a GUI application. Yeah, that's a problem, because you don't need the GUI for this kind of functionality. runoff is a commandline tool, that automates the process of exporting your chat history.
|
7
|
+
|
8
|
+
## Install
|
9
|
+
|
10
|
+
Sorry, this gem is still in development, therefore I haven't published it to RubyGems yet, but you can build it locally and install it that way.
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
The current version only works with the default Skype location.
|
15
|
+
|
16
|
+
<pre><code>runoff all skype_username # this will save all the files in your home directory
|
17
|
+
</code></pre>
|
18
|
+
|
19
|
+
To export files to a specific directory you can use <code>--to</code> or <code>-t</code> option.
|
20
|
+
|
21
|
+
<pre><code>runoff all skype_username --to ~/skype_backup
|
22
|
+
</code></pre>
|
23
|
+
|
24
|
+
If you're confused, you can get some help.
|
25
|
+
|
26
|
+
<pre><code>runoff help all
|
27
|
+
</code></pre>
|
28
|
+
|
29
|
+
If you don't want to install the development version, clone down the repository and call the executable file directly!
|
30
|
+
|
31
|
+
<pre><code>ruby -Ilib ./bin/runoff.rb
|
32
|
+
</code></pre>
|
33
|
+
|
34
|
+
## What else?
|
35
|
+
|
36
|
+
Things to do before runoff is ready to be published:
|
37
|
+
- Add tests for the executable file.
|
38
|
+
- Use <code>--from</code>/<code>-f</code> option to specify the location of main.db file.
|
39
|
+
- Add comments to the code.
|
40
|
+
|
41
|
+
Things to do in the future versions:
|
42
|
+
- Add additional methods to export only specific conversations.
|
43
|
+
- Append only new messages to the previously genetrated files instead of appending everything.
|
data/Rakefile
ADDED
data/bin/runoff
ADDED
data/gem-public_cert.pem
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDnjCCAoagAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMRowGAYDVQQDDBFkemVy
|
3
|
+
dmluaWtzLmFpZ2FyczEXMBUGCgmSJomT8ixkARkWB291dGxvb2sxEzARBgoJkiaJ
|
4
|
+
k/IsZAEZFgNjb20wHhcNMTMwMzI1MTczODU4WhcNMTQwMzI1MTczODU4WjBKMRow
|
5
|
+
GAYDVQQDDBFkemVydmluaWtzLmFpZ2FyczEXMBUGCgmSJomT8ixkARkWB291dGxv
|
6
|
+
b2sxEzARBgoJkiaJk/IsZAEZFgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
7
|
+
ggEKAoIBAQC0WariaIzrjdSzAcUM4k7kUfZGk4S1eaHcby97aDutB3FNzyCuFfxU
|
8
|
+
7PvwrGoZKAFTF52w9j+Jx9cWAOo9ZLCsfbJwN4f7kMS8zdXUbtwz//Ex6sZpoodN
|
9
|
+
dCgIC6MroQwK/vc5q1b/teWS4QB4IfpEj6RYb8QaJmZ0IGnvirfXOpVXy7NHIwfT
|
10
|
+
SsDytaBE1siP0gx4a9i5SZN5G3U2vbEna9h278/g9WlJqB7VkqoXN/syA8/UHxue
|
11
|
+
W/KYFT1WgliGIwixl+AIyQp4llpBl9H3nu7dSvndr9UHRdlkzYxFV3Fc3EurpiPM
|
12
|
+
OPSjzI/G8WxIai+UeZciDF1IsYi5tacHAgMBAAGjgY4wgYswCQYDVR0TBAIwADAL
|
13
|
+
BgNVHQ8EBAMCBLAwHQYDVR0OBBYEFHCJfKCutfHDLgf1JKjOV6uGLujLMCgGA1Ud
|
14
|
+
EQQhMB+BHWR6ZXJ2aW5pa3MuYWlnYXJzQG91dGxvb2suY29tMCgGA1UdEgQhMB+B
|
15
|
+
HWR6ZXJ2aW5pa3MuYWlnYXJzQG91dGxvb2suY29tMA0GCSqGSIb3DQEBBQUAA4IB
|
16
|
+
AQCntpOQvox8TVSwwXy1qC2uZvvP55zZ6hxyzzbHdRqwAAOnssfAVAZHiXTfLdQO
|
17
|
+
87gyLbO5bVQkGNkyZzdVMbc59a4H6dKSr6AmcCtb/tf+ZWjGU7oAvRFnYRwhirFL
|
18
|
+
Y8ML3UdD2TgDDAXAOHKUlsq6tKlU1NEsLv1BRBNkLPo25XyESTzJTV3c8fIn6iKr
|
19
|
+
Z/pNzxAd9R7oS8LCfXaLhPxo9vErUVKwt64zdaKbACkjjZnCZoeahlOwgsVsyl0o
|
20
|
+
44ixLmq4u15kH+PTktqOXZurNEZgS2TRXnFDoSZdmOr1236e/R50ssWeGGHQJ1Wl
|
21
|
+
7fYlViA8Ocfwhgrh72sNfTzS
|
22
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Runoff
|
5
|
+
|
6
|
+
# Public: Provides interaction with a Skype database file.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# composition = Composition.new 'path/to/the/main.db'
|
11
|
+
# exported_files_count = composition.export 'export/folder'
|
12
|
+
class Composition
|
13
|
+
|
14
|
+
# Public: Returns a Set object of all the names of the exported files.
|
15
|
+
attr_reader :exported_filenames
|
16
|
+
|
17
|
+
# Public: Initialize a Composition object.
|
18
|
+
#
|
19
|
+
# main_db_file_path - A String with the path to the Skype database file.
|
20
|
+
#
|
21
|
+
# Raises IOError if the file cannot be found
|
22
|
+
def initialize(main_db_file_path)
|
23
|
+
unless File.exists? main_db_file_path
|
24
|
+
raise IOError, "File doesn't exist"
|
25
|
+
end
|
26
|
+
|
27
|
+
skype_database = Sequel.connect "sqlite://#{main_db_file_path}"
|
28
|
+
@messages = skype_database.from :Messages
|
29
|
+
@exported_filenames = Set.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Exports Skype chat history to text files.
|
33
|
+
#
|
34
|
+
# destination_path - A String with folder path, where to put exported files.
|
35
|
+
#
|
36
|
+
# Examples
|
37
|
+
#
|
38
|
+
# export '~/skype_backup'
|
39
|
+
# # => 8
|
40
|
+
#
|
41
|
+
# Returns the count of the exported files.
|
42
|
+
def export(destination_path)
|
43
|
+
chat_records = @messages.select(:chatname, :timestamp, :from_dispname, :body_xml)
|
44
|
+
|
45
|
+
chat_records.each { |record| save_to_file record, destination_path }
|
46
|
+
|
47
|
+
@exported_filenames.count
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Gets parsed chatnames together with partly parsed chatnames.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# get_chatnames
|
55
|
+
# # => [['something-more', 'somethindg-else'], ['#something/$more;6521032', '#something/$else;8971263']]
|
56
|
+
#
|
57
|
+
# Returns two Array objects containing parsed chatnames and partly parsed chatnames.
|
58
|
+
def get_chatnames
|
59
|
+
chatnames = @messages.select(:chatname)
|
60
|
+
raw_chatnames = chatnames.map { |record| partly_parse_chatname record[:chatname] }.uniq
|
61
|
+
clean_chatnames = raw_chatnames.map { |chatname| parse_chatname chatname }
|
62
|
+
|
63
|
+
return clean_chatnames, raw_chatnames
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Exports Skype chat history to text files for specified chats.
|
67
|
+
#
|
68
|
+
# chatnames - Array of chatnames, which will be exported
|
69
|
+
# destination_path - A String with folder path, where to put exported files.
|
70
|
+
#
|
71
|
+
# Examples
|
72
|
+
#
|
73
|
+
# export_chats ['#something/$more;', '#something/$else;'], '~/skype_backup'
|
74
|
+
# # => 2
|
75
|
+
#
|
76
|
+
# Returns the count of the exported files.
|
77
|
+
def export_chats(chatnames, destination_path)
|
78
|
+
pattern_chatnames = chatnames.map { |name| "#{name}%" }
|
79
|
+
chat_records = @messages.where(Sequel.like(:chatname, *pattern_chatnames))
|
80
|
+
|
81
|
+
chat_records.each { |record| save_to_file record, destination_path }
|
82
|
+
|
83
|
+
@exported_filenames.count
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Internal: Saves a single chat message to a file.
|
89
|
+
#
|
90
|
+
# chat_record - a Hash containing data about a single chat message.
|
91
|
+
# output_directory - A String with the path to the directory, wher the file will be saved.
|
92
|
+
#
|
93
|
+
# Examples
|
94
|
+
#
|
95
|
+
# save_to_file record, '~/skype_backup'
|
96
|
+
def save_to_file(chat_record, output_directory)
|
97
|
+
datetime = Time.at chat_record[:timestamp]
|
98
|
+
output_record = "[#{datetime.to_date}] #{chat_record[:from_dispname]}: #{chat_record[:body_xml]}"
|
99
|
+
filename = "#{output_directory}/#{parse_chatname chat_record[:chatname]}.txt"
|
100
|
+
|
101
|
+
File.open(filename, 'a') do |file|
|
102
|
+
file.puts output_record
|
103
|
+
end
|
104
|
+
|
105
|
+
@exported_filenames << filename
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal: Converts chatname from database to a valid file name.
|
109
|
+
#
|
110
|
+
# raw_chatname - A String with a chatname read from the database.
|
111
|
+
#
|
112
|
+
# Examples
|
113
|
+
#
|
114
|
+
# parse_chatname '#someone/$someone_else;521357125362'
|
115
|
+
# # => someone-someone_else.txt
|
116
|
+
#
|
117
|
+
# Returns a String with a valid file name.
|
118
|
+
def parse_chatname(raw_chatname)
|
119
|
+
match = raw_chatname.match(/#(.+)\/\$(.+);|#(.+)\/\$/)
|
120
|
+
first_part, second_part, third_part = match.captures
|
121
|
+
chatname = "#{first_part}-#{second_part}-#{third_part}"
|
122
|
+
|
123
|
+
trim_dashes chatname
|
124
|
+
end
|
125
|
+
|
126
|
+
# Internal: Removes extra characters from the end of a chatname.
|
127
|
+
#
|
128
|
+
# raw_chatname - A String with a chatname read from the database
|
129
|
+
#
|
130
|
+
# Examples
|
131
|
+
#
|
132
|
+
# partly_parse_chatname '#someone/$someone_else;521357125362'
|
133
|
+
# # => #someone/$someone_else;
|
134
|
+
#
|
135
|
+
# Returns a String with a chatname without extra characters at the end.
|
136
|
+
def partly_parse_chatname(raw_chatname)
|
137
|
+
match = raw_chatname.match(/(#.+\/\$.+;)|(#.+\/\$)/)
|
138
|
+
first_group, second_group = match.captures
|
139
|
+
|
140
|
+
first_group || second_group
|
141
|
+
end
|
142
|
+
|
143
|
+
# Internal: Removes unnecessary dashes from the begining and the end of the string.
|
144
|
+
#
|
145
|
+
# string - A String possibly containing dashes at the beggining or the end
|
146
|
+
#
|
147
|
+
# Examples
|
148
|
+
#
|
149
|
+
# str = '--example-'
|
150
|
+
# trim_dashes str
|
151
|
+
# # => example
|
152
|
+
#
|
153
|
+
# Returns a string without leading and ending dashes.
|
154
|
+
def trim_dashes(string)
|
155
|
+
string.gsub! /^-+/, ''
|
156
|
+
string.gsub! /-+$/, ''
|
157
|
+
|
158
|
+
string
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Runoff
|
2
|
+
|
3
|
+
# Public: Contains class methods for finding out the appropriate file paths.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# Location::default_skype_data_location skype_username
|
8
|
+
# # => /home/user/.Skype/skype_username/main.db
|
9
|
+
class Location
|
10
|
+
|
11
|
+
# Public: Composes the default Skype database location depending on the operating system.
|
12
|
+
#
|
13
|
+
# skype_username - A String that contains a username of the Skype account,
|
14
|
+
# which database we want to access.
|
15
|
+
#
|
16
|
+
# Examples
|
17
|
+
#
|
18
|
+
# On Linux:
|
19
|
+
# default_skype_data_location skype_username
|
20
|
+
# # => /home/user/.Skype/skype_username/mai.db
|
21
|
+
#
|
22
|
+
# On Windows:
|
23
|
+
# default_skype_data_location skype_username
|
24
|
+
# # => C:\Users\user\AppData\Roaming\Skype\skype_username\main.db
|
25
|
+
#
|
26
|
+
# Returns a String that contains the path to the Skype database file.
|
27
|
+
def self.default_skype_data_location(skype_username)
|
28
|
+
if RbConfig::CONFIG['host_os'] =~ /mingw/
|
29
|
+
"#{ENV['APPDATA']}\\Skype\\#{skype_username}\\main.db"
|
30
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux/
|
31
|
+
"#{ENV['HOME']}/.Skype/#{skype_username}/main.db"
|
32
|
+
else
|
33
|
+
"~/Library/Application Support/Skype/#{skype_username}/main.db"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Clarifies the path to the user's home directory depending on the operating system
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
#
|
41
|
+
# On Linux:
|
42
|
+
# home_path
|
43
|
+
# # => /home/user
|
44
|
+
#
|
45
|
+
# On Windows:
|
46
|
+
# home_path
|
47
|
+
# # => C:\Users\user
|
48
|
+
#
|
49
|
+
# Returns a String that contains the path to the user's home directory.
|
50
|
+
def self.home_path
|
51
|
+
if RbConfig::CONFIG['host_os'] =~ /mingw/
|
52
|
+
ENV['USERPROFILE']
|
53
|
+
else
|
54
|
+
ENV['HOME']
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
# Public: Provides the functionality to back up Skype chat history.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# runoff all skype_username
|
8
|
+
# # => Finished: 8 files were exported
|
9
|
+
module Runoff
|
10
|
+
|
11
|
+
# Public: Entry point for the executable. Processes the CLI input from the user.
|
12
|
+
class Source < Thor
|
13
|
+
desc 'all [SKYPE_USERNAME] [OPTIONS]', 'Export all chat history'
|
14
|
+
|
15
|
+
long_desc <<-LONGDESC
|
16
|
+
runoff all [SKYPE_USERNAME] [OPTIONS] will export all your Skype chat history as text files.
|
17
|
+
|
18
|
+
SKYPE_USERNAME - the Skype account username, which data you want to access
|
19
|
+
LONGDESC
|
20
|
+
|
21
|
+
method_option :from, aliases: '-f', desc: 'Specify the location of the main.db file'
|
22
|
+
method_option :to, aliases: '-t', desc: 'Specify where to put export files'
|
23
|
+
|
24
|
+
# Public: Exports all chat history from the Skype database.
|
25
|
+
#
|
26
|
+
# skype_username - A String that contains a username of the Skype account,
|
27
|
+
# which database we want to access.
|
28
|
+
#
|
29
|
+
# Examples
|
30
|
+
#
|
31
|
+
# all 'skype_username'
|
32
|
+
# # => Finished: 8 files were exported
|
33
|
+
def all(skype_username = nil)
|
34
|
+
composition = get_composition skype_username
|
35
|
+
destination = get_destination
|
36
|
+
|
37
|
+
print_result composition.export(destination)
|
38
|
+
|
39
|
+
rescue IOError => e
|
40
|
+
puts e
|
41
|
+
rescue StandardError => e
|
42
|
+
puts e
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'chat [SKYPE_USERNAME] [OPTIONS]', 'Export pecified chats history'
|
46
|
+
|
47
|
+
long_desc <<-LONGDESC
|
48
|
+
runoff chat [SKYPE_USERNAME] [OPTIONS] will export all your Skype chat history as text files.
|
49
|
+
|
50
|
+
SKYPE_USERNAME - the Skype account username, which data you want to access
|
51
|
+
LONGDESC
|
52
|
+
|
53
|
+
method_option :from, aliases: '-f', desc: 'Specify the location of the main.db file'
|
54
|
+
method_option :to, aliases: '-t', desc: 'Specify where to put export files'
|
55
|
+
|
56
|
+
# Public: Exports specified chats from the Skype database.
|
57
|
+
#
|
58
|
+
# skype_username - A String that contains a username of the Skype account,
|
59
|
+
# which database we want to access.
|
60
|
+
#
|
61
|
+
# Examples
|
62
|
+
#
|
63
|
+
# chat 'skype_username'
|
64
|
+
# # => Finished: 3 files were exported
|
65
|
+
def chat(skype_username = nil)
|
66
|
+
composition = get_composition skype_username
|
67
|
+
destination = get_destination
|
68
|
+
chatnames, raw_chatnames = composition.get_chatnames
|
69
|
+
|
70
|
+
list_chatnames chatnames
|
71
|
+
indecies = ask "Which chats do you want to export? (Enter indecies) "
|
72
|
+
indecies = indecies.split.map { |index| index.to_i }
|
73
|
+
selected_chatnames = []
|
74
|
+
|
75
|
+
indecies.each { |index| selected_chatnames << raw_chatnames[index] }
|
76
|
+
print_result composition.export_chats(selected_chatnames, destination)
|
77
|
+
|
78
|
+
rescue IOError => e
|
79
|
+
puts e
|
80
|
+
rescue StandardError => e
|
81
|
+
puts e
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Internal: Gets a Composition object.
|
87
|
+
#
|
88
|
+
# skype_username - A String that contains a username of the Skype account,
|
89
|
+
# which database we want to access.
|
90
|
+
#
|
91
|
+
# Examples
|
92
|
+
#
|
93
|
+
# get_composition 'skype_username'
|
94
|
+
# # => #<Composition:0x00002324212>
|
95
|
+
#
|
96
|
+
# Returns a Composition object with a reference to a specific Skype database.
|
97
|
+
# Raises StandardError if no Skype username or --from option is provided.
|
98
|
+
def get_composition(skype_username)
|
99
|
+
unless skype_username || options[:from]
|
100
|
+
raise StandardError, 'You must provide a Skype username or a --from option'
|
101
|
+
end
|
102
|
+
|
103
|
+
main_db_file_location = options[:from] || Location.default_skype_data_location(skype_username)
|
104
|
+
Composition.new main_db_file_location
|
105
|
+
end
|
106
|
+
|
107
|
+
# Internal: Gets a destination path depending on the entered options.
|
108
|
+
#
|
109
|
+
# Examples
|
110
|
+
#
|
111
|
+
# get_destination
|
112
|
+
# # => '~/skype_backup'
|
113
|
+
#
|
114
|
+
# Returns a String containing a path to the destination directory.
|
115
|
+
def get_destination
|
116
|
+
options[:to] || Location.home_path
|
117
|
+
end
|
118
|
+
|
119
|
+
# Internal: Informs the user that the application has finished running.
|
120
|
+
#
|
121
|
+
# count - A number of files that have been exported
|
122
|
+
#
|
123
|
+
# Examples
|
124
|
+
#
|
125
|
+
# print_result 4
|
126
|
+
# # => Finished: 4 files were exported
|
127
|
+
def print_result(count)
|
128
|
+
if count == 1
|
129
|
+
puts "Finished: 1 file was exported"
|
130
|
+
elsif count > 1
|
131
|
+
puts "Finished: #{count} files were exported"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Internal: Prints available chatnames.
|
136
|
+
#
|
137
|
+
# chatnames - An Array containing the chatname strings
|
138
|
+
#
|
139
|
+
# Examples
|
140
|
+
#
|
141
|
+
# list_chatnames ['something-more', 'something-else']
|
142
|
+
# # => [0] something-more
|
143
|
+
# [1] something-else
|
144
|
+
def list_chatnames(chatnames)
|
145
|
+
chatnames.each_with_index { |n, i| puts "[#{i}] #{n}" }
|
146
|
+
puts
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/runoff.rb
ADDED
data/runoff.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'runoff/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = "runoff"
|
9
|
+
gem.version = Runoff::VERSION
|
10
|
+
gem.authors = ["Aigars Dzerviniks"]
|
11
|
+
gem.email = ["dzerviniks.aigars@outlook.com"]
|
12
|
+
gem.description = %q{runoff provides functionality to export all the Skype chat history or only specified chats from the Skype SQLite database file to text files}
|
13
|
+
gem.summary = %q{Tool to export Skype chat history from the SQLite database to text files}
|
14
|
+
gem.homepage = 'https://github.com/aidzis/runoff'
|
15
|
+
gem.license = 'MIT'
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
|
22
|
+
gem.add_dependency 'thor'
|
23
|
+
gem.add_dependency 'sqlite3'
|
24
|
+
gem.add_dependency 'sequel'
|
25
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'runoff'
|
4
|
+
|
5
|
+
describe Runoff::Composition do
|
6
|
+
before { @composition = Runoff::Composition.new 'test/test_db.sqlite' }
|
7
|
+
|
8
|
+
it "must raise an IOError if the file that is passed to the constructor doesn't exist" do
|
9
|
+
->{ composition = Runoff::Composition.new 'not_existing.db' }.must_raise IOError
|
10
|
+
end
|
11
|
+
|
12
|
+
it "must have a getter method for exported filenames" do
|
13
|
+
@composition.must_respond_to :exported_filenames
|
14
|
+
end
|
15
|
+
|
16
|
+
it "must remove all starting and ending dashes from a string" do
|
17
|
+
string = "---example--"
|
18
|
+
valid_name = @composition.send :trim_dashes, string
|
19
|
+
|
20
|
+
valid_name.must_equal 'example'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "must return a valid and readable name from a raw chatname" do
|
24
|
+
raw_chatname = '#something/$else;521357125362'
|
25
|
+
chatname = @composition.send :parse_chatname, raw_chatname
|
26
|
+
|
27
|
+
chatname.must_equal 'something-else'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "must return a valid and readable name from broken chatname records" do
|
31
|
+
raw_chatname = '#something/$521357125362'
|
32
|
+
chatname = @composition.send :parse_chatname, raw_chatname
|
33
|
+
|
34
|
+
chatname.must_equal 'something'
|
35
|
+
end
|
36
|
+
|
37
|
+
it "must return a chatname without the extra symbols" do
|
38
|
+
raw_chatname = '#something/$else;521357125362'
|
39
|
+
chatname = @composition.send :partly_parse_chatname, raw_chatname
|
40
|
+
|
41
|
+
chatname.must_equal '#something/$else;'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "must return a chatname without the extra symbols for broken chatname records" do
|
45
|
+
raw_chatname = '#something/$521357125362'
|
46
|
+
chatname = @composition.send :partly_parse_chatname, raw_chatname
|
47
|
+
|
48
|
+
chatname.must_equal '#something/$'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "must return parsed chatnames together with partly parsed chatnames" do
|
52
|
+
chatnames, raw_chatnames = @composition.get_chatnames
|
53
|
+
|
54
|
+
chatnames.must_equal ['something-more', 'something-else']
|
55
|
+
raw_chatnames.must_equal ['#something/$more;', '#something/$else;']
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#save_to_file" do
|
59
|
+
before do
|
60
|
+
@composition = Runoff::Composition.new 'test/test_db.sqlite'
|
61
|
+
@incorrect_chat_record = {
|
62
|
+
chatname: '#something/$;521357125362',
|
63
|
+
timestamp: 1362864487,
|
64
|
+
from_dispname: 'Aidzis',
|
65
|
+
body_xml: ''
|
66
|
+
}
|
67
|
+
@chat_record = {
|
68
|
+
chatname: '#something/$more;521357125362',
|
69
|
+
timestamp: 1362864487,
|
70
|
+
from_dispname: 'Aidzis',
|
71
|
+
body_xml: ''
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
after { FileUtils.rm_rf 'test/tmp/.' }
|
76
|
+
|
77
|
+
it "must write chat content to file" do
|
78
|
+
@incorrect_chat_record[:chatname] = '#something/$else;521357125362'
|
79
|
+
|
80
|
+
@composition.send :save_to_file, @chat_record, 'test/tmp'
|
81
|
+
@composition.send :save_to_file, @incorrect_chat_record, 'test/tmp'
|
82
|
+
@composition.exported_filenames.count.must_equal 2
|
83
|
+
end
|
84
|
+
|
85
|
+
it "must append to a file if the filename already exists" do
|
86
|
+
@incorrect_chat_record[:chatname] = '#something/$else;521357125362'
|
87
|
+
@additional_chat_record = @chat_record
|
88
|
+
|
89
|
+
@composition.send :save_to_file, @chat_record, 'test/tmp'
|
90
|
+
@composition.send :save_to_file, @incorrect_chat_record, 'test/tmp'
|
91
|
+
@composition.send :save_to_file, @additional_chat_record, 'test/tmp'
|
92
|
+
@composition.exported_filenames.count.must_equal 2
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#export" do
|
97
|
+
after { FileUtils.rm_rf 'test/tmp/.' }
|
98
|
+
|
99
|
+
it "must return a count of the exported filenames" do
|
100
|
+
composition = Runoff::Composition.new 'test/test_db.sqlite'
|
101
|
+
file_count = composition.export 'test/tmp'
|
102
|
+
|
103
|
+
file_count.must_equal 2
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#export_chats" do
|
108
|
+
after { FileUtils.rm_rf 'test/tmp/.' }
|
109
|
+
|
110
|
+
it "must return a count of the exported filenames" do
|
111
|
+
composition = Runoff::Composition.new 'test/test_db.sqlite'
|
112
|
+
file_count = composition.export_chats ['#something/$more;', '#something/$else;'], 'test/tmp'
|
113
|
+
|
114
|
+
file_count.must_equal 2
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'runoff'
|
4
|
+
|
5
|
+
describe Runoff::Location do
|
6
|
+
describe '.default_skype_data_location' do
|
7
|
+
it "must return a default path depending on the operating system" do
|
8
|
+
path = Runoff::Location.default_skype_data_location 'aidzis_skype'
|
9
|
+
|
10
|
+
if RbConfig::CONFIG['host_os'] =~ /mingw/
|
11
|
+
path.must_match /[A-Z]:\\Users\\[a-zA-Z0-9]+\\AppData\\Roaming\\Skype\\aidzis_skype\\main\.db/
|
12
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux/
|
13
|
+
path.must_match /\/home\/[a-zA-Z0-9]+\/\.Skype\/aidzis_skype\/main\.db/
|
14
|
+
else
|
15
|
+
path.must_match /~\/Library\/Application Support\/Skype\/aidzis_skype\/main\.db/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".home_path" do
|
21
|
+
it "must return a path to the user home directory depending on the operating system" do
|
22
|
+
path = Runoff::Location.home_path
|
23
|
+
|
24
|
+
if RbConfig::CONFIG['host_os'] =~ /mingw/
|
25
|
+
path.must_match /[A-Z]:\\Users\\[a-zA-Z0-9]+/
|
26
|
+
else
|
27
|
+
path.must_match /\/home\/[a-zA-Z0-9]+/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'runoff'
|
4
|
+
|
5
|
+
describe Runoff::Source do
|
6
|
+
after { FileUtils.rm_rf 'test/tmp/.' }
|
7
|
+
|
8
|
+
it "must export all the chats from a Skype database into text files and print a count of the exported files" do
|
9
|
+
command = "all -f test/test_db.sqlite -t test/tmp"
|
10
|
+
|
11
|
+
->{ Runoff::Source.start command.split }.must_output "Finished: 2 files were exported\n"
|
12
|
+
end
|
13
|
+
end
|
data/test/test_db.sqlite
ADDED
Binary file
|
data.tar.gz.sig
ADDED
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: runoff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aigars Dzerviniks
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- !binary |-
|
12
|
+
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURuakNDQW9hZ0F3SUJB
|
13
|
+
Z0lCQVRBTkJna3Foa2lHOXcwQkFRVUZBREJLTVJvd0dBWURWUVFEREJGa2Vt
|
14
|
+
VnkKZG1sdWFXdHpMbUZwWjJGeWN6RVhNQlVHQ2dtU0pvbVQ4aXhrQVJrV0Iy
|
15
|
+
OTFkR3h2YjJzeEV6QVJCZ29Ka2lhSgprL0lzWkFFWkZnTmpiMjB3SGhjTk1U
|
16
|
+
TXdNekkxTVRjek9EVTRXaGNOTVRRd016STFNVGN6T0RVNFdqQktNUm93CkdB
|
17
|
+
WURWUVFEREJGa2VtVnlkbWx1YVd0ekxtRnBaMkZ5Y3pFWE1CVUdDZ21TSm9t
|
18
|
+
VDhpeGtBUmtXQjI5MWRHeHYKYjJzeEV6QVJCZ29Ka2lhSmsvSXNaQUVaRmdO
|
19
|
+
amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBdwpnZ0VLQW9J
|
20
|
+
QkFRQzBXYXJpYUl6cmpkU3pBY1VNNGs3a1VmWkdrNFMxZWFIY2J5OTdhRHV0
|
21
|
+
QjNGTnp5Q3VGZnhVCjdQdndyR29aS0FGVEY1Mnc5aitKeDljV0FPbzlaTENz
|
22
|
+
ZmJKd040ZjdrTVM4emRYVWJ0d3ovL0V4NnNacG9vZE4KZENnSUM2TXJvUXdL
|
23
|
+
L3ZjNXExYi90ZVdTNFFCNElmcEVqNlJZYjhRYUptWjBJR252aXJmWE9wVlh5
|
24
|
+
N05ISXdmVApTc0R5dGFCRTFzaVAwZ3g0YTlpNVNaTjVHM1UydmJFbmE5aDI3
|
25
|
+
OC9nOVdsSnFCN1ZrcW9YTi9zeUE4L1VIeHVlClcvS1lGVDFXZ2xpR0l3aXhs
|
26
|
+
K0FJeVFwNGxscEJsOUgzbnU3ZFN2bmRyOVVIUmRsa3pZeEZWM0ZjM0V1cnBp
|
27
|
+
UE0KT1BTanpJL0c4V3hJYWkrVWVaY2lERjFJc1lpNXRhY0hBZ01CQUFHamdZ
|
28
|
+
NHdnWXN3Q1FZRFZSMFRCQUl3QURBTApCZ05WSFE4RUJBTUNCTEF3SFFZRFZS
|
29
|
+
ME9CQllFRkhDSmZLQ3V0ZkhETGdmMUpLak9WNnVHTHVqTE1DZ0dBMVVkCkVR
|
30
|
+
UWhNQitCSFdSNlpYSjJhVzVwYTNNdVlXbG5ZWEp6UUc5MWRHeHZiMnN1WTI5
|
31
|
+
dE1DZ0dBMVVkRWdRaE1CK0IKSFdSNlpYSjJhVzVwYTNNdVlXbG5ZWEp6UUc5
|
32
|
+
MWRHeHZiMnN1WTI5dE1BMEdDU3FHU0liM0RRRUJCUVVBQTRJQgpBUUNudHBP
|
33
|
+
UXZveDhUVlN3d1h5MXFDMnVadnZQNTV6WjZoeHl6emJIZFJxd0FBT25zc2ZB
|
34
|
+
VkFaSGlYVGZMZFFPCjg3Z3lMYk81YlZRa0dOa3laemRWTWJjNTlhNEg2ZEtT
|
35
|
+
cjZBbWNDdGIvdGYrWldqR1U3b0F2UkZuWVJ3aGlyRkwKWThNTDNVZEQyVGdE
|
36
|
+
REFYQU9IS1Vsc3E2dEtsVTFORXNMdjFCUkJOa0xQbzI1WHlFU1R6SlRWM2M4
|
37
|
+
ZkluNmlLcgpaL3BOenhBZDlSN29TOExDZlhhTGhQeG85dkVyVVZLd3Q2NHpk
|
38
|
+
YUtiQUNrampabkNab2VhaGxPd2dzVnN5bDBvCjQ0aXhMbXE0dTE1a0grUFRr
|
39
|
+
dHFPWFp1ck5FWmdTMlRSWG5GRG9TWmRtT3IxMjM2ZS9SNTBzc1dlR0dIUUox
|
40
|
+
V2wKN2ZZbFZpQThPY2Z3aGdyaDcyc05mVHpTCi0tLS0tRU5EIENFUlRJRklD
|
41
|
+
QVRFLS0tLS0K
|
42
|
+
date: 2013-03-25 00:00:00.000000000 Z
|
43
|
+
dependencies:
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: thor
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sqlite3
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: sequel
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
type: :runtime
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
description: runoff provides functionality to export all the Skype chat history or
|
87
|
+
only specified chats from the Skype SQLite database file to text files
|
88
|
+
email:
|
89
|
+
- dzerviniks.aigars@outlook.com
|
90
|
+
executables:
|
91
|
+
- runoff
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- .gitignore
|
96
|
+
- Gemfile
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.md
|
99
|
+
- Rakefile
|
100
|
+
- bin/runoff
|
101
|
+
- gem-public_cert.pem
|
102
|
+
- lib/runoff.rb
|
103
|
+
- lib/runoff/composition.rb
|
104
|
+
- lib/runoff/location.rb
|
105
|
+
- lib/runoff/source.rb
|
106
|
+
- lib/runoff/version.rb
|
107
|
+
- runoff.gemspec
|
108
|
+
- test/runoff_composition_test.rb
|
109
|
+
- test/runoff_locations_test.rb
|
110
|
+
- test/runoff_source_test.rb
|
111
|
+
- test/test_db.sqlite
|
112
|
+
homepage: https://github.com/aidzis/runoff
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.0.3
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: Tool to export Skype chat history from the SQLite database to text files
|
136
|
+
test_files:
|
137
|
+
- test/runoff_composition_test.rb
|
138
|
+
- test/runoff_locations_test.rb
|
139
|
+
- test/runoff_source_test.rb
|
140
|
+
- test/test_db.sqlite
|
metadata.gz.sig
ADDED
Binary file
|