mess 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 70d63987314f20d83db49e145f32fb23e5bb1b20608d343672c474f3aea128e4
4
+ data.tar.gz: 3883e201fb6e57978cd55215a9b4951e8c0133d441bd4a9514f4e2cb3f7410df
5
+ SHA512:
6
+ metadata.gz: 98019de61d628b344ab85c05d81e5db2aecbb63cc101e8164cbec8395f871d31b1ac5fa4d56ad859d662d31698747a51f874c6e38a1f580b85198ce537ed90be
7
+ data.tar.gz: '09922eb1f81a2030e9a1fdfc1e8224b3886ad0048281048b4deb62a078d604df59e456eb5f6cb45f9212aed782e5b1275b449bb658f1c3640adc01c29a1f4a81'
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mess.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
@@ -0,0 +1,21 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mess (0.0.0)
5
+ slop (~> 4.8)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ rake (12.3.3)
11
+ slop (4.8.2)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ mess!
18
+ rake (~> 12.0)
19
+
20
+ BUNDLED WITH
21
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 patztablook22
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,65 @@
1
+ # mess
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!
4
+
5
+ ## Installation
6
+ Ruby is required. You can download mess as a Ruby Gem:
7
+ ```bash
8
+ gem install mess
9
+ ```
10
+ Alternatively, after cloning this repo, run:
11
+ ```bash
12
+ bundle exec rake install
13
+ ```
14
+
15
+ ## Facebook archive
16
+ provide the program with **JSON** (!) Facebook archive _(as opposed to HTML format)_
17
+
18
+ ## Examples
19
+ Call `main` either directly or via link.
20
+ ```bash
21
+
22
+ # print help
23
+ main --help
24
+
25
+ # guided analysis of given archive
26
+ main --facebook my/facebook/archive
27
+
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
33
+
34
+ ```
35
+
36
+ ## Contributing
37
+ Mess provides class `Mess::Plot` to be inherited for custom plots; \
38
+ quick example (for more, see `mess/plots` dir):
39
+ ```ruby
40
+ module Mess
41
+ class MyPlot < Plot
42
+
43
+ # this will make MyPlot "available"
44
+ describe "my plot for magic analysis"
45
+
46
+ def initialize chat # constructor
47
+ # blah blah # whatever is needed
48
+ # more code # should be only preparation
49
+ end
50
+
51
+ def push msg # being called for every single message chronologically
52
+ if msg["sender_name"].include? "my"
53
+ # do stuff
54
+ else
55
+ # don't etc.
56
+ end
57
+ end
58
+
59
+ def review # called after all messages have been push-ed
60
+ # body # perhaps reformat the data and so on
61
+ end
62
+
63
+ end
64
+ end
65
+ ```
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mess"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,232 @@
1
+ #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'io/console'
5
+ require 'slop'
6
+ require_relative '../lib/mess'
7
+
8
+ Signal.trap('INT') { exit }
9
+
10
+ $listing = Array.new
11
+
12
+ begin
13
+ opts = Slop.parse do |o|
14
+
15
+ o.banner = "usage: #{$0} [options] target"
16
+
17
+ o.on '--help', 'print help' do
18
+ puts o
19
+ exit
20
+ end
21
+
22
+ o.on '--version', 'print version' do
23
+ puts Mess::VERSION
24
+ exit
25
+ end
26
+
27
+ o.string '--output', '-o', 'output file', default: 'mess_out.txt'
28
+
29
+ o.bool '--archived', '-a', 'archived threads too', default: false
30
+
31
+ o.bool '--list', '-l', 'listing mode'
32
+
33
+ o.array '--chat', '-c', 'chat specification' do
34
+ $listing << :chats
35
+ end
36
+
37
+ o.array '--plot', '-p', 'plot specification' do
38
+ $listing << :plots
39
+ end
40
+
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}"
88
+ exit 1
89
+ end
90
+ end
91
+
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
103
+
104
+ puts
105
+ exit
106
+
107
+ end
108
+
109
+ begin
110
+
111
+ # given directly chat
112
+ chat = Mess::Chat.new(target)
113
+
114
+ rescue Mess::ChatInvalidError
115
+
116
+ # given tree
117
+ begin
118
+ tree = Mess::Tree.new(target, archived: opts.archived?)
119
+ rescue Mess::TreeInvalidError
120
+ $stderr.puts "not a chat/archive: #{target}"
121
+ exit 1
122
+ end
123
+
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
145
+
146
+ end
147
+
148
+ end
149
+
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
163
+ end
164
+
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
171
+
172
+ # failed
173
+ puts
174
+ list_plots Mess::Plot unless failed
175
+ failed = true
176
+ print 'select> '
177
+ input = $stdin.gets.strip.split(/[, ]/).compact
178
+
179
+ end
180
+
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
227
+
228
+ File.open(opts[:output], 'w') do |f|
229
+ f.puts data.to_json
230
+ end
231
+
232
+ puts 'exporting... done'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'json'
5
+ require 'date'
6
+ require_relative 'mess/version'
7
+
8
+ module Mess
9
+ class Error < StandardError; end
10
+ # Your code goes here...
11
+ end
12
+
13
+ require_relative 'mess/error'
14
+ require_relative 'mess/plot'
15
+ Dir["#{__dir__}/mess/**/*.rb"].each { |f| require_relative f }
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ class Chat
5
+
6
+ attr_reader :path, :title, :usrs
7
+
8
+ def initialize path
9
+ @path = File.expand_path path
10
+
11
+ # make sure path exists
12
+ unless File.exists? @path
13
+ raise ChatInvalidError
14
+ end
15
+
16
+ # if file, scope out to the whole chat dir
17
+ if File.file? @path
18
+ @path = File.dirname @path
19
+ end
20
+
21
+ @title = nil
22
+ @usrs = nil
23
+ @msgs = Array.new
24
+
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
43
+ end
44
+
45
+ @title = '*you*' if @title.empty?
46
+ @ready = false
47
+
48
+ end
49
+
50
+ def count
51
+ @msgs.size
52
+ end
53
+
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
+ private
65
+
66
+ def get_json file
67
+ buffer = JSON.parse(File.read(file))
68
+ @title ||= buffer['title']
69
+ @usrs ||= buffer['participants'].map{ |p| p['name'] }.sort
70
+ @msgs += buffer['messages']
71
+ end
72
+
73
+ def get_html file
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ module Mess
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ class ChatInvalidError < Error
7
+ end
8
+
9
+ class TreeInvalidError < Error
10
+ end
11
+
12
+ end
@@ -0,0 +1,30 @@
1
+ module Mess
2
+ class Plot
3
+
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
24
+ end
25
+
26
+ def review
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ class DailyMsgs < Plot
5
+
6
+ describe 'daily messages by user'
7
+
8
+ def initialize chat
9
+ @data = Hash.new
10
+ @usrs = chat.usrs
11
+ oldest = get_day(chat.msgs[0])
12
+ latest = get_day(chat.msgs[-1])
13
+ for day in (oldest..latest)
14
+ @data[day.to_s] = Array.new(@usrs.size, 0)
15
+ end
16
+ end
17
+
18
+ def push msg
19
+ usr = msg['sender_name']
20
+ day = get_day(msg).to_s
21
+ @data[day][@usrs.index(usr)] += 1
22
+ end
23
+
24
+ private
25
+
26
+ def get_day msg
27
+ unix = msg['timestamp_ms'] / 1000
28
+ Date.parse(Time.at(unix).to_s)
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,78 @@
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mess
4
+ VERSION = '0.0.0'
5
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'lib/mess/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "mess"
5
+ spec.version = Mess::VERSION
6
+ spec.authors = ["patztablook22"]
7
+ spec.email = ["patz@tuta.io"]
8
+
9
+ spec.summary = "Facebook Messenger Statistician"
10
+ spec.description = "Analyze your archived Messenger chatlogs in JSON format!"
11
+ spec.homepage = "https://github.com/patztablook22/mess"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(__dir__) do
18
+ Dir["**/*"].reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "slop", "~> 4.8"
25
+ end
Binary file
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mess
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - patztablook22
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-10-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.8'
27
+ description: Analyze your archived Messenger chatlogs in JSON format!
28
+ email:
29
+ - patz@tuta.io
30
+ executables:
31
+ - mess
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - bin/console
41
+ - bin/setup
42
+ - exe/mess
43
+ - lib/mess.rb
44
+ - lib/mess/chat.rb
45
+ - lib/mess/error.rb
46
+ - 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
52
+ - lib/mess/version.rb
53
+ - mess.gemspec
54
+ - pkg/mess-0.0.0.gem
55
+ homepage: https://github.com/patztablook22/mess
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.3.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.1.4
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Facebook Messenger Statistician
78
+ test_files: []