nosy 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/README.md +113 -0
- data/lib/.DS_Store +0 -0
- data/lib/nosy/hunter.rb +12 -12
- data/lib/nosy/parser/imessage.rb +24 -0
- data/lib/nosy/parser/message_support.rb +50 -0
- data/lib/nosy/parser/parse_checks.rb +28 -0
- data/lib/nosy/parser/smsmessage.rb +20 -0
- data/lib/nosy/parser.rb +29 -102
- data/lib/nosy/searcher.rb +26 -26
- data/lib/nosy.rb +16 -12
- data/nosy.gemspec +13 -0
- data/spec/.DS_Store +0 -0
- data/spec/nosy/.DS_Store +0 -0
- data/spec/nosy/hunter_spec.rb +13 -0
- data/spec/nosy/parser_spec.rb +207 -0
- data/spec/nosy/searcher_spec.rb +103 -0
- data/spec/nosy_spec.rb +48 -0
- data/spec/support/.DS_Store +0 -0
- data/spec/support/db/.DS_Store +0 -0
- data/spec/support/db/empty_iphone_database.sqlite +0 -0
- data/spec/support/db/incorrect_format_texts.sqlite +0 -0
- data/spec/support/db/invalid_file_type_texts.txt +1 -0
- data/spec/support/db/missing_table_texts.sqlite +0 -0
- data/spec/support/db/texts.db +0 -0
- data/spec/support/db/texts.sqlite +0 -0
- data/spec/support/db/this_file_does_not_exist.sqlite +0 -0
- metadata +33 -21
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# Nosy
|
2
|
+
|
3
|
+
**Output, backup, and read all your iPhone text messages.**
|
4
|
+
|
5
|
+
Nosy extracts text messages from the native iOS (iPhone, iPad) database stored on your computer after each backup and organizes it into an array of easily explorable messages.
|
6
|
+
|
7
|
+
Nosy returns an array of messages with the fields
|
8
|
+
|
9
|
+
* receiver
|
10
|
+
* sender
|
11
|
+
* message
|
12
|
+
* imessage (Boolean)
|
13
|
+
* date (contains a unix timestamp)
|
14
|
+
|
15
|
+
|
16
|
+
## Install
|
17
|
+
|
18
|
+
$ gem install nosy
|
19
|
+
|
20
|
+
Or add Nosy to your Gemfile and run `bundle install`.
|
21
|
+
|
22
|
+
## Find and Parse
|
23
|
+
If you are on a Mac OSX system, Nosy (is nosy, and) will try to grab your iOS message database and parse it.
|
24
|
+
|
25
|
+
all_texts = Nosy.hunt_and_parse
|
26
|
+
|
27
|
+
all_texts.each do |text|
|
28
|
+
text.receiver
|
29
|
+
text.sender
|
30
|
+
text.message
|
31
|
+
text.imessage
|
32
|
+
text.date
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
## Location
|
37
|
+
The location of the folder storing the iOS message backups in Mac OSX is
|
38
|
+
|
39
|
+
$ ~/Library/Application Support/MobileSync/Backup/
|
40
|
+
|
41
|
+
Each of the folders will represent an Apple device. Within your desired device's folder, the iOS message database will be the file entitled `3d0d7e5fb2ce288813306e4d4636395e047a3d28`
|
42
|
+
|
43
|
+
|
44
|
+
### Hunt
|
45
|
+
|
46
|
+
If you are on Mac OSX running Nosy.hunt will return a String with the location of the first parseable iOS message database if there is one.
|
47
|
+
|
48
|
+
require 'nosy'
|
49
|
+
|
50
|
+
new_text_backup_file_location = Nosy.hunt
|
51
|
+
|
52
|
+
|
53
|
+
## Parsing
|
54
|
+
|
55
|
+
If you pass in an iOS message database, Nosy can detect if a file can be parsed as an iPhone database.
|
56
|
+
|
57
|
+
valid = Nosy.can_parse?(iphone_database_location)
|
58
|
+
|
59
|
+
Also, Nosy can parse the file and return an array of all available text messages.
|
60
|
+
|
61
|
+
all_texts = Nosy.parse(iphone_database_location)
|
62
|
+
|
63
|
+
all_texts.each do |text|
|
64
|
+
text.receiver
|
65
|
+
text.sender
|
66
|
+
text.message
|
67
|
+
text.imessage
|
68
|
+
text.date
|
69
|
+
end
|
70
|
+
|
71
|
+
## Searching
|
72
|
+
|
73
|
+
Nosy can search through the texts in your iOS message database. Nosy searching supports any combination of the following keys:
|
74
|
+
|
75
|
+
* receiver
|
76
|
+
* sender
|
77
|
+
* message
|
78
|
+
* imessage
|
79
|
+
* date - supports a Unix timestamp and an optional leading operator [ < , > , <= , >= ]
|
80
|
+
|
81
|
+
#
|
82
|
+
|
83
|
+
nosty_texts = Nosy.new(iphone_database_location)
|
84
|
+
|
85
|
+
# Array of texts where I am the sender
|
86
|
+
my_sent_texts = nosty_texts.search(:sender => "me")
|
87
|
+
|
88
|
+
# Array of texts where the date is less than 1273175931
|
89
|
+
before_then_texts = nosty_texts.search(:date => "<1273175931")
|
90
|
+
|
91
|
+
# Array of texts where I am the sender and the date is less than 1273175931
|
92
|
+
before_then_texts = nosty_texts.search(:sender => "me", :date => "<1273175931")
|
93
|
+
|
94
|
+
## Contributions
|
95
|
+
Contributions, improvements, and suggestions more than welcome! Please!
|
96
|
+
|
97
|
+
### Some Ideas
|
98
|
+
|
99
|
+
Currently, Nosy is yet to...
|
100
|
+
|
101
|
+
* Properly handle MMS (group and media) messages.
|
102
|
+
* Let you search for two values in one key.
|
103
|
+
* Let you hunt for the iOS message database on systems other than Mac OSX.
|
104
|
+
* Support creating a backup file.
|
105
|
+
* Run on Heroku due to its dependency on Sqlite3.
|
106
|
+
|
107
|
+
## Acknowledgment
|
108
|
+
|
109
|
+
* For a similar implementation in Python check out [iPhone SMS Backup](https://github.com/toffer/iphone-sms-backup/)
|
110
|
+
* Inspiration from Gabe Berke-Williams's [Chat Stew](https://github.com/gabebw/chat_stew)
|
111
|
+
* [Mike Coutermarsh](https://github.com/mscoutermarsh)
|
112
|
+
* [Sarah Canieso](https://github.com/scanieso)
|
113
|
+
* [Eric Kelly](https://github.com/HeroicEric) - Providing additional SMS Databases
|
data/lib/.DS_Store
ADDED
Binary file
|
data/lib/nosy/hunter.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
|
3
1
|
module Nosy
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
def hunt
|
8
|
-
if RUBY_PLATFORM =~ /darwin/
|
9
|
-
FileUtils.cp "#{Dir.home}/Library/Application Support/MobileSync/Backup/55e67384ae82dd8a317f29585e1d7b5884e43107/3d0d7e5fb2ce288813306e4d4636395e047a3d28", "#{FileUtils.pwd}/texts.sqlite"
|
10
|
-
"#{FileUtils.pwd}/texts.sqlite"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
3
|
+
class Hunter
|
15
4
|
|
5
|
+
def hunt
|
6
|
+
if RUBY_PLATFORM =~ /darwin/
|
7
|
+
Dir[File.join("#{Dir.home}/Library/Application Support/MobileSync/Backup", "*")].select{|file| File.ftype(file) == "directory"}.each do |folder|
|
8
|
+
if Nosy.can_parse?(folder+"/3d0d7e5fb2ce288813306e4d4636395e047a3d28")
|
9
|
+
return "#{folder}/3d0d7e5fb2ce288813306e4d4636395e047a3d28"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
16
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Nosy
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
|
5
|
+
module Imessage
|
6
|
+
|
7
|
+
def sent_imessage( parsed_message, message )
|
8
|
+
parsed_message.receiver = format_address(message[7])
|
9
|
+
parsed_message.sender = "me"
|
10
|
+
end
|
11
|
+
|
12
|
+
def received_imessage( parsed_message, message )
|
13
|
+
parsed_message.receiver = "me"
|
14
|
+
parsed_message.sender = format_address(message[7])
|
15
|
+
end
|
16
|
+
|
17
|
+
def sent_group_imessage( parsed_message, message )
|
18
|
+
parsed_message.sender = "me"
|
19
|
+
parsed_message.receiver = "group"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Nosy
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
|
5
|
+
module MessageSupport
|
6
|
+
|
7
|
+
def find_all_messages( file )
|
8
|
+
SQLite3::Database.new( file ).execute( "select rowid, date, address, text, flags, is_madrid, madrid_flags, madrid_handle from message" )
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_imessage( parsed_message, message )
|
12
|
+
parsed_message.imessage = true
|
13
|
+
parsed_message.date = imessage_time_to_unixtime(message[1])
|
14
|
+
|
15
|
+
if message[6] == 36869
|
16
|
+
sent_imessage( parsed_message, message )
|
17
|
+
elsif message[6] == 12289
|
18
|
+
received_imessage( parsed_message, message )
|
19
|
+
elsif message[6] == 32773
|
20
|
+
sent_group_imessage( parsed_message, message )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_sms_message( parsed_message, message )
|
25
|
+
parsed_message.imessage = false
|
26
|
+
parsed_message.date = message[1]
|
27
|
+
|
28
|
+
if message[4] == 3 || message[4] == 35 || message[4] == 33 || message[4] == 16387
|
29
|
+
sent_sms_message( parsed_message, message )
|
30
|
+
elsif message[4] == 2
|
31
|
+
received_sms_message( parsed_message, message )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def imessage_time_to_unixtime( imessage_time )
|
38
|
+
imessage_time + 978307200
|
39
|
+
end
|
40
|
+
|
41
|
+
def format_address(address)
|
42
|
+
if !address.nil?
|
43
|
+
address = address.gsub(/\s+|[()-]/, "")
|
44
|
+
address =~ /^[0-9]+$/ ? "+"+address : address
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Nosy
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
|
5
|
+
module ParseChecks
|
6
|
+
|
7
|
+
def has_messages_table( database )
|
8
|
+
begin
|
9
|
+
database.execute2( "select rowid, date, address, text, flags, is_madrid, madrid_flags, madrid_handle from message" )
|
10
|
+
rescue SQLite3::SQLException
|
11
|
+
return false
|
12
|
+
rescue SQLite3::NotADatabaseException
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_iphone_database( results )
|
18
|
+
if ( results[0][0] == "ROWID" && results[0][1] == "date" && results[0][2] == "address" && results[0][3] == "text" && results[0][4] == "flags" && results[0][5] == "is_madrid" && results[0][6] == "madrid_flags" && results[0][7] == "madrid_handle") && results.count > 1
|
19
|
+
return true
|
20
|
+
else
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Nosy
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
|
5
|
+
module Smsmessage
|
6
|
+
|
7
|
+
def sent_sms_message( parsed_message, message )
|
8
|
+
parsed_message.receiver = format_address(message[2])
|
9
|
+
parsed_message.sender = "me"
|
10
|
+
end
|
11
|
+
|
12
|
+
def received_sms_message( parsed_message, message )
|
13
|
+
parsed_message.receiver = "me"
|
14
|
+
parsed_message.sender = format_address(message[2])
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/nosy/parser.rb
CHANGED
@@ -1,110 +1,37 @@
|
|
1
1
|
require 'sqlite3'
|
2
|
+
require 'nosy/parser/parse_checks'
|
3
|
+
require 'nosy/parser/message_support'
|
4
|
+
require 'nosy/parser/imessage'
|
5
|
+
require 'nosy/parser/smsmessage'
|
2
6
|
|
3
7
|
Message = Struct.new(:receiver, :sender, :message, :imessage, :date)
|
4
8
|
|
5
9
|
module Nosy
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
all_messages = db.execute( "select * from message" )
|
33
|
-
|
34
|
-
all_messages.map do |message|
|
35
|
-
|
36
|
-
# http://www.slideshare.net/hrgeeks/iphone-forensics-without-the-iphone
|
37
|
-
# http://www.scip.ch/?labs.20111103
|
38
|
-
|
39
|
-
parsed_message = Message.new("", "", message[3], "", "")
|
40
|
-
|
41
|
-
## If it is an iMessage
|
42
|
-
if message[29] == 1
|
43
|
-
parsed_message.imessage = true
|
44
|
-
parsed_message.date = message[2] + 978307200
|
45
|
-
|
46
|
-
# Sent an iMessage
|
47
|
-
if message[25] == 36869
|
48
|
-
parsed_message.receiver = format_address(message[18])
|
49
|
-
parsed_message.sender = "me"
|
50
|
-
|
51
|
-
# Recieved an iMessage
|
52
|
-
elsif message[25] == 12289
|
53
|
-
parsed_message.receiver = "me"
|
54
|
-
parsed_message.sender = format_address(message[18])
|
55
|
-
|
56
|
-
# Sent a group iMessage
|
57
|
-
elsif message[25] == 32773
|
58
|
-
parsed_message.sender = "me"
|
59
|
-
parsed_message.receiver = "group"
|
60
|
-
end
|
61
|
-
|
62
|
-
## It is an SMS
|
63
|
-
else
|
64
|
-
|
65
|
-
parsed_message.imessage = false
|
66
|
-
parsed_message.date = message[2]
|
67
|
-
|
68
|
-
# Sent SMS
|
69
|
-
if message[4] == 3
|
70
|
-
parsed_message.receiver = format_address(message[1])
|
71
|
-
parsed_message.sender = "me"
|
72
|
-
|
73
|
-
# Recieved an SMS
|
74
|
-
elsif message[4] == 2
|
75
|
-
parsed_message.receiver = "me"
|
76
|
-
parsed_message.sender = format_address(message[1])
|
77
|
-
|
78
|
-
# Sent SMS on retry
|
79
|
-
elsif message[4] == 35
|
80
|
-
parsed_message.receiver = format_address(message[1])
|
81
|
-
parsed_message.sender = "me"
|
82
|
-
|
83
|
-
# SMS Failed
|
84
|
-
elsif message[4] == 33
|
85
|
-
parsed_message.receiver = format_address(message[1])
|
86
|
-
parsed_message.sender = "me"
|
87
|
-
|
88
|
-
# iMessage Failed and sent as SMS
|
89
|
-
elsif message[4] == 16387
|
90
|
-
parsed_message.receiver = format_address(message[1])
|
91
|
-
parsed_message.sender = "me"
|
92
|
-
end
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
parsed_message
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
private
|
101
|
-
def format_address(address)
|
102
|
-
if !address.nil?
|
103
|
-
address = address.gsub(/\s+|[()-]/, "")
|
104
|
-
address =~ /^[0-9]+$/ ? "+"+address : address
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
11
|
+
class Parser
|
12
|
+
|
13
|
+
include MessageSupport
|
14
|
+
include ParseChecks
|
15
|
+
include Imessage
|
16
|
+
include Smsmessage
|
17
|
+
|
18
|
+
def can_parse?(file)
|
19
|
+
db = SQLite3::Database.new( file )
|
20
|
+
results = has_messages_table(db)
|
21
|
+
db != false && results != false ? is_iphone_database( results ) : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(file)
|
25
|
+
find_all_messages( file ).map do |message|
|
26
|
+
parsed_message = Message.new("", "", message[3], "", "")
|
27
|
+
if message[5] == 1
|
28
|
+
parse_imessage( parsed_message, message)
|
29
|
+
else
|
30
|
+
parse_sms_message( parsed_message, message)
|
31
|
+
end
|
32
|
+
parsed_message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
109
36
|
|
110
37
|
end
|
data/lib/nosy/searcher.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
module Nosy
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
class Searcher
|
4
|
+
attr_accessor :texts
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def initialize( file )
|
7
|
+
@texts = Parser.new.parse( file )
|
8
|
+
end
|
9
9
|
|
10
|
-
|
10
|
+
def search(filters={})
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
@texts.select do |text|
|
13
|
+
filters.all?{ |key, value| key == :date ? filter_dates(key, value, text) : text[key] == value }
|
14
|
+
end
|
15
|
+
end
|
16
16
|
|
17
|
-
|
17
|
+
private
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
def filter_dates( key, value, text )
|
20
|
+
if value[1] == "="
|
21
|
+
value[0] == ">" ? Time.at(text[key]) >= Time.at(value[2..-1].to_i) : Time.at(text[key]) <= Time.at(value[2..-1].to_i)
|
22
|
+
else
|
23
|
+
if value[0] == ">"
|
24
|
+
Time.at(text[key]) > Time.at(value[1..-1].to_i)
|
25
|
+
elsif value[0] == "<"
|
26
|
+
Time.at(text[key]) < Time.at(value[1..-1].to_i)
|
27
|
+
else
|
28
|
+
Time.at(text[key]) == Time.at(value.to_i)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
34
|
|
35
35
|
end
|
data/lib/nosy.rb
CHANGED
@@ -4,19 +4,23 @@ require "nosy/searcher"
|
|
4
4
|
|
5
5
|
module Nosy
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def self.hunt
|
8
|
+
Hunter.new.hunt
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def self.hunt_and_parse
|
12
|
+
Parser.new.parse(Hunter.new.hunt)
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def self.new(file)
|
16
|
+
Searcher.new(file)
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def self.can_parse?(file)
|
20
|
+
Parser.new.can_parse?(file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.parse(file)
|
24
|
+
Parser.new.parse(file)
|
25
|
+
end
|
22
26
|
end
|
data/nosy.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'nosy'
|
3
|
+
gem.version = '0.0.4'
|
4
|
+
gem.date = '2012-01-02'
|
5
|
+
gem.summary = "Output, backup, and read all your iPhone text messages"
|
6
|
+
gem.description = "Nosy fetches, parses and searches the iPhone's SMS database that is created on your machine each time you make a backup."
|
7
|
+
gem.authors = ["Philip Dudley"]
|
8
|
+
gem.homepage = "https://github.com/pdud/Nosy"
|
9
|
+
gem.email = 'pdudley89@gmail.com'
|
10
|
+
gem.files = `git ls-files`.split("\n")
|
11
|
+
gem.add_dependency("sqlite3", "~> 1.3.4")
|
12
|
+
gem.add_development_dependency("rspec", "~> 2.6.0")
|
13
|
+
end
|
data/spec/.DS_Store
ADDED
Binary file
|
data/spec/nosy/.DS_Store
ADDED
Binary file
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'nosy'
|
2
|
+
|
3
|
+
describe Nosy::Parser, "#can_parse?" do
|
4
|
+
|
5
|
+
let(:acceptable_iphone_database) { File.join('spec', 'support', 'db', 'texts.sqlite') }
|
6
|
+
let(:invalid_file_type_iphone_database) { File.join('spec', 'support', 'db', 'invalid_file_type_texts.txt') }
|
7
|
+
let(:empty_iphone_database) { File.join('spec', 'support', 'db', 'empty_iphone_database.sqlite') }
|
8
|
+
let(:missing_table_iphone_database) { File.join('spec', 'support', 'db', 'missing_table_texts.sqlite') }
|
9
|
+
let(:incorrect_format_iphone_database) { File.join('spec', 'support', 'db', 'incorrect_format_texts.sqlite') }
|
10
|
+
let(:missing_iphone_database) { File.join('spec', 'support', 'db', 'this_file_does_not_exist.sqlite') }
|
11
|
+
|
12
|
+
it "returns true for an iPhone Database" do
|
13
|
+
subject.can_parse?(acceptable_iphone_database).should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns false for a file that is not a database" do
|
17
|
+
subject.can_parse?(invalid_file_type_iphone_database).should == false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns false for a file that is an empty database" do
|
21
|
+
subject.can_parse?(empty_iphone_database).should == false
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns false for a file that is missing the messages table" do
|
25
|
+
subject.can_parse?(missing_table_iphone_database).should == false
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns false for a file that is missing the necessary fields in the messages table" do
|
29
|
+
subject.can_parse?(incorrect_format_iphone_database).should == false
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns false for a file that is missing" do
|
33
|
+
subject.can_parse?(missing_iphone_database).should == false
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe Nosy::Parser, "#parse(file)" do
|
39
|
+
|
40
|
+
let(:iphone_database) { File.join('spec', 'support', 'db', 'texts.sqlite') }
|
41
|
+
let(:parsed) { subject.parse(iphone_database) }
|
42
|
+
|
43
|
+
it "should parse each texts as a Messages" do
|
44
|
+
parsed[0].should be_a_kind_of Message
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should parse the collection of texts as an Array" do
|
48
|
+
parsed.should be_a_kind_of Array
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
it "correctly parses the text" do
|
53
|
+
parsed[0].message.should == "Hey it's Phil. I'm moving some stuff to next years apartment at the mo. But you left stuff here so you should come round later to grab it."
|
54
|
+
parsed[3].message.should == "Hi :)"
|
55
|
+
end
|
56
|
+
|
57
|
+
context "SMS Messages" do
|
58
|
+
|
59
|
+
it "correctly detects the message as an iMessage" do
|
60
|
+
parsed[0].imessage.should == false
|
61
|
+
parsed[1].imessage.should == false
|
62
|
+
parsed[2].imessage.should == false
|
63
|
+
end
|
64
|
+
|
65
|
+
it "correctly parses the date" do
|
66
|
+
parsed[0].date.should == 1273175931
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Sent from me" do
|
70
|
+
|
71
|
+
it "correctly parses the sender" do
|
72
|
+
parsed[0].sender.should == "me"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "correctly parses the receiver" do
|
76
|
+
parsed[0].receiver.should == "+1234567890"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "Sent to me" do
|
82
|
+
|
83
|
+
it "correctly parses the sender" do
|
84
|
+
parsed[1].sender.should == "+1234567890"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "correctly parses the receiver" do
|
88
|
+
parsed[1].receiver.should == "me"
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "Sent from me, Failed, then Succeded" do
|
94
|
+
|
95
|
+
it "correctly parses the sender" do
|
96
|
+
parsed[2].sender.should == "me"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "correctly parses the receiver" do
|
100
|
+
parsed[2].receiver.should == "+1234567890"
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "Sent from me and Failed" do
|
106
|
+
|
107
|
+
it "correctly parses the sender" do
|
108
|
+
parsed[7].sender.should == "me"
|
109
|
+
end
|
110
|
+
|
111
|
+
it "correctly parses the receiver" do
|
112
|
+
parsed[7].receiver.should == "+1234567890"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "iMessage Failed and sent as SMS" do
|
118
|
+
|
119
|
+
it "correctly parses the sender" do
|
120
|
+
parsed[5].sender.should == "me"
|
121
|
+
end
|
122
|
+
|
123
|
+
it "correctly parses the receiver" do
|
124
|
+
parsed[5].receiver.should == "+1234567890"
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
context "iMessages" do
|
132
|
+
|
133
|
+
it "correctly detects the message as an iMessage" do
|
134
|
+
parsed[3].imessage.should == true
|
135
|
+
parsed[4].imessage.should == true
|
136
|
+
end
|
137
|
+
|
138
|
+
it "correctly parses the date" do
|
139
|
+
parsed[3].date.should == 1318524672
|
140
|
+
parsed[4].date.should == 1318524796
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "Sent to me" do
|
144
|
+
|
145
|
+
it "correctly parses the sender" do
|
146
|
+
parsed[3].sender.should == "user@gmail.com"
|
147
|
+
end
|
148
|
+
|
149
|
+
it "correctly parses the receiver" do
|
150
|
+
parsed[3].receiver.should == "me"
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "Sent from me" do
|
156
|
+
|
157
|
+
it "correctly parses the sender" do
|
158
|
+
parsed[4].sender.should == "me"
|
159
|
+
end
|
160
|
+
|
161
|
+
it "correctly parses the receiver" do
|
162
|
+
parsed[4].receiver.should == "user@gmail.com"
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "Group message sent from me" do
|
168
|
+
|
169
|
+
it "correctly parses the sender" do
|
170
|
+
parsed[6].sender.should == "me"
|
171
|
+
end
|
172
|
+
|
173
|
+
it "correctly parses the receiver" do
|
174
|
+
parsed[6].receiver.should == "group"
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
describe Nosy::Parser, "#format_address" do
|
183
|
+
|
184
|
+
let(:parser) { subject }
|
185
|
+
|
186
|
+
it "does not format nil addresses" do
|
187
|
+
parser.send(:format_address, nil).should == nil
|
188
|
+
end
|
189
|
+
|
190
|
+
it "removes white space" do
|
191
|
+
parser.send(:format_address, "my address").should == "myaddress"
|
192
|
+
end
|
193
|
+
|
194
|
+
it "removes parenthesis" do
|
195
|
+
parser.send(:format_address, "my(address)").should == "myaddress"
|
196
|
+
end
|
197
|
+
|
198
|
+
it "removes seperators" do
|
199
|
+
parser.send(:format_address, "my-address").should == "myaddress"
|
200
|
+
end
|
201
|
+
|
202
|
+
it "adds a + if it consists of all numbers" do
|
203
|
+
parser.send(:format_address, "1 (234) 567-8901").should == "+12345678901"
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'nosy'
|
2
|
+
|
3
|
+
describe Nosy::Searcher, "#new" do
|
4
|
+
|
5
|
+
let(:iphone_database) { File.join('spec', 'support', 'db', 'texts.sqlite') }
|
6
|
+
let(:texts) { Nosy::Searcher.new( iphone_database ) }
|
7
|
+
|
8
|
+
it "should create a Nosy::Searcher class" do
|
9
|
+
texts.should be_a_kind_of Nosy::Searcher
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should fill texts" do
|
13
|
+
texts.texts.should_not be nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should fill texts with Messages" do
|
17
|
+
texts.texts[0].should be_a_kind_of Message
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Nosy::Searcher, "#search" do
|
23
|
+
|
24
|
+
let(:iphone_database) { File.join('spec', 'support', 'db', 'texts.sqlite') }
|
25
|
+
let(:texts) { Nosy::Searcher.new( iphone_database ) }
|
26
|
+
|
27
|
+
it "should return all texts if no filters are passed in" do
|
28
|
+
texts.search().count.should == 8
|
29
|
+
texts.search().should == Nosy::Parser.new.parse( iphone_database )
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should be searchable by sender" do
|
33
|
+
search = texts.search(:sender => "me").each do |text|
|
34
|
+
text.sender.should == "me"
|
35
|
+
end
|
36
|
+
search.count.should_not == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be searchable by receiver" do
|
40
|
+
search = texts.search(:receiver => "me").each do |text|
|
41
|
+
text.receiver.should == "me"
|
42
|
+
end
|
43
|
+
search.count.should_not == 0
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be searchable by type of imessage" do
|
47
|
+
search = texts.search(:imessage => true).each do |text|
|
48
|
+
text.imessage.should == true
|
49
|
+
end
|
50
|
+
search.count.should_not == 0
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should be searchable by text of message (full text)" do
|
54
|
+
search = texts.search(:message => "Here :)").each do |text|
|
55
|
+
text.message.should == "Here :)"
|
56
|
+
end
|
57
|
+
search.count.should_not == 0
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be searchable by date of message" do
|
61
|
+
search = texts.search(:date => "1320880443").each do |text|
|
62
|
+
text.date.should == 1320880443
|
63
|
+
end
|
64
|
+
search.count.should_not == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be searchable by more than one field" do
|
68
|
+
search = texts.search(:sender => "me", :message => "Here :)").each do |text|
|
69
|
+
text.sender.should == "me"
|
70
|
+
text.message.should == "Here :)"
|
71
|
+
end
|
72
|
+
search.count.should_not == 0
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should be searchable after a given date" do
|
76
|
+
search = texts.search(:date => ">1318524672").each do |text|
|
77
|
+
Time.at(text.date).should > Time.at(1318524672)
|
78
|
+
end
|
79
|
+
search.count.should_not == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should be searchable before a given date" do
|
83
|
+
search = texts.search(:date => "<1320880443").each do |text|
|
84
|
+
Time.at(text.date).should < Time.at(1320880443)
|
85
|
+
end
|
86
|
+
search.count.should_not == 0
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be searchable after and equal to a given date" do
|
90
|
+
search = texts.search(:date => ">=1318524672").each do |text|
|
91
|
+
Time.at(text.date).should >= Time.at(1318524672)
|
92
|
+
end
|
93
|
+
search.count.should_not == 0
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should be searchable before and equal to a given date" do
|
97
|
+
search = texts.search(:date => "<=1320880443").each do |text|
|
98
|
+
Time.at(text.date).should <= Time.at(1320880443)
|
99
|
+
end
|
100
|
+
search.count.should_not == 0
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/spec/nosy_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'nosy'
|
2
|
+
|
3
|
+
describe Nosy do
|
4
|
+
|
5
|
+
let(:iphone_database) { File.join('spec', 'support', 'db', 'texts.sqlite') }
|
6
|
+
let(:texts) { subject.new(iphone_database) }
|
7
|
+
|
8
|
+
describe "#hunt" do
|
9
|
+
|
10
|
+
it "should return the same results as a hunt" do
|
11
|
+
Nosy.hunt.should == Nosy::Hunter.new.hunt
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#new" do
|
17
|
+
|
18
|
+
it "should return the same results as a search" do
|
19
|
+
Nosy.new(iphone_database).texts.should == Nosy::Searcher.new(iphone_database).texts
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#hunt_and_parse" do
|
25
|
+
|
26
|
+
it "should get the database and parse it" do
|
27
|
+
Nosy.hunt_and_parse.should == Nosy::Parser.new.parse(Nosy::Hunter.new.hunt)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#parse" do
|
33
|
+
|
34
|
+
it "should return the same results as a parse" do
|
35
|
+
Nosy.parse(iphone_database).should == Nosy::Parser.new.parse(iphone_database)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#can_parse?" do
|
41
|
+
|
42
|
+
it "should return the same results as a parse" do
|
43
|
+
Nosy.can_parse?(iphone_database).should == Nosy::Parser.new.can_parse?(iphone_database)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
We can't have this working!
|
File without changes
|
File without changes
|
Binary file
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nosy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-01-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sqlite3
|
16
|
-
requirement: &
|
16
|
+
requirement: &70203490284220 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,21 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.3.4
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: fileutils
|
27
|
-
requirement: &70300181810080 !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
|
-
requirements:
|
30
|
-
- - ~>
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '0.7'
|
33
|
-
type: :runtime
|
34
|
-
prerelease: false
|
35
|
-
version_requirements: *70300181810080
|
24
|
+
version_requirements: *70203490284220
|
36
25
|
- !ruby/object:Gem::Dependency
|
37
26
|
name: rspec
|
38
|
-
requirement: &
|
27
|
+
requirement: &70203490283740 !ruby/object:Gem::Requirement
|
39
28
|
none: false
|
40
29
|
requirements:
|
41
30
|
- - ~>
|
@@ -43,7 +32,7 @@ dependencies:
|
|
43
32
|
version: 2.6.0
|
44
33
|
type: :development
|
45
34
|
prerelease: false
|
46
|
-
version_requirements: *
|
35
|
+
version_requirements: *70203490283740
|
47
36
|
description: Nosy fetches, parses and searches the iPhone's SMS database that is created
|
48
37
|
on your machine each time you make a backup.
|
49
38
|
email: pdudley89@gmail.com
|
@@ -51,11 +40,36 @@ executables: []
|
|
51
40
|
extensions: []
|
52
41
|
extra_rdoc_files: []
|
53
42
|
files:
|
43
|
+
- .gitignore
|
44
|
+
- .rspec
|
45
|
+
- README.md
|
46
|
+
- lib/.DS_Store
|
54
47
|
- lib/nosy.rb
|
55
48
|
- lib/nosy/hunter.rb
|
56
49
|
- lib/nosy/parser.rb
|
50
|
+
- lib/nosy/parser/imessage.rb
|
51
|
+
- lib/nosy/parser/message_support.rb
|
52
|
+
- lib/nosy/parser/parse_checks.rb
|
53
|
+
- lib/nosy/parser/smsmessage.rb
|
57
54
|
- lib/nosy/searcher.rb
|
58
|
-
|
55
|
+
- nosy-0.0.4.gem
|
56
|
+
- nosy.gemspec
|
57
|
+
- spec/.DS_Store
|
58
|
+
- spec/nosy/.DS_Store
|
59
|
+
- spec/nosy/hunter_spec.rb
|
60
|
+
- spec/nosy/parser_spec.rb
|
61
|
+
- spec/nosy/searcher_spec.rb
|
62
|
+
- spec/nosy_spec.rb
|
63
|
+
- spec/support/.DS_Store
|
64
|
+
- spec/support/db/.DS_Store
|
65
|
+
- spec/support/db/empty_iphone_database.sqlite
|
66
|
+
- spec/support/db/incorrect_format_texts.sqlite
|
67
|
+
- spec/support/db/invalid_file_type_texts.txt
|
68
|
+
- spec/support/db/missing_table_texts.sqlite
|
69
|
+
- spec/support/db/texts.db
|
70
|
+
- spec/support/db/texts.sqlite
|
71
|
+
- spec/support/db/this_file_does_not_exist.sqlite
|
72
|
+
homepage: https://github.com/pdud/Nosy
|
59
73
|
licenses: []
|
60
74
|
post_install_message:
|
61
75
|
rdoc_options: []
|
@@ -78,7 +92,5 @@ rubyforge_project:
|
|
78
92
|
rubygems_version: 1.8.10
|
79
93
|
signing_key:
|
80
94
|
specification_version: 3
|
81
|
-
summary:
|
82
|
-
on your machine each time you make a backup.
|
95
|
+
summary: Output, backup, and read all your iPhone text messages
|
83
96
|
test_files: []
|
84
|
-
has_rdoc:
|