@ffflorian/icanhazdadjoke 2.4.6 → 2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # ICanHazDadJoke [![npm version](https://img.shields.io/npm/v/@ffflorian/icanhazdadjoke.svg)](https://www.npmjs.com/package/@ffflorian/icanhazdadjoke)
2
2
 
3
- An [ICanHazDadJoke](https://icanhazdadjoke.com) API client with a CLI.
3
+ An [ICanHazDadJoke](https://icanhazdadjoke.com) API client with a CLI. For a documentation on the API see [the icanhazdadjoke API reference](https://icanhazdadjoke.com/api).
4
4
 
5
5
  ## Usage
6
6
 
@@ -0,0 +1,13 @@
1
+ import type { ClientOptions } from './Interfaces';
2
+ import { ICanHazDadJokeAPI } from './ICanHazDadJokeAPI';
3
+ export declare class ICanHazDadJoke {
4
+ readonly api: ICanHazDadJokeAPI;
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
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ICanHazDadJoke = void 0;
4
+ const api_client_1 = require("@ffflorian/api-client");
5
+ const ICanHazDadJokeAPI_1 = require("./ICanHazDadJokeAPI");
6
+ class ICanHazDadJoke {
7
+ constructor(options) {
8
+ this.options = Object.assign({ apiUrl: 'https://icanhazdadjoke.com' }, options);
9
+ this.apiClient = new api_client_1.APIClient(this.options.apiUrl, {
10
+ headers: {
11
+ Accept: 'application/json',
12
+ 'User-Agent': 'icanhazdadjoke.com API Client (https://github.com/ffflorian/api-clients/tree/main/packages/icanhazdadjoke)',
13
+ },
14
+ });
15
+ this.api = new ICanHazDadJokeAPI_1.ICanHazDadJokeAPI(this.apiClient, this.options);
16
+ }
17
+ /**
18
+ * Set a new API URL.
19
+ * @param url The new API URL.
20
+ */
21
+ setApiUrl(url) {
22
+ this.apiClient.setBaseURL(url);
23
+ }
24
+ }
25
+ exports.ICanHazDadJoke = ICanHazDadJoke;
@@ -0,0 +1,49 @@
1
+ import type { APIClient } from '@ffflorian/api-client';
2
+ import type { ClientOptions, GraphQLResponse, JokeResult, JokeResultWithImage, JokeSearchResult, RequestOptions, SearchOptions, SlackJokeResult } from './Interfaces';
3
+ export declare class ICanHazDadJokeAPI {
4
+ protected readonly apiClient: APIClient;
5
+ protected readonly options: ClientOptions;
6
+ constructor(apiClient: APIClient, options: ClientOptions);
7
+ /**
8
+ * Fetch a dad joke by ID.
9
+ * @see https://icanhazdadjoke.com/api#fetch-a-dad-joke
10
+ */
11
+ getById(id: string, options: {
12
+ withImage: true;
13
+ }): Promise<JokeResultWithImage>;
14
+ getById(id: string, options?: RequestOptions): Promise<JokeResult>;
15
+ /**
16
+ * Fetch a dad joke by ID.
17
+ * @see https://icanhazdadjoke.com/api#fetch-a-dad-joke
18
+ */
19
+ getByID(id: string, options: {
20
+ withImage: true;
21
+ }): Promise<JokeResultWithImage>;
22
+ getByID(id: string, options?: RequestOptions): Promise<JokeResult>;
23
+ /**
24
+ * Fetch a random dad joke.
25
+ * @see https://icanhazdadjoke.com/api#fetch-a-random-dad-joke
26
+ */
27
+ getRandom(options: {
28
+ withImage: true;
29
+ }): Promise<JokeResultWithImage>;
30
+ getRandom(options?: RequestOptions): Promise<JokeResult>;
31
+ /**
32
+ * Fetch a random dad joke as a Slack message.
33
+ * @see https://icanhazdadjoke.com/api#fetch-a-random-dad-joke-as-a-slack-message
34
+ */
35
+ getSlack(): Promise<SlackJokeResult>;
36
+ /**
37
+ * Execute a GraphQL query.
38
+ * @see https://icanhazdadjoke.com/api#graphql
39
+ */
40
+ graphql(query: string): Promise<GraphQLResponse>;
41
+ /**
42
+ * Search for dad jokes.
43
+ * @param options Search options (default: list all jokes)
44
+ * @see https://icanhazdadjoke.com/api#search-for-dad-jokes
45
+ */
46
+ search(options: SearchOptions): Promise<JokeSearchResult>;
47
+ search(query: string, options?: Omit<SearchOptions, 'term'>): Promise<JokeSearchResult>;
48
+ private getImage;
49
+ }
@@ -0,0 +1,89 @@
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.ICanHazDadJokeAPI = void 0;
13
+ const MIN_SEARCH_LIMIT = 1;
14
+ const MAX_SEARCH_LIMIT = 30;
15
+ class ICanHazDadJokeAPI {
16
+ constructor(apiClient, options) {
17
+ this.apiClient = apiClient;
18
+ this.options = options;
19
+ }
20
+ getById(id, options = {}) {
21
+ return this.getByID(id, options);
22
+ }
23
+ getByID(id_1) {
24
+ return __awaiter(this, arguments, void 0, function* (id, options = {}) {
25
+ const { data: metaData } = yield this.apiClient.get(`/j/${id}`);
26
+ if (options.withImage) {
27
+ const imageData = yield this.getImage(`/j/${id}.png`);
28
+ return Object.assign(Object.assign({}, metaData), { image: imageData });
29
+ }
30
+ return metaData;
31
+ });
32
+ }
33
+ getRandom() {
34
+ return __awaiter(this, arguments, void 0, function* (options = {}) {
35
+ const { data: metaData } = yield this.apiClient.get('/');
36
+ if (options.withImage) {
37
+ const imageData = yield this.getImage(`/j/${metaData.id}.png`);
38
+ return Object.assign(Object.assign({}, metaData), { image: imageData });
39
+ }
40
+ return metaData;
41
+ });
42
+ }
43
+ /**
44
+ * Fetch a random dad joke as a Slack message.
45
+ * @see https://icanhazdadjoke.com/api#fetch-a-random-dad-joke-as-a-slack-message
46
+ */
47
+ getSlack() {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ const { data } = yield this.apiClient.get('/slack');
50
+ return data;
51
+ });
52
+ }
53
+ /**
54
+ * Execute a GraphQL query.
55
+ * @see https://icanhazdadjoke.com/api#graphql
56
+ */
57
+ graphql(query) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const { data } = yield this.apiClient.post('/graphql', { query });
60
+ return data;
61
+ });
62
+ }
63
+ search(query_1) {
64
+ return __awaiter(this, arguments, void 0, function* (query, options = {}) {
65
+ if (typeof query === 'string') {
66
+ options.term = query;
67
+ }
68
+ if (options.limit !== undefined && (options.limit < MIN_SEARCH_LIMIT || options.limit > MAX_SEARCH_LIMIT)) {
69
+ throw new Error(`The search limit must be between ${MIN_SEARCH_LIMIT} and ${MAX_SEARCH_LIMIT}, got ${options.limit}.`);
70
+ }
71
+ const { data } = yield this.apiClient.get('/search', {
72
+ params: options,
73
+ });
74
+ return data;
75
+ });
76
+ }
77
+ getImage(imageUrl) {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ const { data } = yield this.apiClient.get(imageUrl, {
80
+ headers: {
81
+ Accept: 'image/png',
82
+ },
83
+ responseType: 'arraybuffer',
84
+ });
85
+ return data;
86
+ });
87
+ }
88
+ }
89
+ exports.ICanHazDadJokeAPI = ICanHazDadJokeAPI;
@@ -0,0 +1,54 @@
1
+ export interface ClientOptions {
2
+ apiUrl?: string;
3
+ }
4
+ export interface GraphQLData {
5
+ joke: GraphQLJokeResult;
6
+ }
7
+ export interface GraphQLJokeResult {
8
+ id: string;
9
+ joke: string;
10
+ permalink: string;
11
+ }
12
+ export interface GraphQLResponse {
13
+ data: GraphQLData;
14
+ }
15
+ export interface JokeResult {
16
+ id: string;
17
+ joke: string;
18
+ status: number;
19
+ }
20
+ export interface JokeResultWithImage extends JokeResult {
21
+ image: Buffer;
22
+ }
23
+ export interface JokeSearchResult {
24
+ current_page: number;
25
+ limit: number;
26
+ next_page: number;
27
+ previous_page: number;
28
+ results: JokeResult[];
29
+ search_term: string;
30
+ status: number;
31
+ total_jokes: number;
32
+ total_pages: number;
33
+ }
34
+ export interface RequestOptions {
35
+ withImage?: boolean;
36
+ }
37
+ export interface SearchOptions {
38
+ /** number of results to return per page (default: 20) (max: 30) */
39
+ limit?: number;
40
+ /** which page of results to fetch (default: 1) */
41
+ page?: number;
42
+ /** search term to use (default: list all jokes) */
43
+ term?: string;
44
+ }
45
+ export interface SlackAttachment {
46
+ fallback: string;
47
+ footer: string;
48
+ text: string;
49
+ }
50
+ export interface SlackJokeResult {
51
+ attachments: SlackAttachment[];
52
+ response_type: string;
53
+ username: string;
54
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,123 @@
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 iCanHazDadJoke = new _1.ICanHazDadJoke();
57
+ return [resolvedPath, iCanHazDadJoke];
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_1, imageResult_1) {
65
+ return __awaiter(this, arguments, void 0, function* (filePath, imageResult, silent = false) {
66
+ const { id, image } = imageResult;
67
+ const resolvedFilePath = path.resolve(filePath, `dad_joke_${id}.png`);
68
+ yield node_fs_1.promises.writeFile(resolvedFilePath, image);
69
+ if (!silent) {
70
+ console.info(`Saved image to "${resolvedFilePath}".`);
71
+ }
72
+ });
73
+ }
74
+ commander_1.program.on('command:*', () => commander_1.program.help());
75
+ commander_1.program
76
+ .name(package_json_1.name.replace(/^@[^/]+\//, ''))
77
+ .version(package_json_1.version, '-v, --version')
78
+ .description(package_json_1.description)
79
+ .option('-i, --image', 'Save the joke as image')
80
+ .option('-o, --output <dir>', 'Specify the output directory', path.resolve('.'))
81
+ .option('-s, --silent', `Don't output save messages`);
82
+ commander_1.program
83
+ .command('random')
84
+ .description('Fetch a random joke')
85
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
86
+ const options = commander_1.program.opts();
87
+ try {
88
+ const [resolvedPath, iCanHazDadJoke] = yield init(options.output);
89
+ const result = yield iCanHazDadJoke.api.getRandom({ withImage: !!options.image });
90
+ console.info(result.joke);
91
+ if (options.image) {
92
+ yield save(resolvedPath, result, !!options.silent);
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.error(`Error: ${error.message}`);
97
+ process.exit(1);
98
+ }
99
+ }));
100
+ commander_1.program
101
+ .command('id')
102
+ .argument('<id>', 'The joke ID')
103
+ .description('Fetch joke by ID')
104
+ .action((id) => __awaiter(void 0, void 0, void 0, function* () {
105
+ const options = commander_1.program.opts();
106
+ try {
107
+ const [resolvedPath, iCanHazDadJoke] = yield init(options.output);
108
+ const result = yield iCanHazDadJoke.api.getById(id, { withImage: !!options.image });
109
+ console.info(result.joke);
110
+ if (options.image) {
111
+ yield save(resolvedPath, result, !!options.silent);
112
+ }
113
+ }
114
+ catch (error) {
115
+ console.error(`Error: ${error.message}`);
116
+ process.exit(1);
117
+ }
118
+ }));
119
+ commander_1.program.parse(process.argv);
120
+ if (!process.argv.slice(2).length) {
121
+ commander_1.program.outputHelp();
122
+ process.exit(1);
123
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ICanHazDadJoke';
2
+ export * from './ICanHazDadJokeAPI';
3
+ export * from './Interfaces';
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("./ICanHazDadJoke"), exports);
18
+ __exportStar(require("./ICanHazDadJokeAPI"), exports);
19
+ __exportStar(require("./Interfaces"), exports);
package/package.json CHANGED
@@ -2,16 +2,18 @@
2
2
  "author": "Florian Imdahl <git@ffflorian.de>",
3
3
  "bin": "dist/cli.js",
4
4
  "dependencies": {
5
- "@ffflorian/api-client": "2.5.0",
5
+ "@ffflorian/api-client": "2.5.1",
6
6
  "commander": "14.0.3"
7
7
  },
8
8
  "description": "An icanhazdadjoke.com API client with a CLI.",
9
9
  "devDependencies": {
10
10
  "@types/node": "~24",
11
+ "nock": "14.0.12",
11
12
  "rimraf": "6.1.3",
12
13
  "tsx": "4.21.0",
13
- "typedoc": "0.28.18",
14
- "typescript": "6.0.2"
14
+ "typedoc": "0.28.19",
15
+ "typescript": "6.0.2",
16
+ "vitest": "4.1.4"
15
17
  },
16
18
  "engines": {
17
19
  "node": ">= 21"
@@ -31,12 +33,13 @@
31
33
  "readme": "https://github.com/ffflorian/api-clients#readme",
32
34
  "repository": "https://github.com/ffflorian/api-clients.git",
33
35
  "scripts": {
36
+ "build": "yarn build:ts",
34
37
  "build:ts": "tsc -p tsconfig.build.json",
35
38
  "build:docs": "typedoc --tsconfig tsconfig.build.json --options ../../typedoc.json --out ../../docs/packages/icanhazdadjoke src/index.ts",
36
39
  "clean": "rimraf dist",
37
- "dist": "yarn clean && yarn build:ts",
40
+ "dist": "yarn clean && yarn build",
38
41
  "start": "tsx src/cli.ts",
39
- "test": "exit 0"
42
+ "test": "vitest"
40
43
  },
41
- "version": "2.4.6"
44
+ "version": "2.5.0"
42
45
  }