tam_tam 0.0.1
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.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +200 -0
- data/Rakefile +10 -0
- data/lib/tam_tam.rb +1 -0
- data/lib/tam_tam/adapter.rb +29 -0
- data/lib/tam_tam/adapters/adium.rb +58 -0
- data/lib/tam_tam/core.rb +28 -0
- data/lib/tam_tam/log_set.rb +59 -0
- data/lib/tam_tam/message.rb +27 -0
- data/lib/tam_tam/message_set.rb +59 -0
- data/lib/tam_tam/version.rb +3 -0
- data/spec/fixtures/adium/AIM.bongo/bongita/bongita (2011-07-23T14.55.47-0700).chatlog/bongita (2011-07-23T14.55.47-0700).xml +45 -0
- data/spec/fixtures/adium/AIM.bongo/bongita/bongita (2011-07-24T15.10.02-0700).chatlog/bongita (2011-07-24T15.10.02-0700).xml +126 -0
- data/spec/fixtures/adium/GTalk.bongo@gmail.com/rumples@gmail.com/rumples@gmail.com (2012-01-13T11.27.54-0800).chatlog/rumples@gmail.com (2012-01-13T11.27.54-0800).xml +29 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/tam_tam/adapter_spec.rb +29 -0
- data/spec/tam_tam/adapters/adium_spec.rb +63 -0
- data/spec/tam_tam/log_set_spec.rb +101 -0
- data/spec/tam_tam/message_set_spec.rb +63 -0
- data/spec/tam_tam/message_spec.rb +37 -0
- data/spec/tam_tam_spec.rb +27 -0
- data/tam_tam.gemspec +29 -0
- metadata +237 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jimmy Cuadra
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# TamTam
|
2
|
+
|
3
|
+
[](https://travis-ci.org/jimmycuadra/tam_tam)
|
4
|
+
|
5
|
+
**TamTam** is a Ruby gem to parse, filter, and analyze logs from chat clients. You can filter by various factors: date, participants, or contents.
|
6
|
+
|
7
|
+
TamTam is agnostic to operating system and chat client, and ships with adapters for:
|
8
|
+
|
9
|
+
* Adium (OS X)
|
10
|
+
|
11
|
+
Additional adapters are planned, and welcome via pull request.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
On the command line:
|
16
|
+
|
17
|
+
``` bash
|
18
|
+
gem install tam_tam
|
19
|
+
```
|
20
|
+
|
21
|
+
Or via Bundler:
|
22
|
+
|
23
|
+
``` ruby
|
24
|
+
gem "tam_tam"
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### Instantiation
|
30
|
+
|
31
|
+
Create a new logs object with the default Adium adapter:
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
require "tam_tam"
|
35
|
+
logs = TamTam.new
|
36
|
+
```
|
37
|
+
|
38
|
+
Specify an adapter:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
logs = TamTam.new(adapter: :messages)
|
42
|
+
```
|
43
|
+
|
44
|
+
List all registered adapters:
|
45
|
+
|
46
|
+
``` ruby
|
47
|
+
TamTam.adapters # [:adium, :messages]
|
48
|
+
```
|
49
|
+
|
50
|
+
Use a non-standard path for log files:
|
51
|
+
|
52
|
+
``` ruby
|
53
|
+
TamTam.new(path: "/path/to/logs")
|
54
|
+
```
|
55
|
+
|
56
|
+
#### Limiting the loaded adapters
|
57
|
+
|
58
|
+
When you `require "tam_tam"`, all the included adapters are loaded. If you want to save memory and require only the one(s) you need, you may do so:
|
59
|
+
|
60
|
+
``` ruby
|
61
|
+
require "tam_tam/adapters/adium"
|
62
|
+
|
63
|
+
logs = TamTam.new
|
64
|
+
```
|
65
|
+
|
66
|
+
If an adapter is not provided when calling `.new`, TamTam defaults to the Adium adapter, or the first registered adapter if the Adium adapter has not been loaded. Details on using custom adapters are included later in this guide.
|
67
|
+
|
68
|
+
### Filtering logs
|
69
|
+
|
70
|
+
Logs can be filtered by participants and date. All the filter methods are chainable, and return a new `TamTam::Logs` object with the filters applied.
|
71
|
+
|
72
|
+
#### #as
|
73
|
+
|
74
|
+
Limits logs to the account you were chatting as. Accepts any number of string arguments with the username of the account.
|
75
|
+
|
76
|
+
``` ruby
|
77
|
+
logs.as("MyAIMScreenName")
|
78
|
+
logs.as("MyAIMScreenName", "google.talk@gmail.com")
|
79
|
+
```
|
80
|
+
|
81
|
+
#### #with
|
82
|
+
|
83
|
+
Limits logs to the account you were chatting with. Like `as`, accepts any number of string arguments with the username of the account.
|
84
|
+
|
85
|
+
``` ruby
|
86
|
+
logs.with("MyFriendJoe")
|
87
|
+
logs.with("MyFriendJoe", "my.secret.crush@gmail.com")
|
88
|
+
```
|
89
|
+
|
90
|
+
#### #on
|
91
|
+
|
92
|
+
Limits logs to chats that occurred on a particular date. The date can be supplied as a string (any format [Chronic](https://github.com/mojombo/chronic "Chronic") accepts) or a temporal object (`Date`, `Time`, etc.)
|
93
|
+
|
94
|
+
``` ruby
|
95
|
+
logs.on("September 23, 2013")
|
96
|
+
logs.on(Date.today)
|
97
|
+
logs.on(Time.now)
|
98
|
+
```
|
99
|
+
|
100
|
+
#### #between
|
101
|
+
|
102
|
+
Limits logs to chats that occurred within a date range. Takes two dates, which, like `on`, can be strings or temporal objects.
|
103
|
+
|
104
|
+
``` ruby
|
105
|
+
logs.between(5.days.ago, Date.today)
|
106
|
+
```
|
107
|
+
|
108
|
+
### Accessing messages
|
109
|
+
|
110
|
+
Once you have filtered logs down to the set you want, you can examine the messages themselves:
|
111
|
+
|
112
|
+
``` ruby
|
113
|
+
messages = logs.messages
|
114
|
+
```
|
115
|
+
|
116
|
+
At this point, the logs on disk are loaded into memory, so the first call to `messages` may take a while, depending on how many logs are being loaded. The messages object is enumerable, and can receive any of the usual iteration and transformation methods.
|
117
|
+
|
118
|
+
### Filtering messages
|
119
|
+
|
120
|
+
Messages can be filtered by contents. All the filter methods are chainable and return a new `TamTam::MessageSet` object with the filters applied.
|
121
|
+
|
122
|
+
#### #including
|
123
|
+
|
124
|
+
Limits messages to those that include the provided substring.
|
125
|
+
|
126
|
+
``` ruby
|
127
|
+
messages.including("how do you feel about")
|
128
|
+
```
|
129
|
+
|
130
|
+
#### #matching
|
131
|
+
|
132
|
+
Limits messages to those that match the provided regular expression.
|
133
|
+
|
134
|
+
``` ruby
|
135
|
+
messages.matching(/^lol,?\s+/)
|
136
|
+
```
|
137
|
+
|
138
|
+
#### #sent_by
|
139
|
+
|
140
|
+
Limits messages to those sent by the provided username.
|
141
|
+
|
142
|
+
``` ruby
|
143
|
+
messages.sent_by("MyFriendJoe")
|
144
|
+
```
|
145
|
+
|
146
|
+
### Analyzing logs
|
147
|
+
|
148
|
+
These methods return interesting data about the messages.
|
149
|
+
|
150
|
+
#### #by_count
|
151
|
+
|
152
|
+
Returns a hash of all the messages grouped by text and the number of times a message with that text was sent. Useful for seeing what messages/phrases you and your chat buddies say most often.
|
153
|
+
|
154
|
+
``` ruby
|
155
|
+
messages.by_count # { "hi" => 4, "how's it going?" => 2 }
|
156
|
+
```
|
157
|
+
|
158
|
+
### Individual messages
|
159
|
+
|
160
|
+
When enumerating messages, each message is a `TamTam::Message` object with the following attributes:
|
161
|
+
|
162
|
+
#### #sender
|
163
|
+
|
164
|
+
The username of the person who sent the message.
|
165
|
+
|
166
|
+
#### #text
|
167
|
+
|
168
|
+
The body of the message.
|
169
|
+
|
170
|
+
#### #time
|
171
|
+
|
172
|
+
The date and time the message was sent, as a `Time` object.
|
173
|
+
|
174
|
+
## Custom adapters
|
175
|
+
|
176
|
+
An adapter is a class that inherits from `TamTam::Adapter` and implements its abstract interface. Once you have defined your adapter class, register it with TamTam:
|
177
|
+
|
178
|
+
``` ruby
|
179
|
+
TamTam.register_adapter(:crazy_chat, CrazyChatAdapter)
|
180
|
+
```
|
181
|
+
|
182
|
+
Adapters must define the following methods:
|
183
|
+
|
184
|
+
* `.default_path`
|
185
|
+
* `#default_matches`
|
186
|
+
* `#load_messages`
|
187
|
+
* `#as`
|
188
|
+
* `#with`
|
189
|
+
* `#on`
|
190
|
+
* `#between`
|
191
|
+
|
192
|
+
See any of the included adapters for examples.
|
193
|
+
|
194
|
+
## Contributing
|
195
|
+
|
196
|
+
Issues and pull requests are welcome! If you're opening a pull request, please make sure your branch maintains full test coverage (open `coverage/index.html` after running the specs), and that you don't get any warnings from running `cane`.
|
197
|
+
|
198
|
+
## License
|
199
|
+
|
200
|
+
TamTam is available under the MIT license. See the provided `LICENSE.txt` for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "cane/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
Cane::RakeTask.new(:quality) do |cane|
|
7
|
+
cane.add_threshold "coverage/covered_percent", :>=, 100
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => [:spec, :quality]
|
data/lib/tam_tam.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "tam_tam/adapters/adium"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "tam_tam/core"
|
2
|
+
require "tam_tam/log_set"
|
3
|
+
|
4
|
+
module TamTam
|
5
|
+
# An exception to raise when abstract methods have not been implemented.
|
6
|
+
class AbstractMethodError < StandardError; end
|
7
|
+
|
8
|
+
# An abstract base class for all adapters.
|
9
|
+
class Adapter
|
10
|
+
attr_accessor :path
|
11
|
+
|
12
|
+
def initialize(path = nil)
|
13
|
+
self.path = path || self.class.default_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
%{#<#{self.class.name}:0x#{object_id.to_s(16)} path="#{path}">}
|
18
|
+
end
|
19
|
+
alias_method :inspect, :to_s
|
20
|
+
|
21
|
+
%w[default_matches load_messages as with on between].each do |method|
|
22
|
+
define_method(method) do
|
23
|
+
raise AbstractMethodError.new(
|
24
|
+
"##{method} must be defined in the adapter."
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "tam_tam/adapter"
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
module TamTam
|
6
|
+
module Adapters
|
7
|
+
# Adapter for Adium. http://adium.im/
|
8
|
+
class Adium < Adapter
|
9
|
+
def self.default_path
|
10
|
+
"#{Dir.home}/Library/Application Support/Adium 2.0/Users/Default/Logs"
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_matches
|
14
|
+
Dir["#{path}/**/*.xml"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_messages(matches)
|
18
|
+
matches.map do |log_path|
|
19
|
+
xml = File.read(log_path)
|
20
|
+
doc = Nokogiri.parse(xml)
|
21
|
+
|
22
|
+
doc.css("chat message").map do |message|
|
23
|
+
{
|
24
|
+
sender: message.attribute("sender").value,
|
25
|
+
text: message.text,
|
26
|
+
time: Chronic.parse(message.attribute("time").value)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end.flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def as(accounts)
|
33
|
+
Dir["#{path}/*\.{#{accounts.join(',')}}/**/*.xml"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def with(participants)
|
37
|
+
Dir["#{path}/*\.*/{#{participants.join(',')}}/**/*.xml"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def on(start)
|
41
|
+
Dir["#{path}/**/*#{start.to_date.to_s}*/*.xml"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def between(start, stop)
|
45
|
+
start = start.to_date
|
46
|
+
stop = stop.to_date
|
47
|
+
|
48
|
+
default_matches.select do |log|
|
49
|
+
match = log.match(/(\d{4})-(\d{2})-(\d{2})/)
|
50
|
+
log_date = Time.new(match[1], match[2], match[3]).to_date
|
51
|
+
log_date >= start && log_date <= stop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
register_adapter :adium, Adapters::Adium
|
58
|
+
end
|
data/lib/tam_tam/core.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "tam_tam/version"
|
2
|
+
|
3
|
+
# The primary entry point for users.
|
4
|
+
module TamTam
|
5
|
+
@adapters ||= {}
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def new(options = {})
|
9
|
+
adapter_key = options[:adapter]
|
10
|
+
adapter_key = :adium if adapter_key.nil? && adapters.include?(:adium)
|
11
|
+
adapter_key = adapters.first if adapter_key.nil?
|
12
|
+
adapter = @adapters[adapter_key].new(options[:path])
|
13
|
+
LogSet.new(adapter)
|
14
|
+
end
|
15
|
+
|
16
|
+
def register_adapter(key, adapter)
|
17
|
+
@adapters[key] = adapter
|
18
|
+
end
|
19
|
+
|
20
|
+
def unregister_adapter(key)
|
21
|
+
@adapters.delete(key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def adapters
|
25
|
+
@adapters.keys
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "tam_tam/message_set"
|
2
|
+
|
3
|
+
require "chronic"
|
4
|
+
|
5
|
+
module TamTam
|
6
|
+
# A set of chat logs. Responsible for filtering at the chat log level.
|
7
|
+
# Delegates to the adapter for client-specific behavior.
|
8
|
+
class LogSet
|
9
|
+
attr_accessor :adapter, :matches
|
10
|
+
|
11
|
+
def initialize(adapter)
|
12
|
+
self.adapter = adapter
|
13
|
+
self.matches = adapter.default_matches
|
14
|
+
end
|
15
|
+
|
16
|
+
def messages
|
17
|
+
@messages ||= MessageSet.new(adapter.load_messages(matches))
|
18
|
+
end
|
19
|
+
|
20
|
+
def as(*accounts)
|
21
|
+
filter(adapter.as(accounts))
|
22
|
+
end
|
23
|
+
|
24
|
+
def with(*participants)
|
25
|
+
filter(adapter.with(participants))
|
26
|
+
end
|
27
|
+
|
28
|
+
def on(start)
|
29
|
+
start = Chronic.parse(start) if start.is_a?(String)
|
30
|
+
filter(adapter.on(start))
|
31
|
+
end
|
32
|
+
|
33
|
+
def between(start, stop)
|
34
|
+
start = Chronic.parse(start) if start.is_a?(String)
|
35
|
+
stop = Chronic.parse(stop) if stop.is_a?(String)
|
36
|
+
filter(adapter.between(start, stop))
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
%{#<#{self.class.name}:0x#{object_id.to_s(16)} adapter=#{adapter.to_s}>}
|
41
|
+
end
|
42
|
+
alias_method :inspect, :to_s
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def clear_messages_cache
|
47
|
+
@messages = nil if defined?(@messages)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def filter(filter_matches)
|
53
|
+
copy = dup
|
54
|
+
copy.clear_messages_cache
|
55
|
+
copy.matches = matches & filter_matches
|
56
|
+
copy
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|