@ffflorian/xkcdjs 3.4.6 → 3.5.0
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.
- package/README.md +3 -3
- package/dist/Interfaces.d.ts +26 -0
- package/dist/Interfaces.js +2 -0
- package/dist/XKCD.d.ts +13 -0
- package/dist/XKCD.js +20 -0
- package/dist/XKCDAPI.d.ts +35 -0
- package/dist/XKCDAPI.js +68 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +135 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# xkcdjs [](https://www.npmjs.com/package/@ffflorian/xkcdjs)
|
|
2
2
|
|
|
3
|
-
An [xkcd](https://xkcd.com) API client with a CLI.
|
|
3
|
+
An [xkcd](https://xkcd.com) API client with a CLI. For a documentation on the API see [the xkcd API reference](https://xkcd.com/json.html).
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ xkcd.api.getRandom().then(result => {
|
|
|
45
45
|
// XKCDResult
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
xkcd.api.
|
|
48
|
+
xkcd.api.getByIndex(2036).then(result => {
|
|
49
49
|
// XKCDResult
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ xkcd.api.getRandom({withData: true}).then(result => {
|
|
|
57
57
|
// XKCDResultWithData
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
xkcd.api.
|
|
60
|
+
xkcd.api.getByIndex(2036, {withData: true}).then(result => {
|
|
61
61
|
// XKCDResultWithData
|
|
62
62
|
});
|
|
63
63
|
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ClientOptions {
|
|
2
|
+
apiUrl?: string;
|
|
3
|
+
}
|
|
4
|
+
export interface ImageData {
|
|
5
|
+
data: Buffer;
|
|
6
|
+
mimeType?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RequestOptions {
|
|
9
|
+
withData?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface XKCDResult {
|
|
12
|
+
alt: string;
|
|
13
|
+
day: string;
|
|
14
|
+
img: string;
|
|
15
|
+
link: string;
|
|
16
|
+
month: string;
|
|
17
|
+
news: string;
|
|
18
|
+
num: number;
|
|
19
|
+
safe_title: string;
|
|
20
|
+
title: string;
|
|
21
|
+
transcript: string;
|
|
22
|
+
year: string;
|
|
23
|
+
}
|
|
24
|
+
export interface XKCDResultWithData extends XKCDResult {
|
|
25
|
+
data: ImageData;
|
|
26
|
+
}
|
package/dist/XKCD.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClientOptions } from './Interfaces';
|
|
2
|
+
import { XKCDAPI } from './XKCDAPI';
|
|
3
|
+
export declare class XKCD {
|
|
4
|
+
readonly api: XKCDAPI;
|
|
5
|
+
private readonly apiClient;
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(options?: ClientOptions);
|
|
8
|
+
/**
|
|
9
|
+
* Set a new API URL.
|
|
10
|
+
* @param url The new API URL.
|
|
11
|
+
*/
|
|
12
|
+
setApiUrl(url: string): void;
|
|
13
|
+
}
|
package/dist/XKCD.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.XKCD = void 0;
|
|
4
|
+
const api_client_1 = require("@ffflorian/api-client");
|
|
5
|
+
const XKCDAPI_1 = require("./XKCDAPI");
|
|
6
|
+
class XKCD {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = Object.assign({ apiUrl: 'https://xkcd.com' }, options);
|
|
9
|
+
this.apiClient = new api_client_1.APIClient(this.options.apiUrl);
|
|
10
|
+
this.api = new XKCDAPI_1.XKCDAPI(this.apiClient, this.options);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Set a new API URL.
|
|
14
|
+
* @param url The new API URL.
|
|
15
|
+
*/
|
|
16
|
+
setApiUrl(url) {
|
|
17
|
+
this.apiClient.setBaseURL(url);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.XKCD = XKCD;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { APIClient } from '@ffflorian/api-client';
|
|
2
|
+
import type { ClientOptions, RequestOptions, XKCDResult, XKCDResultWithData } from './Interfaces';
|
|
3
|
+
export declare class XKCDAPI {
|
|
4
|
+
protected readonly apiClient: APIClient;
|
|
5
|
+
protected readonly options: ClientOptions;
|
|
6
|
+
private readonly JSON_INFO_FILE;
|
|
7
|
+
private readonly lowestIndex;
|
|
8
|
+
constructor(apiClient: APIClient, options: ClientOptions);
|
|
9
|
+
/**
|
|
10
|
+
* Get a comic by index.
|
|
11
|
+
* @param index Index number
|
|
12
|
+
* @param options Request options
|
|
13
|
+
*/
|
|
14
|
+
getByIndex(index: number, options: {
|
|
15
|
+
withData: true;
|
|
16
|
+
}): Promise<XKCDResultWithData>;
|
|
17
|
+
getByIndex(index: number, options?: RequestOptions): Promise<XKCDResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Get the latest comic.
|
|
20
|
+
* @param options Request options
|
|
21
|
+
*/
|
|
22
|
+
getLatest(options: {
|
|
23
|
+
withData: true;
|
|
24
|
+
}): Promise<XKCDResultWithData>;
|
|
25
|
+
getLatest(options?: RequestOptions): Promise<XKCDResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Get a random comic.
|
|
28
|
+
* @param options Request options
|
|
29
|
+
*/
|
|
30
|
+
getRandom(options: {
|
|
31
|
+
withData: true;
|
|
32
|
+
}): Promise<XKCDResultWithData>;
|
|
33
|
+
getRandom(options?: RequestOptions): Promise<XKCDResult>;
|
|
34
|
+
private getImage;
|
|
35
|
+
}
|
package/dist/XKCDAPI.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.XKCDAPI = void 0;
|
|
13
|
+
class XKCDAPI {
|
|
14
|
+
constructor(apiClient, options) {
|
|
15
|
+
this.apiClient = apiClient;
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.lowestIndex = 1;
|
|
18
|
+
this.JSON_INFO_FILE = 'info.0.json';
|
|
19
|
+
}
|
|
20
|
+
getByIndex(index_1) {
|
|
21
|
+
return __awaiter(this, arguments, void 0, function* (index, options = {}) {
|
|
22
|
+
if (index < this.lowestIndex) {
|
|
23
|
+
throw new Error(`Index is lower than the lowest index of ${this.lowestIndex}.`);
|
|
24
|
+
}
|
|
25
|
+
const { data: metaData } = yield this.apiClient.get(`/${index}/${this.JSON_INFO_FILE}`);
|
|
26
|
+
if (options.withData === true) {
|
|
27
|
+
const imageData = yield this.getImage(metaData.img);
|
|
28
|
+
return Object.assign(Object.assign({}, metaData), { data: imageData });
|
|
29
|
+
}
|
|
30
|
+
return metaData;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
getLatest() {
|
|
34
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
35
|
+
const { data: metaData } = yield this.apiClient.get(`/${this.JSON_INFO_FILE}`);
|
|
36
|
+
if (options.withData) {
|
|
37
|
+
const imageData = yield this.getImage(metaData.img);
|
|
38
|
+
return Object.assign(Object.assign({}, metaData), { data: imageData });
|
|
39
|
+
}
|
|
40
|
+
return metaData;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
getRandom() {
|
|
44
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
45
|
+
const latest = yield this.getLatest();
|
|
46
|
+
const randomIndex = Math.floor(Math.random() * (latest.num - this.lowestIndex + 1)) + this.lowestIndex;
|
|
47
|
+
const metaData = yield this.getByIndex(randomIndex);
|
|
48
|
+
if (options.withData === true) {
|
|
49
|
+
const imageData = yield this.getImage(metaData.img);
|
|
50
|
+
return Object.assign(Object.assign({}, metaData), { data: imageData });
|
|
51
|
+
}
|
|
52
|
+
return metaData;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
getImage(imageUrl) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
const { data, headers } = yield this.apiClient.get(imageUrl, {
|
|
58
|
+
responseType: 'arraybuffer',
|
|
59
|
+
});
|
|
60
|
+
const contentType = headers.get('content-type');
|
|
61
|
+
return {
|
|
62
|
+
data,
|
|
63
|
+
mimeType: contentType ? String(contentType) : undefined,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.XKCDAPI = XKCDAPI;
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
const commander_1 = require("commander");
|
|
47
|
+
const node_fs_1 = require("node:fs");
|
|
48
|
+
const path = __importStar(require("node:path"));
|
|
49
|
+
const package_json_1 = require("../package.json");
|
|
50
|
+
const _1 = require("./");
|
|
51
|
+
function init() {
|
|
52
|
+
return __awaiter(this, arguments, void 0, function* (dir = '.') {
|
|
53
|
+
const resolvedPath = path.resolve(dir);
|
|
54
|
+
try {
|
|
55
|
+
yield node_fs_1.promises.access(resolvedPath, node_fs_1.constants.F_OK | node_fs_1.constants.R_OK);
|
|
56
|
+
const xkcd = new _1.XKCD();
|
|
57
|
+
return [resolvedPath, xkcd];
|
|
58
|
+
}
|
|
59
|
+
catch (_a) {
|
|
60
|
+
throw new Error('The specified path does not exist or is not writable.');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function save(filePath, imageResult) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const { data, num, safe_title } = imageResult;
|
|
67
|
+
const extension = data.mimeType ? data.mimeType.replace('image/', '') : 'png';
|
|
68
|
+
const resolvedFilePath = path.resolve(filePath, `xkcd #${num} - ${safe_title}.${extension}`);
|
|
69
|
+
yield node_fs_1.promises.writeFile(resolvedFilePath, data.data);
|
|
70
|
+
console.info(`Saved image to "${resolvedFilePath}".`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
commander_1.program.on('command:*', () => commander_1.program.help());
|
|
74
|
+
commander_1.program
|
|
75
|
+
.name(package_json_1.name.replace(/^@[^/]+\//, ''))
|
|
76
|
+
.version(package_json_1.version, '-v, --version')
|
|
77
|
+
.description(package_json_1.description)
|
|
78
|
+
.option('-o, --output <dir>', 'Specify the output directory', path.resolve('.'));
|
|
79
|
+
commander_1.program
|
|
80
|
+
.command('latest')
|
|
81
|
+
.description('Save the latest comic')
|
|
82
|
+
.action((command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
+
try {
|
|
84
|
+
const [resolvedPath, xkcd] = yield init(command.parent.output);
|
|
85
|
+
const imageData = yield xkcd.api.getLatest({ withData: true });
|
|
86
|
+
yield save(resolvedPath, imageData);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error(`Error: ${error.message}`);
|
|
90
|
+
commander_1.program.outputHelp();
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
commander_1.program
|
|
95
|
+
.command('random')
|
|
96
|
+
.description('Save a random comic')
|
|
97
|
+
.action((command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
98
|
+
try {
|
|
99
|
+
const [resolvedPath, xkcd] = yield init(command.parent.output);
|
|
100
|
+
const imageData = yield xkcd.api.getRandom({ withData: true });
|
|
101
|
+
yield save(resolvedPath, imageData);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`Error: ${error.message}`);
|
|
105
|
+
commander_1.program.outputHelp();
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
commander_1.program
|
|
110
|
+
.command('number <index>')
|
|
111
|
+
.description('Save comic by index number')
|
|
112
|
+
.action((index, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
+
let parsedIndex;
|
|
114
|
+
try {
|
|
115
|
+
parsedIndex = parseInt(index, 10);
|
|
116
|
+
}
|
|
117
|
+
catch (_a) {
|
|
118
|
+
throw new Error('Invalid number specified.');
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const [resolvedPath, xkcd] = yield init(command.parent.output);
|
|
122
|
+
const imageData = yield xkcd.api.getByIndex(parsedIndex, { withData: true });
|
|
123
|
+
yield save(resolvedPath, imageData);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.error(`Error: ${error.message}`);
|
|
127
|
+
commander_1.program.outputHelp();
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
commander_1.program.parse(process.argv);
|
|
132
|
+
if (!process.argv.slice(2).length) {
|
|
133
|
+
commander_1.program.outputHelp();
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./Interfaces"), exports);
|
|
18
|
+
__exportStar(require("./XKCD"), exports);
|
|
19
|
+
__exportStar(require("./XKCDAPI"), exports);
|
package/package.json
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
"author": "Florian Imdahl <git@ffflorian.de>",
|
|
3
3
|
"bin": "dist/cli.js",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"@ffflorian/api-client": "2.5.
|
|
5
|
+
"@ffflorian/api-client": "2.5.1",
|
|
6
6
|
"commander": "14.0.3"
|
|
7
7
|
},
|
|
8
8
|
"description": "An xkcd API client with a CLI.",
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@types/node": "~24",
|
|
11
11
|
"http-status-codes": "2.3.0",
|
|
12
|
-
"nock": "14.0.
|
|
12
|
+
"nock": "14.0.12",
|
|
13
13
|
"rimraf": "6.1.3",
|
|
14
14
|
"tsx": "4.21.0",
|
|
15
|
-
"typedoc": "0.28.
|
|
16
|
-
"typescript": "6.0.
|
|
17
|
-
"vitest": "4.1.
|
|
15
|
+
"typedoc": "0.28.19",
|
|
16
|
+
"typescript": "6.0.3",
|
|
17
|
+
"vitest": "4.1.5"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">= 21"
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
"readme": "https://github.com/ffflorian/api-clients#readme",
|
|
35
35
|
"repository": "https://github.com/ffflorian/api-clients.git",
|
|
36
36
|
"scripts": {
|
|
37
|
+
"build": "yarn build:ts",
|
|
37
38
|
"build:ts": "tsc -p tsconfig.build.json",
|
|
38
39
|
"build:docs": "typedoc --tsconfig tsconfig.build.json --options ../../typedoc.json --out ../../docs/packages/xkcdjs src/index.ts",
|
|
39
40
|
"clean": "rimraf dist",
|
|
40
|
-
"dist": "yarn clean && yarn build
|
|
41
|
+
"dist": "yarn clean && yarn build",
|
|
41
42
|
"start": "tsx src/cli.ts",
|
|
42
43
|
"test": "vitest"
|
|
43
44
|
},
|
|
44
|
-
"version": "3.
|
|
45
|
+
"version": "3.5.0"
|
|
45
46
|
}
|