runoff 0.3.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,79 +1,118 @@
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
- # # => /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
- case RbConfig::CONFIG['host_os']
29
- when /mingw/
30
- if File.exist?("#{ENV['APPDATA']}\\Skype")
31
- format_windows_path "#{ENV['APPDATA']}\\Skype\\#{skype_username}\\main.db"
32
- else
33
- format_windows_path self.get_default_skype_data_location_on_windows_8(skype_username)
34
- end
35
- when /linux/
36
- "#{ENV['HOME']}/.Skype/#{skype_username}/main.db"
37
- else
38
- "#{ENV['HOME']}/Library/Application Support/Skype/#{skype_username}/main.db"
39
- end
40
- end
41
-
42
- private
43
-
44
- # Internal: Replaces backslashes with forward slashes and removes drive letter.
45
- #
46
- # path - String containing a directory path.
47
- #
48
- # Examples
49
- #
50
- # format_windows_path 'C:\Users\username\AppData\Roaming\Skype\skype_username\main.db'
51
- # # => /Users/username/AppData/ROaming/Skype/skype_username/main.db
52
- #
53
- # Returns a String with modified directory path.
54
- def self.format_windows_path(path)
55
- path = path.gsub(/\\/, '/')
56
- path.gsub(/^[a-zA-Z]:/, '')
57
- end
58
-
59
- # Internal: Composes the default Skype database location for the Windows 8 operating system.
60
- #
61
- # skype_username - A String that contains a username of the Skype account,
62
- # which database we want to access.
63
- #
64
- # Examples
65
- #
66
- # get_default_skype_data_location_on_windows_8 skype_username
67
- # # => C:/Users/user/AppData/Local/Packages/Microsoft.SkypeApp_sakjhds8asd/LocalState/skype_username/main.db
68
- #
69
- # Returns a String that contains the path to the Skype database file.
70
- def self.get_default_skype_data_location_on_windows_8(skype_username)
71
- location = "#{ENV['HOME']}/AppData/Local/Packages"
72
- skype_folder = Dir["#{location}/Microsoft.SkypeApp*"].first
73
-
74
- raise IOError.new "The default Skype directory doesn't exist." unless skype_folder
75
-
76
- "#{skype_folder}/LocalState/#{skype_username}/main.db"
77
- end
78
- end
79
- end
1
+ require 'fileutils'
2
+
3
+ module Runoff
4
+ # Contains class methods for finding out the appropriate file paths.
5
+ #
6
+ # Examples
7
+ #
8
+ # Location::default_skype_data_location skype_username
9
+ # # => /home/user/.Skype/skype_username/main.db
10
+ class Location
11
+ # Public: Gets a path to the Skype's main.db file.
12
+ #
13
+ # username - A String containing Skype username
14
+ # options - A hash containing command-line options passed to the command.
15
+ # If the username is empty, then the hash must contain :from key.
16
+ #
17
+ # Examples
18
+ #
19
+ # get_database_path('john_doe', {})
20
+ # # => Path to the default Skype database location depending on the operating system.
21
+ #
22
+ # get_database_path('', { from: '~/Desktop/main.db' })
23
+ # # => '~/Desktop/main.db'
24
+ #
25
+ # Returns a String
26
+ def self.get_database_path(username, options)
27
+ if options.from
28
+ options.from
29
+ else
30
+ self.default_skype_data_location username
31
+ end
32
+ end
33
+
34
+ # Public: Composes the default Skype database location depending on the operating system.
35
+ #
36
+ # skype_username - A String that contains a username of the Skype account,
37
+ # which database we want to access.
38
+ #
39
+ # Examples
40
+ #
41
+ # On Linux:
42
+ # default_skype_data_location skype_username
43
+ # # => /home/user/.Skype/skype_username/mai.db
44
+ #
45
+ # On Windows:
46
+ # default_skype_data_location skype_username
47
+ # # => /Users/user/AppData/Roaming/Skype/skype_username/main.db
48
+ #
49
+ # Returns a String that contains the path to the Skype database file.
50
+ def self.default_skype_data_location(skype_username)
51
+ case RbConfig::CONFIG['host_os']
52
+ when /mingw/
53
+ if File.exist?("#{ENV['APPDATA']}\\Skype")
54
+ format_windows_path "#{ENV['APPDATA']}\\Skype\\#{skype_username}\\main.db"
55
+ else
56
+ format_windows_path self.get_default_skype_data_location_on_windows_8(skype_username)
57
+ end
58
+ when /linux/
59
+ "#{ENV['HOME']}/.Skype/#{skype_username}/main.db"
60
+ else
61
+ "#{ENV['HOME']}/Library/Application Support/Skype/#{skype_username}/main.db"
62
+ end
63
+ end
64
+
65
+ # Public: Composes a path where the exported files must be saved.
66
+ #
67
+ # options - an object with command line options passed to the runoff executable.
68
+ #
69
+ # Returns a string with a directory path.
70
+ def self.get_export_path(options)
71
+ path = options.destination || "#{ENV['HOME']}"
72
+ path = "#{path}/skype_chat_history"
73
+
74
+ unless File.exist?(path)
75
+ FileUtils::mkdir_p path
76
+ end
77
+
78
+ path
79
+ end
80
+
81
+ private
82
+
83
+ # Internal: Replaces backslashes with forward slashes and removes drive letter.
84
+ #
85
+ # path - String containing a directory path.
86
+ #
87
+ # Examples
88
+ #
89
+ # format_windows_path 'C:\Users\username\AppData\Roaming\Skype\skype_username\main.db'
90
+ # # => /Users/username/AppData/ROaming/Skype/skype_username/main.db
91
+ #
92
+ # Returns a String with modified directory path.
93
+ def self.format_windows_path(path)
94
+ path = path.gsub(/\\/, '/')
95
+ path.gsub(/^[a-zA-Z]:/, '')
96
+ end
97
+
98
+ # Internal: Composes the default Skype database location for the Windows 8 operating system.
99
+ #
100
+ # skype_username - A String that contains a username of the Skype account,
101
+ # which database we want to access.
102
+ #
103
+ # Examples
104
+ #
105
+ # get_default_skype_data_location_on_windows_8 skype_username
106
+ # # => C:/Users/user/AppData/Local/Packages/Microsoft.SkypeApp_sakjhds8asd/LocalState/skype_username/main.db
107
+ #
108
+ # Returns a String that contains the path to the Skype database file.
109
+ def self.get_default_skype_data_location_on_windows_8(skype_username)
110
+ location = "#{ENV['HOME']}/AppData/Local/Packages"
111
+ skype_folder = Dir["#{location}/Microsoft.SkypeApp*"].first
112
+
113
+ raise IOError.new "The default Skype directory doesn't exist." unless skype_folder
114
+
115
+ "#{skype_folder}/LocalState/#{skype_username}/main.db"
116
+ end
117
+ end
118
+ end
@@ -1,104 +1,128 @@
1
- module Runoff
2
- # Public: Deals with Skype specific formats.
3
- class SkypeDataFormat
4
- #Public: Builds an appropriate filename.
5
- #
6
- # chat_record - A Hash containing necessary data.
7
- #
8
- # Examples
9
- #
10
- # get_filename { chatname: 'demo-chat' }
11
- # # => demo-chat.txt
12
- #
13
- # Retruns a filename as a String.
14
- def get_filename(chat_record)
15
- "#{parse_chatname chat_record[:chatname]}.txt"
16
- end
17
-
18
- # Public: Creates a String with one entry from the chat.
19
- #
20
- # chat_record - a Hash containing data about a single chat message.
21
- #
22
- # Examples
23
- #
24
- # build_entry { timestamp: 213213232, from_dispname: 'aidzis', body_xml: 'This is text.' }
25
- # # => [2013-04-13 14:23:57] aidzis: This is text.
26
- #
27
- # Returns a String with a chat entry.
28
- def build_entry(chat_record)
29
- datetime = Time.at chat_record[:timestamp]
30
- output_record = "[#{datetime.strftime "%Y-%m-%d %H:%M:%S"}] "
31
- output_record << "#{chat_record[:from_dispname]}: #{parse_body_xml chat_record[:body_xml]}"
32
-
33
- output_record
34
- rescue StandardError
35
- end
36
-
37
- # Public: Converts chatname from database to a valid file name.
38
- #
39
- # raw_chatname - A String with a chatname read from the database.
40
- #
41
- # Examples
42
- #
43
- # parse_chatname '#someone/$someone_else;521357125362'
44
- # # => someone-someone_else.txt
45
- #
46
- # Returns a String with a valid file name.
47
- def parse_chatname(raw_chatname)
48
- match = raw_chatname.match(/#(.+)\/\$(.+);|#(.+)\/\$/)
49
- first_part, second_part, third_part = match.captures
50
- chatname = "#{first_part}-#{second_part}-#{third_part}"
51
-
52
- trim_dashes chatname
53
- end
54
-
55
- # Public: Removes extra characters from the end of a chatname.
56
- #
57
- # raw_chatname - A String with a chatname read from the database
58
- #
59
- # Examples
60
- #
61
- # partly_parse_chatname '#someone/$someone_else;521357125362'
62
- # # => #someone/$someone_else;
63
- #
64
- # Returns a String with a chatname without extra characters at the end.
65
- def partly_parse_chatname(raw_chatname)
66
- match = raw_chatname.match(/(#.+\/\$.+;)|(#.+\/\$)/)
67
- first_group, second_group = match.captures
68
-
69
- first_group || second_group
70
- end
71
-
72
- private
73
- # Internal: Remove Skype emotion tags.
74
- #
75
- # text - String containing XML data
76
- #
77
- # Examples
78
- #
79
- # parse_body_xml "Some text <ss type="laugh">:D</ss>"
80
- # # => "Some text :D"
81
- #
82
- # Returns the duplicated String.
83
- def parse_body_xml(text)
84
- clean_text = text.gsub(/<ss type=".+">/, '')
85
- clean_text.gsub(/<\/ss>/, '')
86
- end
87
-
88
- # Internal: Removes unnecessary dashes from the begining and the end of the string.
89
- #
90
- # string - A String possibly containing dashes at the beggining or the end
91
- #
92
- # Examples
93
- #
94
- # str = '--example-'
95
- # trim_dashes str
96
- # # => example
97
- #
98
- # Returns a string without leading and ending dashes.
99
- def trim_dashes(string)
100
- clean_string = string.gsub(/^-+/, '')
101
- clean_string.gsub(/-+$/, '')
102
- end
103
- end
104
- end
1
+ module Runoff
2
+ # Defines methods to hide the specific format that is used in the Skype database.
3
+ class SkypeDataFormat
4
+ # Public: Defines what kind of information can be queried from the database.
5
+ #
6
+ # Example
7
+ #
8
+ # get_schema do |table, columns|
9
+ # # SELSECT *columns FROM table
10
+ # end
11
+ #
12
+ # get_schema
13
+ # # => { table: :Messages, columns: [:chatname, :timestamp, :from_dispname, :body_xml] }
14
+ #
15
+ # Returns a hash with keys "table" and "columns" if no block is given.
16
+ def get_schema
17
+ if block_given?
18
+ yield :Messages, [:chatname, :timestamp, :from_dispname, :body_xml]
19
+ else
20
+ return {
21
+ table: :Messages,
22
+ columns: [:chatname, :timestamp, :from_dispname, :body_xml]
23
+ }
24
+ end
25
+ end
26
+
27
+ # Public: Provides a filename and the data that should be written to the file.
28
+ #
29
+ # fields - an array representing a single entry in the database.
30
+ #
31
+ # Examples
32
+ #
33
+ # build_entry {
34
+ # chatname: "#john/$doe;1243435",
35
+ # from_dispname: "John",
36
+ # body_xml: "Lorem ipsum",
37
+ # timestamp: 12435463
38
+ # } # => { filename: john-doe.txt, content: "[2013-12-27 12:23:43] John: Lorem ipsum" }
39
+ #
40
+ # Returns a hash with "filename" and "content" keys.
41
+ def build_entry(fields)
42
+ chatname = fields[:chatname]
43
+ username = fields[:from_dispname]
44
+ message = parse_xml fields[:body_xml]
45
+ datetime = Time.at(fields[:timestamp]).strftime "%Y-%m-%d %H:%M:%S"
46
+
47
+ {
48
+ filename: get_filename(chatname),
49
+ content: "[#{datetime}] #{username}: #{message}\n"
50
+ }
51
+ end
52
+
53
+ # Public: Parse a into a human readable format.
54
+ #
55
+ # chatname - a string that must be normalized.
56
+ #
57
+ # Examples
58
+ #
59
+ # normalize "#john/$doe;2354657"
60
+ # # => john-doe
61
+ #
62
+ # Returns a string without unnecessary characters.
63
+ def normalize(chatname)
64
+ pattern = /^#(.+)\/\$(.+)?;.*$/
65
+ initiator, respondent = chatname.match(pattern).captures
66
+
67
+ "#{initiator}-#{respondent}".gsub(/(^-+|-+$)/, '')
68
+ end
69
+
70
+ # Public: Converts a string that is similar to the chat title stored
71
+ # in the Skype database.
72
+ #
73
+ # dispname - a string that is displayed to the user as a chat title.
74
+ #
75
+ # Examples
76
+ #
77
+ # denormalize "john-doe"
78
+ # # => "#john/$doe;"
79
+ #
80
+ # Returns a string that can be used to query Skype database.
81
+ def denormalize(dispname)
82
+ parts = dispname.split '-'
83
+
84
+ parts.count == 2 ? "##{parts[0]}/$#{parts[1]};" : "##{parts[0]}/$;"
85
+ end
86
+
87
+ private
88
+
89
+ # Internal: Parses a string into a valid file name.
90
+ #
91
+ # chatname - a string that must be converted into a valid filename.
92
+ #
93
+ # Examples
94
+ #
95
+ # get_filename "#john/$doe;2354657"
96
+ # # => john-doe.txt
97
+ #
98
+ # Returns a string that can be used as a file name.
99
+ def get_filename(chatname)
100
+ normalize(chatname) + ".txt"
101
+ end
102
+
103
+ # Internal: removes unnecessary XML code from the message.
104
+ #
105
+ # message - a string to parse
106
+ #
107
+ # Examples
108
+ #
109
+ # parse_xml "<ss type="smile">:)</ss>"
110
+ # # => ":)"
111
+ #
112
+ # Returns an XML-free string.
113
+ def parse_xml(message)
114
+ if message
115
+ message = message.gsub /<ss type=".*">/, ''
116
+ message = message.gsub /<\/ss>/, ''
117
+ message = message.gsub /<a href=".*">/, ''
118
+ message = message.gsub /<\/a>/, ''
119
+ message = message.gsub /&apos;/, "'"
120
+ message = message.gsub /&lt;/, '<'
121
+ message = message.gsub /&gt;/, '>'
122
+ message = message.gsub /&quot;/, '"'
123
+ end
124
+
125
+ message
126
+ end
127
+ end
128
+ end
@@ -1,3 +1,3 @@
1
- module Runoff
2
- VERSION = '0.3.3'
3
- end
1
+ module Runoff
2
+ VERSION = '1.0.0'
3
+ end
data/lib/runoff.rb CHANGED
@@ -1,10 +1,7 @@
1
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
-
3
- require "runoff/version"
4
- require "runoff/location"
5
- require "runoff/skype_data_format"
6
- require "runoff/file_writer"
7
- require "runoff/composition"
8
- require "runoff/commands/command"
9
- require "runoff/commands/all"
10
- require "runoff/commands/chat"
1
+ require 'runoff/version'
2
+ require 'runoff/location'
3
+ require 'runoff/skype_data_format'
4
+ require 'runoff/file_writer'
5
+ require 'runoff/commands/command'
6
+ require 'runoff/commands/all'
7
+ require 'runoff/commands/some'
data/runoff.gemspec CHANGED
@@ -1,26 +1,27 @@
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 'commander'
23
- gem.add_dependency 'sequel'
24
- gem.add_dependency 'rubyzip'
25
- gem.add_dependency 'sqlite3'
26
- end
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/aigarsdz/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 'commander'
23
+ gem.add_dependency 'sequel'
24
+ gem.add_dependency 'rubyzip'
25
+ gem.add_dependency 'sqlite3'
26
+ gem.add_dependency 'colorize'
27
+ end
@@ -1,71 +1,58 @@
1
- require 'minitest/autorun'
2
- require 'minitest/unit'
3
- require 'runoff'
4
-
5
- class TestSkypeDataFormat < MiniTest::Test
6
- def setup
7
- @format = Runoff::SkypeDataFormat.new
8
- end
9
-
10
- def test_must_return_a_valid_filename_from_a_hash
11
- record = { chatname: '#brian/$john;521357125362' }
12
- filename = @format.get_filename record
13
-
14
- assert_equal 'brian-john.txt', filename
15
- end
16
-
17
- def test_must_build_a_string_from_the_necessary_database_record_columns
18
- record = {
19
- chatname: '#brian/$john;521357125362',
20
- timestamp: 1366280218,
21
- from_dispname: 'Aidzis',
22
- body_xml: 'This is a text.'
23
- }
24
-
25
- entry = @format.build_entry record
26
-
27
- assert_equal '[2013-04-18 13:16:58] Aidzis: This is a text.', entry
28
- end
29
-
30
- def test_must_return_a_valid_and_readable_name_from_a_raw_chatname
31
- raw_chatname = '#something/$else;521357125362'
32
- chatname = @format.parse_chatname raw_chatname
33
-
34
- assert_equal 'something-else', chatname
35
- end
36
-
37
- def test_must_return_a_valid_and_readable_name_from_a_broken_chatname_record
38
- raw_chatname = '#something/$521357125362'
39
- chatname = @format.parse_chatname raw_chatname
40
-
41
- assert_equal 'something', chatname
42
- end
43
-
44
- def test_must_return_a_chatname_without_the_extra_symbols
45
- raw_chatname = '#something/$else;521357125362'
46
- chatname = @format.partly_parse_chatname raw_chatname
47
-
48
- assert_equal '#something/$else;', chatname
49
- end
50
-
51
- def test_must_return_a_chatname_without_the_extra_symbols_for_a_broken_chatname_record
52
- raw_chatname = '#something/$521357125362'
53
- chatname = @format.partly_parse_chatname raw_chatname
54
-
55
- assert_equal '#something/$', chatname
56
- end
57
-
58
- def test_must_return_a_string_without_skype_emotion_xml_tags
59
- string = 'Some text <ss type="laugh">:D</ss>'
60
- clean_string = @format.send :parse_body_xml, string
61
-
62
- assert_equal 'Some text :D', clean_string
63
- end
64
-
65
- def test_must_remove_all_starting_and_ending_dashes_from_a_string
66
- string = '---example--'
67
- valid_name = @format.send :trim_dashes, string
68
-
69
- assert_equal 'example', valid_name
70
- end
1
+ require 'minitest/autorun'
2
+ require 'runoff'
3
+
4
+ describe Runoff::SkypeDataFormat do
5
+ before do
6
+ @format = Runoff::SkypeDataFormat.new
7
+ end
8
+
9
+ it "must return a schema of the necessary data" do
10
+ expected = { table: :Messages, columns: [:chatname, :timestamp, :from_dispname, :body_xml] }
11
+
12
+ @format.get_schema.must_equal expected
13
+ end
14
+
15
+ it "must yield a schema of the necessary data to a block" do
16
+ @format.get_schema do |table, columns|
17
+ table.must_equal :Messages
18
+ columns.must_equal [:chatname, :timestamp, :from_dispname, :body_xml]
19
+ end
20
+ end
21
+
22
+ it "must return a hash with 'filename' and 'content' keys based on the input data" do
23
+ fields = {
24
+ chatname: "#john/$doe;1243435",
25
+ from_dispname: "John",
26
+ body_xml: "Lorem ipsum",
27
+ timestamp: 1387756800
28
+ }
29
+
30
+ expected = { filename: "john-doe.txt", content: "[2013-12-23 02:00:00] John: Lorem ipsum\n" }
31
+
32
+ @format.build_entry(fields).must_equal expected
33
+ end
34
+
35
+ it "must normalize a Skype specific chat title into a human readable string" do
36
+ @format.normalize('#john/$doe;2354657').must_equal 'john-doe'
37
+ end
38
+
39
+ it "must normalize a Skype specific chat title into a human readable string even in case of invalid title" do
40
+ @format.normalize('#john/$;2354657').must_equal 'john'
41
+ end
42
+
43
+ it "must parse a Skype specific chat title into a valid file name" do
44
+ @format.send(:get_filename, '#john/$doe;2354657').must_equal 'john-doe.txt'
45
+ end
46
+
47
+ it "must parse a Skype specific chat title into a valid file name even in case of invalid title" do
48
+ @format.send(:get_filename, '#john/$;2354657').must_equal 'john.txt'
49
+ end
50
+
51
+ it "must denormalize a human readable chat title into a string that can be used in a database query" do
52
+ @format.denormalize('john-doe').must_equal '#john/$doe;'
53
+ end
54
+
55
+ it "must denormalize a human readable, invalid chat title into a string that can be used in a database query" do
56
+ @format.denormalize('john').must_equal '#john/$;'
57
+ end
71
58
  end