nosy 0.0.3 → 0.0.4
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 +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:
|