internetarchive-sdk-js
Version:
NodeJS / Typescript SDK for Internet Archive APIs
215 lines (214 loc) • 9.82 kB
JavaScript
import fs from 'fs';
import HttpClient from './HttpClient.js';
import endpoints from './endpoints.js';
import { isASCII, getPackageInfo } from './utils.js';
export * from './types.js';
const defaultIaOptions = {
testmode: false,
setScanner: true,
};
class InternetArchive {
/**
* Provides access to Internet Archive APIs through methods
*
* @param token - {@link https://archive.org/developers/tutorial-get-ia-credentials.html S3-like API Key} formatted as "accesskey:secretkey" (required for all methods except getItem or getItems)
* @param options - InternetArchive API options
* @param options.testmode - Option to add item to {@link https://archive.org/details/test_collection Test Collection} (auto deletes in 30 days) - default FALSE
* @param options.setScanner - option to add scanner metadata for internetarchive-sdk-js - default TRUE
* @see {@link https://archive.org/developers/tutorial-get-ia-credentials.html Archive.org - Get your Internet Archive credentials}
* @see {@link https://archive.org/details/test_collection Archive.org - Test Collection}
*/
constructor(token, options = {}) {
this.token = token ?? null;
this.options = {
testmode: options?.testmode ?? defaultIaOptions.testmode,
setScanner: options?.setScanner ?? defaultIaOptions.setScanner,
};
this.httpClient = new HttpClient(token, this.options);
}
/**
* Creates an Item in a Collection (Uploads a file and adds metadata).
*
* @param item - identifier, collection, mediatype, upload, metadata.
* @param item.identifier - The unique identifier for the item.
* @param item.collection - The collection that the item belongs to.
* @param item.mediatype - The item mediatype.
* @param item.upload - The item upload.
* @param item.metadata - The item metadata (optional).
* @returns The item identifier, metadata, and upload filename.
*
* @see {@link https://archive.org/developers/ias3.html Archive.org - ias3 Internet archive S3-like API}
*/
async createItem(item) {
const { identifier, collection, mediatype, upload, metadata } = item;
const packageInfo = await getPackageInfo();
const isTestCollection = this.options?.testmode ?? collection === 'test_collection';
const headers = {
'x-amz-auto-make-bucket': 1,
'x-archive-interactive-priority': 1,
'x-archive-meta-identifier': identifier,
'x-archive-meta-mediatype': mediatype,
...(isTestCollection && collection !== 'test_collection' ? { 'x-archive-meta01-collection': collection, 'x-archive-meta02-collection': 'test_collection' } : { 'x-archive-meta-collection': collection }),
...(this.options?.setScanner && packageInfo && { 'x-archive-meta-scanner': `${packageInfo.name}-${packageInfo.version}` }),
};
if (metadata && Object.keys(metadata).length) {
/* filters out identifier, mediatype, or collection from metadata */
Object.entries(metadata).filter(([key, _val]) => !['identifier', 'mediatype', 'collection', 'scanner'].includes(key)).forEach(([key, val]) => {
/* returns error if item create contains ascii characters */
if (val !== '' && !isASCII(val)) {
throw new Error(`Metadata values cannot include ASCII characters on Item Create requests. Field <${key}> contains value '${val}'.`);
}
headers[`x-archive-meta-${key}`] = val;
});
}
const data = upload?.data ?? (upload?.path ? fs.readFileSync(upload.path) : null);
const path = upload?.filename ? `${identifier}/${upload?.filename}` : identifier;
await this.httpClient.makeRequest(endpoints.createItem, { data, path, headers });
/* returns id and metadata */
return {
identifier,
metadata,
...(upload && {
upload: {
filename: upload.filename,
path,
},
}),
};
}
/**
* Returns Items based on filters and options.
*
* @param items - filters (collection, subject, creator) and options (fields, rows).
* @param items.filters - Filter by collection, subject, creator.
* @param items.options - Options to specify fields returned and amount of items.
* @returns The responseHeader and response with items as docs.
*
* @see {@link https://archive.org/advancedsearch.php Archive.org - Advanced Search API}
*/
async getItems(items) {
const { filters, options } = items || {};
const { fields, rows } = options ?? {};
const params = {
'q': filters?.collection && filters?.subject && filters?.creator
? `collection:(${filters?.collection}) AND subject:(${filters?.subject}) AND creator:(${filters?.creator})`
: filters?.collection && filters?.subject
? `collection:(${filters.collection}) AND subject:(${filters.subject})`
: filters?.collection && filters?.creator
? `collection:(${filters.collection}) AND creator:(${filters.creator})`
: filters?.subject && filters?.creator
? `subject:(${filters.subject}) AND creator:(${filters.creator})`
: filters?.collection
? `collection:(${filters.collection})`
: filters?.subject
? `subject:(${filters.subject})`
: filters?.creator
? `creator:(${filters.creator})`
: null,
...(fields && { 'fl[]': fields.replace(/ /g, '') }),
'rows': Number(rows) || 50,
'output': 'json',
'sort[]': 'date desc',
};
if (!params.q) {
throw new Error('collection, subject, or creator required');
}
return await this.httpClient.makeRequest(endpoints.getItems, { params });
}
/**
* Returns an Item by identifier.
*
* @param identifier - The unique identifier for the item.
* @returns Item metadata, file paths, and other info.
*
* @see {@link https://archive.org/developers/metadata.html Archive.org - Item Metadata API API}
*/
async getItem(identifier) {
return await this.httpClient.makeRequest(endpoints.getItem, { path: identifier });
}
/**
* Updates an Item by identifier and metadata.
*
* @param identifier - The unique identifier for the item.
* @param metadata - The item metadata.
* @returns Update response (success, error, task_id, log).
*
* @see {@link https://archive.org/developers/metadata.html Archive.org - Item Metadata API API}
*/
async updateItem(identifier, metadata) {
const packageInfo = await getPackageInfo();
if (this.options?.setScanner && packageInfo) {
metadata.scanner = `${packageInfo.name}-${packageInfo.version}`;
}
const patch = Object.keys(metadata).map((key) => {
return {
op: 'add',
path: `/${key}`,
value: metadata[key],
};
});
const data = {
'-target': 'metadata',
'-patch': patch,
};
const headers = {
'content-type': 'application/x-www-form-urlencoded;',
};
return await this.httpClient.makeRequest(endpoints.updateItem, { path: identifier, data, headers });
}
/**
* Uploads a File to a parent Item.
*
* @param upload - identifier, mediatype, file.
* @param upload.identifier - The unique identifier of the parent item.
* @param upload.mediatype - The upload mediatype.
* @param upload.file - The upload file.
*
* @see {@link https://archive.org/developers/ias3.html Archive.org - ias3 Internet archive S3-like API}
*/
async uploadFile(upload) {
const { identifier, mediatype, file } = upload;
const { path, filename, data: buffer } = file || {};
const headers = {
'x-archive-interactive-priority': 1,
'x-archive-meta-mediatype': mediatype,
};
if (!filename) {
throw new Error('filename required');
}
const data = buffer ?? (path ? fs.readFileSync(path) : null);
if (!data) {
throw new Error('buffer or path required');
}
return await this.httpClient.makeRequest(endpoints.uploadFile, { data, path: `${identifier}/${filename}`, headers });
}
/**
* Deletes a File from an Item.
*
* @param path - The path of the file [identifier/filename].
*
* @see {@link https://archive.org/developers/ias3.html Archive.org - ias3 Internet archive S3-like API}
*/
async deleteFile(path) {
const headers = {
'x-archive-cascade-delete': 1,
};
return await this.httpClient.makeRequest(endpoints.deleteFile, { path, headers });
}
/**
* Returns Tasks from an Item.
*
* @param identifier - identifier, mediatype, file.
* @param criteria - Parameters to filter item tasks.
*
* @see {@link https://archive.org/developers/tasks.html Archive.org - Tasks API}
*/
async getItemTasks(identifier, criteria) {
const params = {
identifier,
...criteria,
};
return await this.httpClient.makeRequest(endpoints.getTask, { params });
}
}
export default InternetArchive;