runoff 1.0.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 916e6a7065a8cc0db0d4d1622ec2b4ef22a6d81e
4
- data.tar.gz: a9294104f13e7413cb485ac18985e843ffe2125d
3
+ metadata.gz: d4aeef27e77018f295faf0e6f43909ddf9516f71
4
+ data.tar.gz: 13d13befb22b12e797673c36448a44dfed2467e8
5
5
  SHA512:
6
- metadata.gz: 719fd36c30dc187090d40894a858a3bcd8c1767743c3ff09df9d31771b837e26f721e7a667b56c0e29e6355f8b7524343f60d565fa3fa09b1e699c4102240a36
7
- data.tar.gz: 02d6a60b016a2639b3ab2aa94826061c2a0140bc4c00ddc5793fd49b1ae931e218fc5c0587a38d0495a30f48117f178ab4fac67516cecf7dcf66b99bd25d3878
6
+ metadata.gz: 2a739d9f63e5176c2563de12886bf4bfcddaf955446e55a6a714d734710e3a3743218569ca6fd78c25677f598eb2fed56b04fd6ccf8e3d04632fa78d9bf2d835
7
+ data.tar.gz: d58f520e774251942f003a61adcb6576364db95d0656b39584157fe7ea37eaae6a4338f4f5ebf896f15a690c195af89878d76bb160f44d4a831c4f6052729511
data/.gitignore CHANGED
@@ -1,18 +1,32 @@
1
1
  *.gem
2
2
  *.rbc
3
- .bundle
4
- .config
5
- .yardoc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Documentation cache and generated files:
13
+ /.yardoc/
14
+ /_yardoc/
15
+ /doc/
16
+ /rdoc/
17
+
18
+ ## Environment normalisation:
19
+ /.bundle/
20
+ /lib/bundler/man/
21
+
22
+ # for a library or gem, you might want to ignore these files since the code is
23
+ # intended to run in multiple environments; otherwise, check them in:
6
24
  Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- .DS_Store
25
+ .ruby-version
26
+ .ruby-gemset
27
+
28
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
29
+ .rvmrc
30
+
31
+ # additional stuff
32
+ .DS_Store
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # runoff
2
2
  ## About
3
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.
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 was called SDBR and it was 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
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 command-line tool, that automates the process of exporting your chat history.
6
+ SDBR was written in C# using WPF, therefore it ran only on Windows. Moreover, it was a GUI application. Yeah, that's a problem, because you don't need the GUI for this kind of functionality. runoff is a command-line tool, that automates the process of exporting your chat history.
7
7
 
8
8
  ## Install
9
9
 
@@ -26,12 +26,18 @@ To export specific chats.
26
26
 
27
27
  runoff some skype_username
28
28
 
29
- If you don't want to put files into an archive, use `--no-archive` option
29
+ If you don't want to put files into an archive, use `--no-archive` option.
30
30
 
31
31
  runoff all skype_username --no-archive
32
32
 
33
33
  runoff some skype_username --no-archive
34
34
 
35
+ Sometimes you might want to use the exported data in a different app, therefore runoff has an option to export in JSON instead of TXT. You just need to specify an adapter using `-A` option.
36
+
37
+ runoff all skype_username -A json
38
+
39
+ The resulting JSON file contains an array of objects with 3 keys: `date`, `user` and `message`. Each object represents a single chat record.
40
+
35
41
  ## What else?
36
42
 
37
43
  Things to do in the future versions:
data/bin/runoff CHANGED
@@ -1,58 +1,31 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
-
5
- require 'commander/import'
6
- require 'runoff'
7
3
  require 'colorize'
8
4
 
9
- program :name, 'runoff'
10
- program :version, Runoff::VERSION
11
- program :description, 'runoff is a simple application to create Skype backups'
12
- program :help, 'Author', 'Aigars Dzerviniks <dzerviniks.aigars@outlook.com>'
13
-
14
- default_command :help
15
-
16
- command :all do |c|
17
- c.syntax = 'runoff all [SKYPE_USERNAME] [OPTIONS]'
18
- c.description = 'Export all chat history'
19
- c.option '-f', '--from FILE', 'Location of the main.db file'
20
- c.option '-d', '--destination DIR', 'Location for the exoprted files'
21
- c.option '--[no-]archive', 'Indicates whether to create an archive'
22
-
23
- c.action do |args, options|
24
- begin
25
- puts "Exporting...".colorize :green
26
- Runoff::Commands::All.process args, options
27
- puts "Export finished.".colorize :green
28
- rescue ArgumentError => e
29
- puts e.message.colorize :red
30
- rescue Sequel::DatabaseError
31
- puts "Error: To use runoff you must make sure that the Skype is not running".colorize :red
32
- rescue StandardError => e
33
- puts e.message.colorize :red
34
- end
35
- end
36
- end
37
-
38
- command :some do |c|
39
- c.syntax = 'runoff some [SKYPE_USERNAME] [OPTIONS]'
40
- c.description = "Export only some chats' history"
41
- c.option '-f', '--from FILE', 'Location of the main.db file'
42
- c.option '-d', '--destination DIR', 'Location for the exoprted files'
43
- c.option '--[no-]archive', 'Indicates whether to create an archive'
44
-
45
- c.action do |args, options|
46
- begin
47
- puts "Exporting...".colorize :green
48
- Runoff::Commands::Some.process args, options
49
- puts "Export finished.".colorize :green
50
- rescue ArgumentError => e
51
- puts e.message.colorize :red
52
- rescue Sequel::DatabaseError
53
- puts "Error: To use runoff you must make sure that the Skype is not running".colorize :red
54
- rescue StandardError => e
55
- puts e.message.colorize :red
56
- end
57
- end
5
+ require_relative '../lib/runoff/commandline/none'
6
+ require_relative '../lib/runoff/commandline/all'
7
+ require_relative '../lib/runoff/commandline/some'
8
+
9
+ options = { archive: true, adapter: 'TxtAdapter' }
10
+ available_commands = {
11
+ '_none' => Runoff::Commandline::None.new(options),
12
+ 'all' => Runoff::Commandline::All.new(options),
13
+ 'some' => Runoff::Commandline::Some.new(options)
14
+ }
15
+
16
+ available_commands['_none'].parser.order!
17
+ command = available_commands[ARGV.shift]
18
+
19
+ if command
20
+ command.parser.order!
21
+ command.parser.parse!
22
+
23
+ #begin
24
+ command.execute ARGV
25
+ #rescue StandardError => e
26
+ # warn e.message.colorize :red
27
+ # puts 'Terminated.'.colorize :yellow
28
+ #end
29
+ else
30
+ puts available_commands['_none'].parser.help
58
31
  end
@@ -0,0 +1,31 @@
1
+ module Runoff
2
+ module Adapters
3
+ class Adapter
4
+ protected
5
+
6
+ # Internal: Converts a Unix timestamp to a datetime string.
7
+ #
8
+ # timestamp - An integer with a Unix timestamp value.
9
+ #
10
+ # Examples
11
+ #
12
+ # format_timestamp 1397852412
13
+ # # => "2014-18-04 20:20:12"
14
+ #
15
+ # Returns a datetime string in a format of YYY-DD-MM HH:MM::SS
16
+ def format_timestamp(timestamp)
17
+ Time.at(timestamp).strftime '%Y-%m-%d %H:%M:%S'
18
+ end
19
+
20
+ # Internal: Skip.
21
+ def format_from_dispname(dispname)
22
+ dispname
23
+ end
24
+
25
+ # Internal: Skip.
26
+ def format_body_xml(xml_content)
27
+ xml_content
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,88 @@
1
+ require 'json'
2
+
3
+ module Runoff
4
+ module Adapters
5
+ class JsonAdapter < Adapter
6
+ # Public: A format String used to build a single entry.
7
+ ENTRY_FORMAT = '{ "date": "%s", "user": "%s", "message": %s }'
8
+
9
+ # Public: Builds a single entry.
10
+ #
11
+ # row - An Array containing a single row of data from the database.
12
+ #
13
+ # Examples
14
+ #
15
+ # build_entry { chatname: "#first_user/$second_user;d3d86c6b0e3b8320" ... }
16
+ # # => "{ "date": "2014-04-18 20:20:12", "user": "first_user", "message": "This is a text" }"
17
+ def build_entry(row)
18
+ formated_data = []
19
+
20
+ # NOTE: The first column in the array is used for the grouping by id and
21
+ # the second is used for the filename.
22
+ Runoff::COLUMNS[2..-1].each do |column|
23
+ formated_data << send("format_#{column}", row[column])
24
+ end
25
+
26
+ ENTRY_FORMAT % formated_data
27
+ end
28
+
29
+ # Public: returns a file name.
30
+ #
31
+ # chatname - A String with a Skype chatname
32
+ #
33
+ # Examples
34
+ #
35
+ # get_file_name "#first_user/$second_user;d3d86c6b0e3b8320"
36
+ # # => first_user_second_user.json
37
+ #
38
+ # Returns a valid file name.
39
+ def get_file_name(chatname)
40
+ parse_chatname(chatname) + '.json'
41
+ end
42
+
43
+ # Public: Parses a chatname into a human readable name.
44
+ #
45
+ # raw_chatname - A String with a Skype chatname.
46
+ #
47
+ # Examples
48
+ #
49
+ # parse_chatname "#first_user/$second_user;d3d86c6b0e3b8320"
50
+ # # => first_user_second_user
51
+ #
52
+ # Returns a valid name.
53
+ def parse_chatname(raw_chatname)
54
+ pattern = /^#(.*)\/\$(.*);.*$/
55
+ parts = raw_chatname.match(pattern).captures
56
+
57
+ parts.reject(&:empty?).join('_')
58
+ end
59
+
60
+ # Public: Formats the provided data buffer so that it could be writter to
61
+ # a JSON file.
62
+ #
63
+ # buffer - An Array containing all the chat entries.
64
+ #
65
+ # Returns a String
66
+ def format_file_content(buffer)
67
+ content = buffer.join(",\n")
68
+
69
+ "[#{content}]"
70
+ end
71
+
72
+ protected
73
+
74
+ # Internal: Escapes the message body so that it could be used in a JSON string
75
+ #
76
+ # xml_content - A String containing the chat message
77
+ #
78
+ # Examples
79
+ #
80
+ # format_body_xml 'foo' # => "\"foo\""
81
+ #
82
+ # Returns a String
83
+ def format_body_xml(xml_content)
84
+ JSON.generate(xml_content, quirks_mode: true)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,69 @@
1
+ module Runoff
2
+ module Adapters
3
+ class TxtAdapter < Adapter
4
+ # Public: A format String used to build a single entry.
5
+ ENTRY_FORMAT = "[%s] %s: %s"
6
+
7
+ # Public: Builds a single entry.
8
+ #
9
+ # row - An Array containing a single row of data from the database.
10
+ #
11
+ # Examples
12
+ #
13
+ # build_entry { chatname: "#first_user/$second_user;d3d86c6b0e3b8320" ... }
14
+ # # => "[2014-04-18 20:20:12] first_user: This is a text"
15
+ def build_entry(row)
16
+ formated_data = []
17
+
18
+ # NOTE: The first column in the array is used for the grouping by id and
19
+ # the second is used for the filename.
20
+ Runoff::COLUMNS[2..-1].each do |column|
21
+ formated_data << send("format_#{column}", row[column])
22
+ end
23
+
24
+ ENTRY_FORMAT % formated_data
25
+ end
26
+
27
+ # Public: returns a file name.
28
+ #
29
+ # chatname - A String with a Skype chatname
30
+ #
31
+ # Examples
32
+ #
33
+ # get_file_name "#first_user/$second_user;d3d86c6b0e3b8320"
34
+ # # => first_user_second_user.txt
35
+ #
36
+ # Returns a valid file name.
37
+ def get_file_name(chatname)
38
+ parse_chatname(chatname) + '.txt'
39
+ end
40
+
41
+ # Public: Parses a chatname into a human readable name.
42
+ #
43
+ # raw_chatname - A String with a Skype chatname.
44
+ #
45
+ # Examples
46
+ #
47
+ # parse_chatname "#first_user/$second_user;d3d86c6b0e3b8320"
48
+ # # => first_user_second_user
49
+ #
50
+ # Returns a valid name.
51
+ def parse_chatname(raw_chatname)
52
+ pattern = /^#(.*)\/\$(.*);.*$/
53
+ parts = raw_chatname.match(pattern).captures
54
+
55
+ parts.reject(&:empty?).join('_')
56
+ end
57
+
58
+ # Public: Formats the provided data buffer so that it could be writter to
59
+ # a text file.
60
+ #
61
+ # buffer - An Array containing all the chat entries.
62
+ #
63
+ # Returns a String
64
+ def format_file_content(buffer)
65
+ buffer.join("\n")
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,38 @@
1
+ require 'sequel'
2
+ require 'set'
3
+
4
+ module Runoff
5
+ # Public: Reads data from the SQLite database used by Skype/
6
+ class Chat
7
+
8
+ # Public: Initializes a Chat object.
9
+ #
10
+ # db_location - A String with a path to the database file.
11
+ def initialize(db_location, options)
12
+ @messages = Sequel.sqlite(db_location)[Runoff::TABLE]
13
+ @adapter = Object.const_get("Runoff::Adapters::#{options[:adapter]}").new
14
+ end
15
+
16
+ # Public: Returns a list of all the records in the databse.
17
+ def get_messages
18
+ @messages.select(*Runoff::COLUMNS).all.sort_by do |row|
19
+ [row[Runoff::COLUMNS[0]], row[Runoff::COLUMNS[2]]]
20
+ end
21
+ end
22
+
23
+ # Public: Creates a collection with all chats available for export.
24
+ #
25
+ # Returns a Set with hashes e.g. [{ id: 12, name: "chatname" }, ... ]
26
+ def get_chatname_options
27
+ options = Set.new
28
+
29
+ @messages.select(*Runoff::COLUMNS[0..1]).each do |row|
30
+ readable_name = @adapter.parse_chatname row[Runoff::COLUMNS[1]]
31
+
32
+ options << { id: row[Runoff::COLUMNS[0]], name: readable_name }
33
+ end
34
+
35
+ options
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'command'
2
+
3
+ module Runoff
4
+ # Public: Command classes used by the executable.
5
+ module Commandline
6
+ # Public: A command class that is used to export all chat history.
7
+ #
8
+ # Examples
9
+ #
10
+ # command = All.new { archive: true }
11
+ # command.execute [ 'skype_username' ]
12
+ class All < Command
13
+ # Public: initialize a new All command object.
14
+ #
15
+ # options - A Hash of commandline options (default { archive: true, adapter: 'TxtAdapter' }).
16
+ def initialize(options = {})
17
+ @options = options
18
+ @parser = OptionParser.new do |opts|
19
+ opts.banner = 'Exports all chats: runoff all [SKYPE_USERNAME] [OPTIONS]'
20
+ opts.on '-h', '--help', 'Displays help' do
21
+ puts opts
22
+ exit
23
+ end
24
+
25
+ opts.on '-f', '--from PATH', 'Specifies the location of the database file (main.db)' do |path|
26
+ @options[:from] = path
27
+ end
28
+
29
+ opts.on '-d', '--destination PATH', 'Changes the default path for the exported files' do |path|
30
+ @options[:destination] = path
31
+ end
32
+
33
+ opts.on '-a', '--[no-]archive', 'Toggles archiving feature' do |enable|
34
+ @options[:archive] = enable
35
+ end
36
+
37
+ opts.on '-A', '--adapter [NAME]', 'Uses a specific file type adapter' do |adapter|
38
+ @options[:adapter] = adapter.capitalize + 'Adapter'
39
+ end
40
+ end
41
+ end
42
+
43
+ # Public: executes the command.
44
+ #
45
+ # args - An Array of commandline arguments.
46
+ def execute(args)
47
+ super args do |chat, file_writer|
48
+ file_writer.write chat.get_messages
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,34 @@
1
+ require 'optparse'
2
+ require 'colorize'
3
+
4
+ require_relative '../../runoff'
5
+
6
+ module Runoff
7
+ # Public: Command classes used by the executable.
8
+ module Commandline
9
+ # Public: A base class for all runoff commands except None.
10
+ #
11
+ # Should be used only by inheriting.
12
+ class Command
13
+ # Public: Returns an OptionParser object
14
+ attr_reader :parser
15
+
16
+ # Public: executes the command.
17
+ #
18
+ # args - An Array of commandline arguments.
19
+ def execute(args)
20
+ puts 'Exporting...'.colorize :green
21
+
22
+ db_location = Location.get_database_path args, @options
23
+ chat = Chat.new db_location, @options
24
+ file_writer = FileWriter.new @options
25
+
26
+ yield chat, file_writer if block_given?
27
+
28
+ file_writer.archive if @options[:archive]
29
+
30
+ puts 'Finished.'.colorize :green
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ require 'optparse'
2
+ require_relative '../../runoff.rb'
3
+
4
+ module Runoff
5
+ # Public: Command classes used by the executable.
6
+ module Commandline
7
+ # Public: The default class that is used when the executable is called
8
+ # without any commands.
9
+ #
10
+ # Examples
11
+ #
12
+ # command = None.new { archive: false }
13
+ class None
14
+ # Public: Returns an OptionParser object
15
+ attr_reader :parser
16
+
17
+ # Public: initialize a new None command object.
18
+ #
19
+ # options - A Hash of commandline options (default { archive: false }).
20
+ def initialize(options = {})
21
+ @option = options
22
+ @parser = OptionParser.new do |opts|
23
+ opts.banner = <<END
24
+ runoff - a simple application to create Skype backups
25
+
26
+ Usage:
27
+
28
+ runoff <COMMAND> [SKYPE_USERNAME] [OPTIONS]
29
+
30
+ END
31
+
32
+ opts.on '-h', '--help', 'Displays help' do
33
+ puts opts
34
+ exit
35
+ end
36
+
37
+ opts.on '-v', '--version', 'Displays a version number' do
38
+ puts Runoff::VERSION
39
+ exit
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ require_relative 'command'
2
+ require 'pry'
3
+
4
+ module Runoff
5
+ # Public: Command classes used by the executable.
6
+ module Commandline
7
+ # Public: A command class that is used to export specified chat history.
8
+ #
9
+ # Examples
10
+ #
11
+ # command = Some.new { archive: true }
12
+ # command.execute [ 'skype_username' ]
13
+ class Some < Command
14
+ # Public: initialize a new All command object.
15
+ #
16
+ # options - A Hash of commandline options (default { archive: true }).
17
+ def initialize(options = {})
18
+ @options = options
19
+ @parser = OptionParser.new do |opts|
20
+ opts.banner = 'Exports specified chats: runoff some [SKYPE_USERNAME] [OPTIONS]'
21
+ opts.on '-h', '--help', 'Displays help' do
22
+ puts opts
23
+ exit
24
+ end
25
+
26
+ opts.on '-f', '--from PATH', 'Specifies the location of the database file (main.db)' do |path|
27
+ @options[:from] = path
28
+ end
29
+
30
+ opts.on '-d', '--destination PATH', 'Changes the default path for the exported files' do |path|
31
+ @options[:destination] = path
32
+ end
33
+
34
+ opts.on '-a', '--[no-]archive', 'Toggles archiving feature' do |enable|
35
+ @options[:archive] = enable
36
+ end
37
+
38
+ opts.on '-A', '--adapter [NAME]', 'Uses a specific file type adapter' do |adapter|
39
+ @options[:adapter] = adapter.capitalize + 'Adapter'
40
+ end
41
+ end
42
+ end
43
+
44
+ # Public: executes the command.
45
+ #
46
+ # args - An Array of commandline arguments.
47
+ def execute(args)
48
+ super args do |chat, file_writer|
49
+ ids = prompt_for_chatnames chat
50
+ messages = chat.get_messages
51
+
52
+ selected_messages = messages.keep_if { |m| ids.include? m[Runoff::COLUMNS[0]] }
53
+
54
+ file_writer.write selected_messages
55
+ end
56
+ end
57
+
58
+ # Public: Asks user to specify, which chats to export.
59
+ #
60
+ # chat - A Chat object.
61
+ #
62
+ # Examples
63
+ #
64
+ # get_requested_chatnames Chat.new('/path/to/main.db')
65
+ # # => [120, 86, 201]
66
+ #
67
+ # Returns an Array of conversation ids.
68
+ def prompt_for_chatnames(chat)
69
+ puts 'Specify numbers of the chats that you want to export (e.g., 201, 86, 120)'
70
+ puts
71
+
72
+ chat.get_chatname_options.each do |opt|
73
+ puts "[#{opt[:id]}] #{opt[:name]}"
74
+ end
75
+
76
+ puts
77
+ print "Numbers: "
78
+ options = STDIN.gets # NOTE: For some reason just gets throws an error here.
79
+
80
+ options.split(',').map(&:to_i)
81
+ end
82
+ end
83
+ end
84
+ end