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 +4 -4
- data/Gemfile.lock +17 -3
- data/README.md +15 -11
- data/exe/mess +83 -190
- data/lib/mess.rb +9 -3
- data/lib/mess/chat.rb +57 -48
- data/lib/mess/error.rb +3 -1
- data/lib/mess/facebook_information.rb +49 -0
- data/lib/mess/msg.rb +9 -0
- data/lib/mess/plot.rb +9 -21
- data/lib/mess/plotter.rb +55 -0
- data/lib/mess/{plot → plotter}/daily_msgs.rb +17 -6
- data/lib/mess/plotter/total_msgs.rb +34 -0
- data/lib/mess/plotter/word_frequency.rb +58 -0
- data/lib/mess/plotter/words_per_msg.rb +42 -0
- data/lib/mess/version.rb +1 -1
- data/mess.gemspec +2 -1
- data/pkg/mess-0.0.1.gem +0 -0
- metadata +27 -11
- data/lib/mess/plot/total_delay.rb +0 -37
- data/lib/mess/plot/total_lettres.rb +0 -22
- data/lib/mess/plot/total_msgs.rb +0 -21
- data/lib/mess/tree.rb +0 -78
- data/pkg/mess-0.0.0.gem +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d71090b50fd3a172418162b58191baa839adedbbc51b5608f63aaf8f162a95a4
|
4
|
+
data.tar.gz: b6b8c3df531c2cd6656c69039448229ce065d8a8c1c90ca0cfaf7601eb51cfe1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
5
|
-
|
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
|
-
|
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
|
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
|
-
#
|
26
|
-
main
|
25
|
+
# interactive prompt for given archive
|
26
|
+
main my/facebook/information
|
27
27
|
|
28
28
|
# analysis specifying chat and plots
|
29
|
-
main
|
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
|
38
|
+
class MyPlotter < Plotter
|
42
39
|
|
43
|
-
# this will make
|
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
|
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
|
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
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require '
|
5
|
-
require '
|
4
|
+
require 'tty-option'
|
5
|
+
require 'tty-prompt'
|
6
6
|
require_relative '../lib/mess'
|
7
7
|
|
8
|
-
|
8
|
+
class Command
|
9
|
+
include TTY::Option
|
9
10
|
|
10
|
-
|
11
|
+
usage do
|
12
|
+
program 'mess'
|
13
|
+
command "\b"
|
14
|
+
desc 'analyze conversation'
|
11
15
|
|
12
|
-
|
13
|
-
opts = Slop.parse do |o|
|
16
|
+
end
|
14
17
|
|
15
|
-
|
18
|
+
argument :path do
|
19
|
+
required
|
20
|
+
desc 'inbox or chat directory path'
|
21
|
+
end
|
16
22
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
flag :help do
|
24
|
+
long '--help'
|
25
|
+
desc 'Print usage'
|
26
|
+
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
28
|
+
flag :archived do
|
29
|
+
short '-a'
|
30
|
+
long '--archived'
|
31
|
+
desc 'list archived conversations too'
|
32
|
+
end
|
26
33
|
|
27
|
-
|
34
|
+
flag :version do
|
35
|
+
long '--version'
|
36
|
+
desc 'print version'
|
37
|
+
end
|
28
38
|
|
29
|
-
|
39
|
+
option :plot do
|
40
|
+
short '-p'
|
41
|
+
long '--plot name'
|
42
|
+
desc 'plot to be generated'
|
43
|
+
end
|
30
44
|
|
31
|
-
|
45
|
+
option :output do
|
46
|
+
short '-o'
|
47
|
+
long '--output path'
|
48
|
+
desc 'output data path'
|
49
|
+
end
|
32
50
|
|
33
|
-
|
34
|
-
|
51
|
+
def run
|
52
|
+
if params[:help]
|
53
|
+
print help
|
54
|
+
exit
|
35
55
|
end
|
36
56
|
|
37
|
-
|
38
|
-
|
57
|
+
if params[:version]
|
58
|
+
puts Mess::VERSION
|
59
|
+
exit
|
39
60
|
end
|
40
61
|
|
41
|
-
|
42
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
105
|
-
exit
|
72
|
+
prompt = TTY::Prompt.new
|
106
73
|
|
107
|
-
|
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
|
-
|
119
|
-
rescue Mess::
|
120
|
-
|
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
|
-
|
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
|
-
|
97
|
+
plot = cmd.params[:plot]
|
149
98
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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
|
-
|
110
|
+
rescue TTY::Reader::InputInterrupt
|
173
111
|
puts
|
174
|
-
|
175
|
-
failed = true
|
176
|
-
print 'select> '
|
177
|
-
input = $stdin.gets.strip.split(/[, ]/).compact
|
178
|
-
|
112
|
+
exit 1
|
179
113
|
end
|
180
114
|
|
181
|
-
|
182
|
-
|
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(
|
229
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
7
|
-
|
14
|
+
attr_reader :path, :title, :usrs, :msgs
|
15
|
+
|
8
16
|
def initialize path
|
9
|
-
@path = File.expand_path
|
17
|
+
@path = File.expand_path(path)
|
10
18
|
|
11
|
-
# make sure path
|
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
|
-
#
|
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
|
-
#
|
26
|
-
|
27
|
-
raise ChatInvalidError unless
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
get
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
@
|
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
|
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
|
-
|
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
|
-
|
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
@@ -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
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
|
-
|
5
|
-
|
6
|
-
|
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
|
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
|
data/lib/mess/plotter.rb
ADDED
@@ -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 <
|
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
|
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
|
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
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 "
|
24
|
+
spec.add_dependency "tty-prompt", "~> 0.23"
|
25
|
+
spec.add_dependency "tty-option", "~> 0.1"
|
25
26
|
end
|
data/pkg/mess-0.0.1.gem
ADDED
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.
|
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:
|
11
|
+
date: 2021-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: tty-prompt
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
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: '
|
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/
|
48
|
-
- lib/mess/
|
49
|
-
- lib/mess/
|
50
|
-
- lib/mess/
|
51
|
-
- lib/mess/
|
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.
|
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
|
data/lib/mess/plot/total_msgs.rb
DELETED
@@ -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
|