runoff 0.3.3 → 1.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,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