mess 0.0.0 → 0.0.2

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
  SHA256:
3
- metadata.gz: 70d63987314f20d83db49e145f32fb23e5bb1b20608d343672c474f3aea128e4
4
- data.tar.gz: 3883e201fb6e57978cd55215a9b4951e8c0133d441bd4a9514f4e2cb3f7410df
3
+ metadata.gz: d71090b50fd3a172418162b58191baa839adedbbc51b5608f63aaf8f162a95a4
4
+ data.tar.gz: b6b8c3df531c2cd6656c69039448229ce065d8a8c1c90ca0cfaf7601eb51cfe1
5
5
  SHA512:
6
- metadata.gz: 98019de61d628b344ab85c05d81e5db2aecbb63cc101e8164cbec8395f871d31b1ac5fa4d56ad859d662d31698747a51f874c6e38a1f580b85198ce537ed90be
7
- data.tar.gz: '09922eb1f81a2030e9a1fdfc1e8224b3886ad0048281048b4deb62a078d604df59e456eb5f6cb45f9212aed782e5b1275b449bb658f1c3640adc01c29a1f4a81'
6
+ metadata.gz: c3ad3c5ae653e7fddfd09957476d7b5a3220ab252e4b0ff79c49116228c338a75d86e96b44ab15ba1ff66e17dc7857b274e9ceb9e7aa3d01c971b03b2b5f8f1e
7
+ data.tar.gz: e6b5e2e7056217c635744c15e9497dbc2d465e14b175403f6a549c308d77c1275bfcf6980053964836f0887036f27453a5cbe14e624757e3bc839cff3faf5043
data/Gemfile.lock CHANGED
@@ -1,14 +1,28 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mess (0.0.0)
5
- slop (~> 4.8)
4
+ mess (0.0.1)
5
+ tty-option (~> 0.1)
6
+ tty-prompt (~> 0.23)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
11
+ pastel (0.8.0)
12
+ tty-color (~> 0.5)
10
13
  rake (12.3.3)
11
- slop (4.8.2)
14
+ tty-color (0.6.0)
15
+ tty-cursor (0.7.1)
16
+ tty-option (0.1.0)
17
+ tty-prompt (0.23.0)
18
+ pastel (~> 0.8)
19
+ tty-reader (~> 0.8)
20
+ tty-reader (0.9.0)
21
+ tty-cursor (~> 0.7)
22
+ tty-screen (~> 0.8)
23
+ wisper (~> 2.0)
24
+ tty-screen (0.8.1)
25
+ wisper (2.0.1)
12
26
 
13
27
  PLATFORMS
14
28
  ruby
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mess
2
2
 
3
- Ruby tool for analysis of native Facebook Messenger Archives in JSON format. Output is universal (also JSON) and can thus be used by table processors (like MS Excell / LibreOffice Calc) and more!
3
+ Ruby tool for analysis of Facebook Messenger conversations in JSON format. Output is table-processor-friendly (like MS Excell / LibreOffice Calc). Independently of `mess` executable, an API is provided for more nuanced use.
4
4
 
5
5
  ## Installation
6
6
  Ruby is required. You can download mess as a Ruby Gem:
@@ -22,14 +22,11 @@ Call `main` either directly or via link.
22
22
  # print help
23
23
  main --help
24
24
 
25
- # guided analysis of given archive
26
- main --facebook my/facebook/archive
25
+ # interactive prompt for given archive
26
+ main my/facebook/information
27
27
 
28
28
  # analysis specifying chat and plots
29
- main --facebook my/facebook/archive --chat 3 --plot asdf
30
-
31
- # list all available chats and plots for given archive
32
- main --facebook my/facebook/archive --list --chat --plot
29
+ main my/facebook/information/chat --plot DailyMsgs
33
30
 
34
31
  ```
35
32
 
@@ -38,9 +35,9 @@ Mess provides class `Mess::Plot` to be inherited for custom plots; \
38
35
  quick example (for more, see `mess/plots` dir):
39
36
  ```ruby
40
37
  module Mess
41
- class MyPlot < Plot
38
+ class MyPlotter < Plotter
42
39
 
43
- # this will make MyPlot "available"
40
+ # this will make MyPlotter "available"
44
41
  describe "my plot for magic analysis"
45
42
 
46
43
  def initialize chat # constructor
@@ -49,16 +46,23 @@ module Mess
49
46
  end
50
47
 
51
48
  def push msg # being called for every single message chronologically
52
- if msg["sender_name"].include? "my"
49
+ if msg.body.include? "my"
53
50
  # do stuff
54
51
  else
55
52
  # don't etc.
56
53
  end
57
54
  end
58
55
 
59
- def review # called after all messages have been push-ed
56
+ def finalize # called after all messages have been push-ed
60
57
  # body # perhaps reformat the data and so on
61
58
  end
59
+
60
+ def generate_plot # output a unified-format plot for further manipulation
61
+ p = Plot.new
62
+ p.data = @data
63
+ p.head = @myHead
64
+ return p
65
+ end
62
66
 
63
67
  end
64
68
  end
data/exe/mess CHANGED
@@ -1,232 +1,125 @@
1
- #! /usr/bin/env ruby
1
+ #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'io/console'
5
- require 'slop'
4
+ require 'tty-option'
5
+ require 'tty-prompt'
6
6
  require_relative '../lib/mess'
7
7
 
8
- Signal.trap('INT') { exit }
8
+ class Command
9
+ include TTY::Option
9
10
 
10
- $listing = Array.new
11
+ usage do
12
+ program 'mess'
13
+ command "\b"
14
+ desc 'analyze conversation'
11
15
 
12
- begin
13
- opts = Slop.parse do |o|
16
+ end
14
17
 
15
- o.banner = "usage: #{$0} [options] target"
18
+ argument :path do
19
+ required
20
+ desc 'inbox or chat directory path'
21
+ end
16
22
 
17
- o.on '--help', 'print help' do
18
- puts o
19
- exit
20
- end
23
+ flag :help do
24
+ long '--help'
25
+ desc 'Print usage'
26
+ end
21
27
 
22
- o.on '--version', 'print version' do
23
- puts Mess::VERSION
24
- exit
25
- end
28
+ flag :archived do
29
+ short '-a'
30
+ long '--archived'
31
+ desc 'list archived conversations too'
32
+ end
26
33
 
27
- o.string '--output', '-o', 'output file', default: 'mess_out.txt'
34
+ flag :version do
35
+ long '--version'
36
+ desc 'print version'
37
+ end
28
38
 
29
- o.bool '--archived', '-a', 'archived threads too', default: false
39
+ option :plot do
40
+ short '-p'
41
+ long '--plot name'
42
+ desc 'plot to be generated'
43
+ end
30
44
 
31
- o.bool '--list', '-l', 'listing mode'
45
+ option :output do
46
+ short '-o'
47
+ long '--output path'
48
+ desc 'output data path'
49
+ end
32
50
 
33
- o.array '--chat', '-c', 'chat specification' do
34
- $listing << :chats
51
+ def run
52
+ if params[:help]
53
+ print help
54
+ exit
35
55
  end
36
56
 
37
- o.array '--plot', '-p', 'plot specification' do
38
- $listing << :plots
57
+ if params[:version]
58
+ puts Mess::VERSION
59
+ exit
39
60
  end
40
61
 
41
- end
42
- rescue Slop::MissingArgument => e
43
- p e.message
44
- rescue Slop::UnknownOption => e
45
- puts e
46
- exit 1
47
- end
48
-
49
- target = opts&.arguments || Array.new
50
- target << '.' if target.none?
51
-
52
- if target.one?
53
- target = target.first
54
- else
55
- $stderr.puts 'too many targets'
56
- exit 1
57
- end
58
-
59
- def list_chats tree
60
- puts "chats"
61
- tree.chats.each_with_index do |chat, index|
62
- index += 1
63
- index = index.to_s.rjust(5)
64
- puts "#{index} | #{chat.title}"
65
- end
66
- end
67
-
68
- def list_plots plots
69
- puts "plots"
70
- index = 1
71
- plots.available.each_pair do |name, desc|
72
- puts "#{index.to_s.rjust(5)} | #{name.ljust(15)} -> #{desc}"
73
- index += 1
74
- end
75
- end
76
-
77
- # listing mode
78
- if opts.list?
79
-
80
- # chats
81
- if $listing.include? :chats
82
- begin
83
- tree = Mess::Tree.new(target)
84
- puts
85
- list_chats tree
86
- rescue Mess::TreeInvalidError
87
- $stderr.puts "not an archive: #{target}"
62
+ if params[:path].nil?
63
+ print help
88
64
  exit 1
89
65
  end
90
66
  end
67
+ end
91
68
 
92
- # plots
93
- if $listing.include? :plots
94
- puts
95
- list_plots Mess::Plot
96
- end
97
-
98
- # none
99
- if $listing.none?
100
- $stderr.puts 'no specification flag for listing'
101
- exit 1
102
- end
69
+ cmd = Command.new
70
+ cmd.parse.run
103
71
 
104
- puts
105
- exit
72
+ prompt = TTY::Prompt.new
106
73
 
107
- end
74
+ todo = nil
108
75
 
109
76
  begin
110
-
111
- # given directly chat
112
- chat = Mess::Chat.new(target)
113
-
77
+ todo = Mess::Chat.new(cmd.params[:path])
114
78
  rescue Mess::ChatInvalidError
115
-
116
- # given tree
117
79
  begin
118
- tree = Mess::Tree.new(target, archived: opts.archived?)
119
- rescue Mess::TreeInvalidError
120
- $stderr.puts "not a chat/archive: #{target}"
80
+ todo = Mess::FacebookInformation.new(cmd.params[:path], cmd.params[:archived])
81
+ rescue Mess::FacebookInformationInvalidError
82
+ puts 'path is not a chat nor an inbox'
121
83
  exit 1
122
84
  end
85
+ end
123
86
 
124
- # select chat
125
- input = opts[:chat].join
126
- chat = nil
127
- failed = false
128
- loop do
129
-
130
- # search for nick or index
131
- chat = tree.chats.find { |c| c.title == input }
132
- chat ||= tree.chats[input.to_i - 1] if input.to_i.to_s == input and (1..tree.chats.size).include? input.to_i
133
- unless chat.nil?
134
- puts if failed
135
- break
136
- end
137
-
138
- # failed
139
- puts
140
- list_chats tree unless failed
141
- failed = true
142
-
143
- print 'select> '
144
- input = $stdin.gets.chomp
87
+ begin
145
88
 
89
+ if todo.class == Mess::FacebookInformation
90
+ todo = prompt.select(
91
+ 'Select chat:',
92
+ todo.chats.map { |c| [c.title, c] }.to_h
93
+ )
94
+ #todo = todo.chats.find { |c| c.title == temp }
146
95
  end
147
96
 
148
- end
97
+ plot = cmd.params[:plot]
149
98
 
150
- # select plot
151
- input = opts[:plot]
152
- plot = nil
153
- failed = false
154
- loop do
155
-
156
- # map indexes to names
157
- input.map! do |p|
158
- if p.to_i.to_s == p and (1..Mess::Plot.available.size).include? p.to_i
159
- Mess::Plot.available.keys[p.to_i - 1]
160
- else
161
- p
162
- end
99
+ if Mess::Plotter.available.keys.include? plot
100
+ plot = Mess.const_get(plot)
101
+ else
102
+ plot = prompt.select(
103
+ 'Select plot:',
104
+ Mess::Plotter.available.keys.map { |p| [p, Mess.const_get(p)] }.to_h
105
+ )
163
106
  end
164
107
 
165
- # validate names
166
- if input.any? and input.all? { |p| Mess::Plot.available.include? p }
167
- plot = Set.new(input)
168
- puts if failed
169
- break
170
- end
108
+ cmd.params[:output] ||= prompt.ask('Output path:', default: "./mess-out-#{Time.now.to_i}.txt")
171
109
 
172
- # failed
110
+ rescue TTY::Reader::InputInterrupt
173
111
  puts
174
- list_plots Mess::Plot unless failed
175
- failed = true
176
- print 'select> '
177
- input = $stdin.gets.strip.split(/[, ]/).compact
178
-
112
+ exit 1
179
113
  end
180
114
 
181
- # prepare
182
- puts 'preparing...'
183
- i = 1
184
- plot.map! do |p|
185
- $stdout.cursor_up 1
186
- puts "preparing... #{i}/#{plot.size}"
187
- Mess.const_get(p).new(chat)
188
- end
189
- $stdout.cursor_up 1
190
- puts 'preparing... done'
191
-
192
- # analyze
193
- total = chat.count
194
- pcapx = total / 100
195
- check = 0
196
- chat.msgs.each_with_index do |m, i|
197
- if i >= check
198
- $stdout.cursor_up 1
199
- puts "analyzing... #{100 * i / total}% "
200
- check += pcapx
201
- end
202
- plot.each do |p|
203
- p.push m #rescue nil
204
- end
205
- end
206
- $stdout.cursor_up 1
207
- puts 'analyzing... done'
208
-
209
- # review
210
- puts
211
- plot.each_with_index do |p, i|
212
- $stdout.cursor_up 1
213
- puts "reviewing... #{i + 1}/#{plot.size}"
214
- p.review
215
- end
216
- $stdout.cursor_up 1
217
- puts 'reviewing... done'
218
-
219
- # export
220
- $stdout.cursor_up 1
221
- puts 'exporting...'
222
- data = Hash.new
223
- plot.each do |p|
224
- data[p.class.name] = p.data
225
- end
226
- $stdout.cursor_up 1
115
+ p = plot.new(todo)
116
+ r = p.run
227
117
 
228
- File.open(opts[:output], 'w') do |f|
229
- f.puts data.to_json
118
+ File.open(cmd.params[:output], 'w') do |f|
119
+ ([r.head] + r.data).each do |row|
120
+ row.each do |col|
121
+ f << col << "\t"
122
+ end
123
+ f << "\n"
124
+ end
230
125
  end
231
-
232
- puts 'exporting... done'
data/lib/mess.rb CHANGED
@@ -1,15 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
3
  require 'json'
4
+ require 'csv'
5
5
  require 'date'
6
- require_relative 'mess/version'
6
+ require 'iconv'
7
7
 
8
8
  module Mess
9
9
  class Error < StandardError; end
10
10
  # Your code goes here...
11
11
  end
12
12
 
13
+ require_relative 'mess/version'
13
14
  require_relative 'mess/error'
15
+ require_relative 'mess/msg'
16
+ require_relative 'mess/chat'
14
17
  require_relative 'mess/plot'
15
- Dir["#{__dir__}/mess/**/*.rb"].each { |f| require_relative f }
18
+ require_relative 'mess/plotter'
19
+ require_relative 'mess/facebook_information'
20
+
21
+ Dir["#{__dir__}/mess/plotter/*.rb"].each { |f| require_relative f }
data/lib/mess/chat.rb CHANGED
@@ -1,77 +1,86 @@
1
- # frozen_string_literal: true
1
+ # frozen string_literal: true
2
+
3
+ # Mess::Chat
4
+ # class wrapping a single Messenger conversation (Group/Person)
5
+ # initializer takes single conversation directory path as an argument
6
+ # (contains text files, attachmed photos and other files etc.)
7
+ # chat.title -> get conversation tytle
8
+ # chat.size -> count messages
9
+ # chat.usrs -> participant list
10
+ # chat.msgs -> all messages
2
11
 
3
12
  module Mess
4
13
  class Chat
5
-
6
- attr_reader :path, :title, :usrs
7
-
14
+ attr_reader :path, :title, :usrs, :msgs
15
+
8
16
  def initialize path
9
- @path = File.expand_path path
17
+ @path = File.expand_path(path)
10
18
 
11
- # make sure path exists
12
- unless File.exists? @path
13
- raise ChatInvalidError
14
- end
19
+ # make sure the path is somewhat valid
20
+ raise ChatInvalidError unless File.exists? @path
15
21
 
16
- # if file, scope out to the whole chat dir
17
- if File.file? @path
18
- @path = File.dirname @path
19
- end
22
+ # maybe given a file within the directory instead? -> scope out
23
+ @path = File.dirname(@path) if File.file? @path
20
24
 
25
+ # prepare data buffers
21
26
  @title = nil
22
27
  @usrs = nil
23
28
  @msgs = Array.new
24
29
 
25
- # exactly one message_1.ext file should exist
26
- tmp = Dir["#@path/message_1\\.*"]
27
- raise ChatInvalidError unless tmp.one?
28
- tmp = tmp.first
29
-
30
- # also store its extension
31
- ext = File.extname tmp # ".json" or ".html"...
32
- ext = ext[1..-1] # "json" or "html"...
33
-
34
- # reference the get_ext function
35
- get = method("get_#{ext}")
36
-
37
- # chronologically iterate through message_X.ext chat files
38
- # decrementally for some reason reee
39
- total = Dir["#@path/message_*\\.#{ext}"].size
40
- for i in (0...total)
41
- file = "#@path/message_#{total - i}.#{ext}"
42
- get.call(file) rescue raise ChatInvalidError
30
+ # either .json or .html -> message_1.ext to find out the format
31
+ message_1 = Dir["#@path/message_1\\.*"]
32
+ raise ChatInvalidError unless message_1.one?
33
+ message_1 = message_1.first
34
+ extension = File.extname(message_1)
35
+ extension = extension[1..-1] # remove the dot in .ext
36
+
37
+ # reference the get_extension function
38
+ get = method("get_#{extension}")
39
+
40
+ # iterate through message_X.ext and use get method to parse the files
41
+ # do it chronologically -> descending X for messenger (reeee)
42
+ total = Dir["#@path/message_*\\.#{extension}"].size
43
+ total.downto(1) do |i|
44
+ file = "#@path/message_#{i}.#{extension}"
45
+ get.call(file) #rescue raise ChatInvalidError
43
46
  end
44
47
 
45
- @title = '*you*' if @title.empty?
46
- @ready = false
47
-
48
+ @title = '*you*' if @title.nil? or @title.empty?
49
+ @msgs.sort_by!(&:time)
50
+ @usrs.sort!
48
51
  end
49
52
 
50
- def count
53
+ def size
51
54
  @msgs.size
52
55
  end
53
56
 
54
- def prepare
55
- @msgs.sort! { |a, b| a['timestamp_ms'] <=> b['timestamp_ms'] }
56
- end
57
-
58
- def msgs
59
- prepare unless @ready
60
- @ready = true
61
- @msgs
62
- end
63
-
64
57
  private
65
58
 
59
+ # parse json file
66
60
  def get_json file
67
- buffer = JSON.parse(File.read(file))
61
+ # get data
62
+ buffer = File.read(file)
63
+ buffer = JSON.parse(buffer)
64
+
65
+ # get metadata if not already done before (each file contains it)
68
66
  @title ||= buffer['title']
69
67
  @usrs ||= buffer['participants'].map{ |p| p['name'] }.sort
70
- @msgs += buffer['messages']
68
+
69
+ # append messages
70
+ buffer['messages'].each do |hash|
71
+ msg = Msg.new
72
+ msg.from = hash['sender_name']
73
+ msg.body = hash['content']
74
+ msg.time = hash['timestamp_ms']
75
+ msg.type = hash['type']
76
+ @msgs << msg
77
+ @usrs << msg.from unless @usrs.include? msg.from
78
+ end
71
79
  end
72
80
 
81
+ # parse html file
73
82
  def get_html file
83
+ raise ChatInvalidError
74
84
  end
75
-
76
85
  end
77
86
  end
data/lib/mess/error.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mess
2
4
 
3
5
  class Error < StandardError
@@ -6,7 +8,7 @@ module Mess
6
8
  class ChatInvalidError < Error
7
9
  end
8
10
 
9
- class TreeInvalidError < Error
11
+ class FacebookInformationInvalidError < Error
10
12
  end
11
13
 
12
14
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ class FacebookInformation
5
+ attr_reader :root, :chats
6
+
7
+ def initialize path, archived_too = false
8
+ path = File.expand_path(path)
9
+
10
+ # find the closest thing to facebook information copy root
11
+ # while gradually ascending
12
+ begin
13
+ break unless File.exists? path
14
+ next unless is_root? path
15
+ @root = path
16
+ end until (path = File.dirname(path)) == '/'
17
+
18
+ raise FacebookInformationInvalidError if @root.nil?
19
+
20
+ @chats = Array.new
21
+ to_list = Array.new
22
+ to_list << "inbox"
23
+ to_list << "archived_threads" if archived_too
24
+ to_list.each do |i|
25
+ list_chats "#@root/#{i}"
26
+ end
27
+ @chats.sort_by!(&:title)
28
+ end
29
+
30
+ private
31
+
32
+ # checks if given path is a root of a facebook information copy
33
+ def is_root? path
34
+ [
35
+ '/',
36
+ '/inbox',
37
+ '/archived_threads',
38
+ ].all? { |dir| File.directory?(path + dir) }
39
+ end
40
+
41
+ def list_chats inbox_dir
42
+ Dir["#{inbox_dir}/*"].each do |chat_dir|
43
+ base = File.basename(chat_dir)
44
+ @chats << Chat.new(chat_dir) rescue nil
45
+ end
46
+ end
47
+
48
+ end
49
+ end
data/lib/mess/msg.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # structure holding message data
4
+
5
+ module Mess
6
+ class Msg
7
+ attr_accessor :from, :body, :time, :type
8
+ end
9
+ end
data/lib/mess/plot.rb CHANGED
@@ -1,29 +1,17 @@
1
1
  module Mess
2
2
  class Plot
3
+ attr_accessor :head, :data
3
4
 
4
- @@available = Hash.new
5
-
6
- def self.available
7
- @@available
8
- end
9
-
10
- def self.describe desc
11
- @@available[name] = desc
12
- end
13
-
14
- def self.name
15
- self.to_s.match(/Mess::(?<name>\w+)$/)[:name]
16
- end
17
-
18
- attr_reader :data
19
-
20
- def initialize chat
21
- end
22
-
23
- def push msg
5
+ def initialize
6
+ @head = Array.new
7
+ @data = Array.new
24
8
  end
25
9
 
26
- def review
10
+ def export_data
11
+ puts @head.join("\t");
12
+ @data.each do |d|
13
+ puts d.join("\t");
14
+ end
27
15
  end
28
16
 
29
17
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # base class for plotters
4
+ # initializer should take in Chat class to be analyzed
5
+ #
6
+ # method push is feeded all messages chronologically
7
+ # after pushing all messages, finalize is called to format data etc.
8
+
9
+ module Mess
10
+ class Plotter
11
+
12
+ # stores all available plotters
13
+ @@available = Hash.new
14
+
15
+ # lists all available plotters
16
+ def self.available
17
+ @@available
18
+ end
19
+
20
+ attr_reader :data
21
+
22
+ # needs to be described in order to be "centrally" available
23
+ # adds plotter class to available plotters with custom description
24
+ # name is derived from plotter class name
25
+ def self.describe desc
26
+ name = self.to_s.match(/Mess::(?<name>\w+)$/)[:name]
27
+ @@available[name] = desc
28
+ end
29
+
30
+ def initialize chat
31
+ @chat = chat
32
+ end
33
+
34
+ # should return Mess::Plot
35
+ def run
36
+ @chat.msgs.each { |msg| push(msg) }
37
+ finalize
38
+ generate_plot
39
+ end
40
+
41
+ private
42
+
43
+ # called for every message in chronological order
44
+ def push msg
45
+ end
46
+
47
+ def finalize
48
+ end
49
+
50
+ def generate_plot
51
+ Plot.new
52
+ end
53
+
54
+ end
55
+ end
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mess
4
- class DailyMsgs < Plot
4
+ class DailyMsgs < Plotter
5
5
 
6
6
  describe 'daily messages by user'
7
7
 
8
8
  def initialize chat
9
+ super
9
10
  @data = Hash.new
10
- @usrs = chat.usrs
11
+ @usrs = @chat.usrs
11
12
  oldest = get_day(chat.msgs[0])
12
13
  latest = get_day(chat.msgs[-1])
13
14
  for day in (oldest..latest)
@@ -15,16 +16,26 @@ module Mess
15
16
  end
16
17
  end
17
18
 
19
+ private
20
+
21
+ def generate_plot
22
+ p = Plot.new
23
+ p.head << 'Date'
24
+ p.head += @usrs
25
+ @data.each do |date, msgs|
26
+ p.data << [date] + msgs
27
+ end
28
+ p
29
+ end
30
+
18
31
  def push msg
19
- usr = msg['sender_name']
32
+ usr = msg.from
20
33
  day = get_day(msg).to_s
21
34
  @data[day][@usrs.index(usr)] += 1
22
35
  end
23
36
 
24
- private
25
-
26
37
  def get_day msg
27
- unix = msg['timestamp_ms'] / 1000
38
+ unix = msg.time / 1000
28
39
  Date.parse(Time.at(unix).to_s)
29
40
  end
30
41
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ class TotalMsgs < Plotter
5
+
6
+ describe 'total messages by user'
7
+
8
+ def initialize chat
9
+ super
10
+ @data = Hash.new
11
+ chat.usrs.each do |u|
12
+ @data[u] = 0
13
+ puts u
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def push msg
20
+ unless @data.include? msg.from
21
+ puts msg.from
22
+ end
23
+ @data[msg.from] += 1
24
+ end
25
+
26
+ def generate_plot
27
+ p = Plot.new
28
+ p.head = ['User', 'Total Messages']
29
+ p.data = @data.to_a
30
+ p
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ class WordFrequency < Plotter
5
+
6
+ describe 'most frequent words by user'
7
+
8
+ def initialize chat
9
+ super
10
+ @data = Hash.new
11
+ chat.usrs.each do |u|
12
+ @data[u] = Hash.new
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def generate_plot
19
+ p = Plot.new
20
+ p.head << 'Index'
21
+ @data.each_key do |usr|
22
+ p.head += ["Word - #{usr}", "Frequency - #{usr}"]
23
+ end
24
+ i = 0
25
+ loop do
26
+ line = [i + 1]
27
+ @data.each_value do |data|
28
+ buff = Array.new(2)
29
+ word = data.to_a[i]
30
+ buff = word unless word.nil?
31
+ line += buff
32
+ end
33
+ break if line.compact.size == 1
34
+ p.data << line
35
+ i += 1
36
+ end
37
+ p
38
+ end
39
+
40
+ def push msg
41
+ return if msg.body.nil?
42
+ words = msg.body.split(/[ \t\n]/)
43
+ words.each do |w|
44
+ w.strip!
45
+ next if w.empty?
46
+ w.downcase!
47
+ @data[msg.from][w] = @data[msg.from][w].to_i + 1
48
+ end
49
+ end
50
+
51
+ def finalize
52
+ @data.each_key do |usr|
53
+ @data[usr] = @data[usr].sort_by { |key, value| -value }
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ class WordsPerMessage < Plotter
5
+
6
+ describe 'average words per message by user'
7
+
8
+ def initialize chat
9
+ super
10
+ @data = Hash.new
11
+ @msgs = Hash.new
12
+ @words = Hash.new
13
+ chat.usrs.each do |u|
14
+ @data[u] = nil
15
+ @msgs[u] = 0
16
+ @words[u] = 0
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def generate_plot
23
+ p = Plot.new
24
+ p.head = ['User', 'Words per Message']
25
+ p.data = @data.to_a
26
+ p
27
+ end
28
+
29
+ def push msg
30
+ @msgs[msg.from] += 1
31
+ return if msg.body.class != String
32
+ @words[msg.from] += msg.body.strip.squeeze.count("\n\t ")
33
+ end
34
+
35
+ def finalize
36
+ @data.each_key do |u|
37
+ @data[u] = @words[u].to_f / @msgs[u]
38
+ end
39
+ end
40
+
41
+ end
42
+ end
data/lib/mess/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mess
4
- VERSION = '0.0.0'
4
+ VERSION = "0.0.2"
5
5
  end
data/mess.gemspec CHANGED
@@ -21,5 +21,6 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_dependency "slop", "~> 4.8"
24
+ spec.add_dependency "tty-prompt", "~> 0.23"
25
+ spec.add_dependency "tty-option", "~> 0.1"
25
26
  end
Binary file
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mess
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - patztablook22
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-26 00:00:00.000000000 Z
11
+ date: 2021-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: slop
14
+ name: tty-prompt
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.8'
19
+ version: '0.23'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.8'
26
+ version: '0.23'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-option
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
27
41
  description: Analyze your archived Messenger chatlogs in JSON format!
28
42
  email:
29
43
  - patz@tuta.io
@@ -43,15 +57,17 @@ files:
43
57
  - lib/mess.rb
44
58
  - lib/mess/chat.rb
45
59
  - lib/mess/error.rb
60
+ - lib/mess/facebook_information.rb
61
+ - lib/mess/msg.rb
46
62
  - lib/mess/plot.rb
47
- - lib/mess/plot/daily_msgs.rb
48
- - lib/mess/plot/total_delay.rb
49
- - lib/mess/plot/total_lettres.rb
50
- - lib/mess/plot/total_msgs.rb
51
- - lib/mess/tree.rb
63
+ - lib/mess/plotter.rb
64
+ - lib/mess/plotter/daily_msgs.rb
65
+ - lib/mess/plotter/total_msgs.rb
66
+ - lib/mess/plotter/word_frequency.rb
67
+ - lib/mess/plotter/words_per_msg.rb
52
68
  - lib/mess/version.rb
53
69
  - mess.gemspec
54
- - pkg/mess-0.0.0.gem
70
+ - pkg/mess-0.0.1.gem
55
71
  homepage: https://github.com/patztablook22/mess
56
72
  licenses:
57
73
  - MIT
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mess
4
- class TotalDelay < Plot
5
-
6
- describe 'total delay by user'
7
-
8
- def initialize chat
9
- @last = nil
10
- @time = Hash.new
11
- @data = Hash.new
12
- chat.usrs.each do |u|
13
- @data[u] = 0
14
- end
15
- end
16
-
17
- def push msg
18
-
19
- usr = msg["sender_name"]
20
- time = msg["timestamp_ms"]
21
-
22
- if @time[usr].to_i < time
23
- @time[usr] = time
24
- end
25
-
26
- if @last.nil? or @last == usr
27
- @last ||= usr
28
- return
29
- end
30
-
31
- @data[usr] += time - @time[@last]
32
- @last = usr
33
-
34
- end
35
-
36
- end
37
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mess
4
- class TotalLetters < Plot
5
-
6
- describe 'total letters per user'
7
-
8
- def initialize chat
9
- @data = Hash.new
10
- chat.usrs.each do |u|
11
- @data[u] = 0
12
- end
13
- end
14
-
15
- def push msg
16
- usr = msg['sender_name']
17
- letters = msg['content'].to_s.length
18
- @data[usr] += letters
19
- end
20
-
21
- end
22
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mess
4
- class TotalMsgs < Plot
5
-
6
- describe 'total messages by user'
7
-
8
- def initialize chat
9
- @data = Hash.new
10
- chat.usrs.each do |u|
11
- @data[u] = 0
12
- end
13
- end
14
-
15
- def push msg
16
- usr = msg['sender_name']
17
- @data[usr] += 1
18
- end
19
-
20
- end
21
- end
data/lib/mess/tree.rb DELETED
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mess
4
- class Tree
5
-
6
- attr_reader :root, :chats
7
-
8
- def initialize path, flags = Hash.new
9
-
10
- # find the root from given path
11
-
12
- path = File.expand_path(path)
13
- begin
14
-
15
- unless File.exists? path
16
- break
17
- end
18
-
19
- if check(path)
20
- @root = path
21
- break
22
- else
23
- next
24
- end
25
-
26
- end until (path = File.dirname(path)) == "/"
27
-
28
- raise TreeInvalidError if @root.nil?
29
-
30
- import flags[:archived]
31
-
32
- end
33
-
34
- def check root
35
- [
36
- '/',
37
- '/inbox',
38
- '/archived_threads',
39
- ].all? { |dir| File.directory?(root + dir) }
40
- end
41
-
42
- private
43
-
44
- def import archived
45
-
46
- @chats = Array.new
47
-
48
- box = Array.new
49
- box << 'inbox'
50
- box << 'archived_threads' if archived
51
-
52
- # find all text chat dirs
53
- box.each do |b|
54
- Dir["#@root/#{b}/*"].each do |dir|
55
-
56
- # exclude asset dirs (capitalized)
57
- base = File.basename dir
58
- next unless base.downcase == base
59
-
60
- # list it
61
- begin
62
- @chats << Chat.new(dir)
63
- rescue
64
- end
65
-
66
- end
67
- end
68
-
69
- @chats.sort! { |a, b| a.title <=> b.title }
70
-
71
- end
72
-
73
- def select i
74
- Chat.new(@index[i.to_i].to_s)
75
- end
76
-
77
- end
78
- end
data/pkg/mess-0.0.0.gem DELETED
Binary file