runoff 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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