TokiCLI 0.0.9 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +166 -20
- data/TokiCLI.gemspec +2 -2
- data/lib/API/dbapi.rb +451 -0
- data/lib/API/helpers.rb +151 -0
- data/lib/TokiCLI/app.rb +225 -106
- data/lib/TokiCLI/authorize.rb +6 -4
- data/lib/TokiCLI/export.rb +81 -0
- data/lib/TokiCLI/import.rb +122 -0
- data/lib/TokiCLI/scan.rb +19 -0
- data/lib/TokiCLI/version.rb +1 -1
- data/lib/TokiCLI/view.rb +100 -43
- metadata +10 -7
- data/lib/TokiCLI/module.rb +0 -224
data/lib/TokiCLI/module.rb
DELETED
@@ -1,224 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module TokiCLI
|
3
|
-
class Toki
|
4
|
-
|
5
|
-
CLIENT_ID = 'm6AccJFM56ENCn58Vde9cSg3uSpbvAAs'
|
6
|
-
CALLBACK_URL = 'http://aya.io/toki_cli/auth.html'
|
7
|
-
|
8
|
-
attr_reader :bundles
|
9
|
-
|
10
|
-
def initialize(token, channel_id)
|
11
|
-
@token = token
|
12
|
-
@channel_id = channel_id
|
13
|
-
if File.exist? "#{Dir.home}/.TokiCLI/apps.json"
|
14
|
-
@bundles = JSON.parse(File.read("#{Dir.home}/.TokiCLI/apps.json"))
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def get_bundle_ids
|
19
|
-
puts "Analyzing apps in /Applications\n\n"
|
20
|
-
require 'CFPropertyList'
|
21
|
-
files = get_plists "/Applications/*/Contents/*"
|
22
|
-
utils = get_plists "/Applications/Utilities/*/Contents/*"
|
23
|
-
home = get_plists "#{Dir.home}/Applications/*/Contents/*"
|
24
|
-
@infos = {}
|
25
|
-
get_bundles files
|
26
|
-
get_bundles utils
|
27
|
-
get_bundles home
|
28
|
-
@infos
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def get_content(options)
|
34
|
-
if options[:adn]
|
35
|
-
get_adn_data.reverse.map {|obj| make_obj_from_adn(obj)}
|
36
|
-
else
|
37
|
-
get_db_data.map {|obj| make_obj_from_db(obj)}
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def get_app(app, synced)
|
42
|
-
name_regex = /#{app.downcase}/
|
43
|
-
app_data = {}
|
44
|
-
totals = {}
|
45
|
-
totals[:app] = 0
|
46
|
-
found = synced.select {|obj| obj.app[:name].downcase =~ name_regex}
|
47
|
-
found.each do |obj|
|
48
|
-
totals[:app] += obj.app[:total]
|
49
|
-
app_data[obj.msg[:id]] = {
|
50
|
-
from: Time.at(obj.app[:active_from]).strftime("%Y/%m/%d %Hh:%Mm:%Ss"),
|
51
|
-
to: Time.at(obj.app[:active_to]).strftime("%Y/%m/%d %Hh:%Mm:%Ss"),
|
52
|
-
duration: humanized_date(obj.app[:total]),
|
53
|
-
name: obj.app[:name],
|
54
|
-
raw_from: obj.app[:active_from]
|
55
|
-
}
|
56
|
-
end
|
57
|
-
app_data['total'] = humanized_date(totals[:app])
|
58
|
-
app_data
|
59
|
-
end
|
60
|
-
|
61
|
-
def all_data(synced)
|
62
|
-
sorted = get_sorted_totals(synced)
|
63
|
-
sorted.inject({}) do |bundles, obj|
|
64
|
-
bundles.merge(obj[0] => humanized_date(obj[1])) #name, total
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def top(synced, number = 5)
|
69
|
-
number = -number
|
70
|
-
sorted = get_sorted_totals(synced)
|
71
|
-
humanized = sorted.map {|n,t| [n, humanized_date(t)]}
|
72
|
-
humanized[number..-1]
|
73
|
-
end
|
74
|
-
|
75
|
-
def get_day(date, entries, options)
|
76
|
-
min_epoch = date.to_time.to_i
|
77
|
-
max_epoch = date.next_day.to_time.to_i
|
78
|
-
get_between(min_epoch, max_epoch, entries)
|
79
|
-
end
|
80
|
-
|
81
|
-
def get_range(day1, day2, entries, options)
|
82
|
-
min_epoch = day1.to_time.to_i
|
83
|
-
max_epoch = day2.next_day.to_time.to_i # between start of 1 and end of 2
|
84
|
-
get_between(min_epoch, max_epoch, entries)
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def get_between(epoch1, epoch2, entries)
|
90
|
-
range = entries.select {|obj| obj.app[:active_from] > epoch1 && obj.app[:active_from] < epoch2}
|
91
|
-
day_totals = sort_totals get_totals(range)
|
92
|
-
day_totals.map {|n,t| [n, humanized_date(t)]}
|
93
|
-
end
|
94
|
-
|
95
|
-
def get_sorted_totals(synced)
|
96
|
-
sort_totals(get_totals(synced))
|
97
|
-
end
|
98
|
-
|
99
|
-
def humanized_date(epoch)
|
100
|
-
human = epoch_to_human(epoch)
|
101
|
-
"#{human[:hours]}h #{human[:minutes]}m #{human[:seconds]}s"
|
102
|
-
end
|
103
|
-
|
104
|
-
def sort_totals(totals)
|
105
|
-
totals.sort_by { |k,v| v }
|
106
|
-
end
|
107
|
-
|
108
|
-
def get_totals(synced)
|
109
|
-
synced.inject({}) do |result, obj|
|
110
|
-
result.merge(obj.app[:name] => app_total(obj, result))
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def app_total(obj, totals)
|
115
|
-
totals[obj.app[:name]].nil? ? obj.app[:total] : totals[obj.app[:name]] + obj.app[:total]
|
116
|
-
end
|
117
|
-
|
118
|
-
def epoch_to_human(epoch)
|
119
|
-
hours = epoch / 3600
|
120
|
-
minutes = (epoch / 60 - hours * 60)
|
121
|
-
seconds = (epoch - (minutes * 60 + hours * 3600))
|
122
|
-
{hours: hours, minutes: minutes, seconds: seconds}
|
123
|
-
end
|
124
|
-
|
125
|
-
def get_adn_data
|
126
|
-
channels = ADNChannels::GetMessages.new(@token)
|
127
|
-
channels.get_messages(@channel_id)
|
128
|
-
end
|
129
|
-
|
130
|
-
def get_db_data
|
131
|
-
db = open_db
|
132
|
-
data = db.execute("SELECT * FROM KKAppActivity")
|
133
|
-
db.close
|
134
|
-
data
|
135
|
-
end
|
136
|
-
|
137
|
-
def open_db
|
138
|
-
toki_path = "#{Dir.home}/.TokiCLI"
|
139
|
-
db_path = "#{Dir.home}/Library/Containers/us.kkob.Toki/Data/Documents/toki_data.sqlite3"
|
140
|
-
abort(Status.no_db) unless File.exist? db_path
|
141
|
-
FileUtils.mkdir_p(toki_path) unless Dir.exist?(toki_path)
|
142
|
-
FileUtils.copy(db_path, "#{toki_path}/toki_data.sqlite3.bak")
|
143
|
-
Amalgalite::Database.new(db_path)
|
144
|
-
end
|
145
|
-
|
146
|
-
def make_obj_from_adn(obj)
|
147
|
-
msg = {}
|
148
|
-
usr = {}
|
149
|
-
app = {}
|
150
|
-
d = obj['annotations'][0]['value']['d']
|
151
|
-
msg[:id] = obj['id']
|
152
|
-
msg[:thread] = obj['thread_id']
|
153
|
-
msg[:date] = parsed_date obj['created_at']
|
154
|
-
usr[:username] = obj['user']['username']
|
155
|
-
usr[:name] = obj['user']['name']
|
156
|
-
usr[:id] = obj['user']['id']
|
157
|
-
app[:id] = d['id']
|
158
|
-
app[:uuid] = d['UUID']
|
159
|
-
app[:name] = make_app_name [nil, d['bundleIdentifier']]
|
160
|
-
app[:active_to] = d['activeTo']
|
161
|
-
app[:active_from] = d['activeFrom']
|
162
|
-
app[:total] = d['totalSeconds']
|
163
|
-
OpenStruct.new(app: app, msg: msg, usr: usr)
|
164
|
-
end
|
165
|
-
|
166
|
-
def make_obj_from_db(obj)
|
167
|
-
# content is an array:
|
168
|
-
# id (INTEGER) 0, bundleIdentifier (VARCHAR) 1, activeFrom (INTEGER) 2, activeTo (INTEGER) 3, totalSeconds (INTEGER) 4, UUID (VARCHAR) 5, synced (INTEGER) 6, availableToSync (INTEGER) 7
|
169
|
-
msg = {}
|
170
|
-
usr = {}
|
171
|
-
app = {}
|
172
|
-
msg[:id] = obj[0]
|
173
|
-
msg[:thread] = msg[:id]
|
174
|
-
msg[:date] = Time.now
|
175
|
-
usr[:username] = ENV['USERNAME']
|
176
|
-
usr[:name] = usr[:username]
|
177
|
-
usr[:id] = obj[5]
|
178
|
-
app[:id] = msg[:id]
|
179
|
-
app[:uuid] = obj[5]
|
180
|
-
app[:name] = make_app_name obj
|
181
|
-
app[:active_to] = obj[3]
|
182
|
-
app[:active_from] = obj[2]
|
183
|
-
app[:total] = obj[4]
|
184
|
-
OpenStruct.new(app: app, msg: msg, usr: usr)
|
185
|
-
end
|
186
|
-
|
187
|
-
def make_app_name obj
|
188
|
-
if @bundles[obj[1]]
|
189
|
-
@bundles[obj[1]]
|
190
|
-
else
|
191
|
-
obj[1]
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def parsed_date(string)
|
196
|
-
"#{string[0...10]} #{string[11...19]}"
|
197
|
-
end
|
198
|
-
|
199
|
-
def get_plists path
|
200
|
-
Dir.glob(path).select {|f| (File.split f).last == 'Info.plist'}
|
201
|
-
end
|
202
|
-
|
203
|
-
def get_bundles plists
|
204
|
-
plists.each do |obj|
|
205
|
-
puts "Analyzing #{obj} ...\n"
|
206
|
-
begin
|
207
|
-
pl = CFPropertyList::List.new(:file => obj)
|
208
|
-
rescue CFFormatError
|
209
|
-
puts "Unable to read the file, skipping...\n"
|
210
|
-
next
|
211
|
-
end
|
212
|
-
data = CFPropertyList.native_types(pl.value)
|
213
|
-
name = data['CFBundleName']
|
214
|
-
bundle_id = data['CFBundleIdentifier']
|
215
|
-
if name.nil?
|
216
|
-
name = data['CFBundleExecutable']
|
217
|
-
end
|
218
|
-
next if name.nil?
|
219
|
-
next if bundle_id.nil? || bundle_id.empty?
|
220
|
-
@infos[bundle_id] = name
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|