-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
359 lines (302 loc) · 12.3 KB
/
index.js
1
2
3
4
5
6
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
var request = require('request');
// LDD stands for LibraryD Data
var LDD = function () {
// Should we load the current LibraryD database initially? Possibily if under 5mb total?
// Define and store variables locally so they cannot be accessed.
// artifactStorage contains all loaded artifacts.
// ToDo: Implement a size limit to the amount of artifacts to store.
this.artifactStorage = [];
// URLS to connect to.
this.dataHostname = "api.alexandria.io";
this.dataHostnameSecure = true;
this.dataSlug = "/alexandria/v2/"
// SEARCH_DELAY is the amount of time required between full loads from LibraryD.
this.SEARCH_DELAY = 30 * 60 * 1000;
this.MAX_SEARCH_RESULTS = 50;
// Time since the last full query from LibraryD
this.lastFullUpdate = 0;
// Method Name: buildURL
// Description: A helper method to build the URL string using private variables.
// Paramaters: None
// Returns: Returns a URL built from private variables.
this.buildURL = function(){
var secureStr = this.dataHostnameSecure ? 'https' : 'http';
return secureStr + '://' + this.dataHostname + this.dataSlug;
}
this.loadAllFromLibraryD();
};
// Method Name: LDD.getArtifact
// Description: Searches to check if an artifact already exists, if it does, it will return the artifact JSON, if not, it will lookup the artifact from LibraryD and store it.
// Paramaters:
// - artifactTXID: the txid of the artifact you are requesting
// - callback: function that will be called once the search finishes.
// Returns: Returns data via the callback function.
LDD.prototype.getArtifact = function(artifactTXID, callback){
// Go through entire artifact storage backwards (doing it backwards will allow the searching through the most recent artifacts first.)
for (var i = this.artifactStorage.length - 1; i >= 0; i--) {
if (typeof this.artifactStorage[i].txid !== 'undefined') {
if (this.artifactStorage[i].txid == artifactTXID){
if (typeof callback !== 'function'){
return [this.artifactStorage[i]];
} else {
callback([this.artifactStorage[i]]);
return;
}
}
}
}
// Make sure the callback exists.
if (typeof callback !== 'function'){
// There is no callback so abort before attempting to query LibraryD.
console.log("No callback, aborting getArtifact before querying LibraryD!");
return [];
}
// Since we got here no artifact was found. Lets get it from LibraryD.
this.queryLibraryD(artifactTXID, 'txid', callback);
}
// Method Name: LDD.search
// Description: Searches through all stored artifact JSON, if the last full query of LibraryD was more than SEARCH_DELAY minutes ago then do another full query.
// Paramaters:
// - searchTerm: The text we are searching for.
// - searchOn: the variable name (txid, publisher, address, etc) or for everything '*' (default '*')
// - module: the module we are searching on, this should be 'media' in almost all cases. It is however an option if wished to be set. (default 'media')
// - callback: function that will be called once the search finishes.
// Returns: Artifacts in a JSON object array.
LDD.prototype.search = function(searchTerm, searchOn, module, callback){
// Make sure paramaters are set and are strings. If they are not set, or if they are not a string, then they will be set to default.
if (typeof searchOn !== 'string'){
// Since it is not a string, check if they put the callback here.
if (typeof searchOn === 'function')
callback = searchOn;
searchOn = '*';
}
if (typeof module !== 'string'){
// Since it is not a string, check if they put the callback here.
if (typeof module === 'function')
callback = module;
module = 'media';
}
if (typeof callback !== 'function'){
// If the callback is not a function cancel the search as we cannot return data recieved.
// It is likely that the user is attempting to call it directly.
// If they called this method and expected the value to be returned into a variable it will return a blank array.
if (searchOn == 'txid')
return this.getArtifact(searchTerm);
else
return [];
}
if (typeof searchTerm !== 'string'){
// No search term provided, return an empty array.
callback([]);
return;
}
// Check to see if it has been at least SEARCH_DELAY ms since the last full query.
var tmpTime = new Date().getTime();
if (tmpTime - this.lastFullUpdate > this.SEARCH_DELAY){
this.queryLibraryD(searchTerm, searchOn, module, callback);
} else {
// It has not been SEARCH_DELAY ms yet, just search the local db.
callback(this.searchArtifactStorage(searchTerm, searchOn, module));
}
}
// Method Name: LDD.changeDataSource
// Description: Changes the data URL that this module requests from (i.e. to be used if you need to set a custom API endpoint instead of the default)
// Paramaters:
// - hostname: the hostname to connect to (api.alexandria.io)
// - secure: is the connection secure/use https (true)
// - slug: the slugpath to connect to (/alexandria/v2/)
// Returns: Artifacts in a JSON object array.
LDD.prototype.changeDataSource = function(hostname, secure, slug){
if (typeof hostname !== 'undefined')
this.dataHostname = hostname;
if (typeof secure !== 'undefined')
this.dataHostnameSecure = secure;
if (typeof secure !== 'undefined')
this.dataSlug = slug;
}
// Method Name: LDD.queryLibraryD
// Description: Looks up the requested term from LibraryD then stores updated data in the cache.
// Paramaters:
// - searchTerm: The text we are searching for.
// - searchOn: the variable name (txid, publisher, address) or for everything '*' (default '*')
// - module: the module we are searching on, this should be 'media' in almost all cases. It is however an option if wished to be set. (default 'media')
// - callback: a function that can revieve JSON data from the search. This will be called with one paramater, the JSON array full of artifacts that fit the search. ( function(matches){} )
// Returns: Returns data via the callback parameter. If no callback is provided; will return an empty array.
LDD.prototype.queryLibraryD = function(searchTerm, searchOn, module, callback){
// Make sure paramaters are set and are strings. If they are not set, or if they are not a string, then they will be set to default.
if (typeof searchOn !== 'string'){
// Since it is not a string, check if they put the callback here.
if (typeof searchOn === 'function')
callback = searchOn;
searchOn = '*';
}
if (typeof module !== 'string'){
// Since it is not a string, check if they put the callback here.
if (typeof module === 'function')
callback = module;
module = 'media';
}
if (typeof callback !== 'function'){
// If the callback is not a function cancel the search as we cannot return data recieved.
// It is likely that the user is attempting to call it directly.
// If they called this method and expected the value to be returned into a variable it will return a blank array.
console.error("No callback provided! Cannot return search data! Aborting searchLibraryD function!");
return [];
}
if (typeof searchTerm !== 'string'){
// No search term provided, return an empty array.
callback([]);
return;
}
// Build the request URL
var requestURL = this.buildURL() + 'search';
// Build the query we are sending.
var queryString = {
"protocol": module,
"search-on": searchOn,
"search-for": searchTerm,
"search-like": true
};
// Build the options.
var opts = {
method: 'POST',
url: requestURL,
body: queryString,
json: true
};
var self = this;
request(opts, function(err, res, body){
if (err){
console.log("Recieved a " + " error when attempting to search LibraryD. \nRequestURL: " + requestURL + "\nqueryString: " + queryString + "\nStacktrace: " + err);
}
// Check if there is any data in the body.
if (body){
// Store the new values we loaded.
if (typeof body.response === 'object'){
if (searchTerm !== ''){
self.artifactStorage = mergeArrays(self.artifactStorage, body.response);
} else if (searchTerm === ''){
self.artifactStorage = body.response;
self.lastFullUpdate = new Date().getTime();
}
}
callback(body.response);
return;
}
// Some error happened, return an empty array.
callback([]);
});
}
// Method Name: LDD.loadAllFromLibraryD
// Description: Loads all artifacts from libraryd into the local storage.
// Paramaters: None
// Returns: None
LDD.prototype.loadAllFromLibraryD = function(){
this.queryLibraryD('', function(data){console.log("Loaded " + data.length + " artifacts from LibraryD")});
}
// Method Name: LDD.searchArtifactStorage
// Description: Searches the local artifact cache and returns any matches.
// Paramaters:
// - searchTerm: The text we are searching for.
// - searchOn: the variable name (txid, publisher, address) or for everything '*' (default '*')
// - module: the module we are searching on, this should be 'media' in almost all cases. It is however an option if wished to be set. (default 'media')
// Returns: Returns any matches.
LDD.prototype.searchArtifactStorage = function(searchTerm, searchOn, module){
if (typeof searchTerm !== 'string')
return [];
if (typeof searchOn !== 'string')
searchOn = '*';
if (typeof module !== 'string')
module = 'media';
// If the searchOn can be found at the root of the JSON object, then just search the root and do not search recursively.
if (searchOn == 'tags' || searchOn == 'timestamp' || searchOn == 'title' || searchOn == 'txid' || searchOn == 'type' || searchOn == 'year' || searchOn == 'publisher'){
var matches = [];
for (var i = 0; i < this.artifactStorage.length; i++) {
if (searchArtifact({a: this.artifactStorage[i][searchOn]}, searchTerm))
matches.push(this.artifactStorage[i]);
}
return matches;
}
// We need to preform a recursive search.
var results = [];
for (var i = this.artifactStorage.length - 1; i >= 0; i--) {
// If we have more than the max results for a '*' search, then break.
if (results.length >= this.MAX_SEARCH_RESULTS)
break;
if (searchArtifact(this.artifactStorage[i], searchTerm))
results.push(this.artifactStorage[i]);
}
return results;
}
// Method Name: mergeArrays
// Description: Merges two JSON arrays.
// Paramaters:
// - arrayOne: The array containing all current artifact objects.
// - arrayTwo: The array containing the new values to be checked and merged in.
// Returns: A JSON array containing all values from the merge.
var mergeArrays = function(arrayOne, arrayTwo){
// Check to see if we have initialized the first array yet.
if (typeof arrayOne !== 'object'){
arrayOne = [];
}
// If the array we are merging in is empty just return arrayOne (arrayOne contains artifactStorage)
if (typeof arrayTwo !== 'object'){
return arrayOne;
}
// If the first array is blank, return the second array.
if (arrayOne == []){
return arrayTwo;
}
// Variable to hold items that need to be merged in.
var newArray = arrayOne;
// Figure out what items we need to merge in.
for (var i = arrayTwo.length - 1; i >= 0; i--) {
var match = false;
for (var j = arrayOne.length - 1; j >= 0; j--) {
if (arrayTwo[i].txid == arrayOne[j].txid){
match = true;
break;
}
}
if (!match)
newArray.push(arrayTwo[i]);
}
return newArray;
}
// Method Name: searchArtifact
// Description: Searches a single artifact for the searchTerm
// Paramaters:
// - artifact: JSON Artifact Object
// - searchTerm: String of search term
// Returns: true if the artifact contains the searchTerm in any of its children, false if it does not at all.
var searchArtifact = function(artifact, searchTerm){
for (key in artifact){
var item = artifact[key];
if (typeof item !== 'object') {
if (typeof item === 'string'){
if (item === searchTerm){
return true;
} else if (item.toLowerCase() === searchTerm.toLowerCase()){
return true;
} else if (item.indexOf(searchTerm) !== -1){
return true;
} else if (item.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1){
return true;
} else if (item.toLowerCase().includes(searchTerm.toLowerCase())){
return true;
}
} else if (item.toString().toLowerCase() === searchTerm.toLowerCase()){
return true;
}
} else {
var subresult = searchArtifact(item, searchTerm);
// If the subresult returns true then it was a match.
if (subresult)
return true;
}
}
// If the entire search fails, return false.
return false;
}
// Expose LDD and all methods.
module.exports = new LDD();