Skip to content

Commit a3e39db

Browse files
committed
started on mdn json api usage
1 parent 9aefaef commit a3e39db

File tree

3 files changed

+121
-2
lines changed

3 files changed

+121
-2
lines changed

src/commands/mdn/index.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import * as errors from '../../utils/errors';
44
import { getSearchUrl } from '../../utils/urlTools';
55
import useData from '../../utils/useData';
66

7-
import { queryBuilder } from '.';
7+
import { queryBuilder, updatedQueryBuilder } from '.';
8+
import { getChosenResult } from '../../utils/discordTools';
9+
import { searchResponse } from './__fixtures__/responses';
810

911
jest.mock('dom-parser');
1012
jest.mock('../../utils/urlTools');
1113
jest.mock('../../utils/useData');
1214

1315
const mockGetSearchUrl: jest.MockedFunction<typeof getSearchUrl> = getSearchUrl as any;
1416
const mockUseData: jest.MockedFunction<typeof useData> = useData as any;
17+
const mockChoose: jest.MockedFunction<typeof getChosenResult> = getChosenResult as any;
1518

1619
describe('handleMDNQuery', () => {
1720
const sendMock = jest.fn();
@@ -115,3 +118,28 @@ describe('handleMDNQuery', () => {
115118
expect(sentMessage).toMatchSnapshot();
116119
});
117120
});
121+
122+
describe('updatedMDNQuery', () => {
123+
const sendMock = jest.fn();
124+
const replyMock = jest.fn();
125+
const msg: any = {
126+
channel: { send: sendMock },
127+
reply: replyMock,
128+
};
129+
130+
test('should work', async () => {
131+
mockGetSearchUrl.mockReturnValue('http://example.com');
132+
mockUseData.mockResolvedValueOnce({
133+
error: false,
134+
text: null,
135+
json: searchResponse,
136+
});
137+
138+
const handler = updatedQueryBuilder(
139+
mockGetSearchUrl,
140+
mockUseData,
141+
mockChoose
142+
);
143+
await handler(msg, 'Search Term');
144+
});
145+
});

src/commands/mdn/index.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,38 @@ interface ParserResult {
2727
meta: string;
2828
}
2929

30+
interface SearchResponse {
31+
query: string;
32+
locale: string;
33+
page: number;
34+
pages: number;
35+
starts: number;
36+
end: number;
37+
next: string;
38+
previous: string | null;
39+
count: number;
40+
filter: Array<{
41+
name: string;
42+
slug: string;
43+
options: Array<{
44+
name: string;
45+
slug: string;
46+
count: number;
47+
active: boolean;
48+
urls: {
49+
active: string;
50+
inactive: string;
51+
};
52+
}>;
53+
}>;
54+
documents: Array<{
55+
title: string;
56+
slug: string;
57+
locale: string;
58+
excerpt: string;
59+
}>;
60+
}
61+
3062
interface ResultMeta {
3163
getElementsByClassName(cls: string): DOMParser.Node[];
3264
}
@@ -75,6 +107,65 @@ const extractMetadataFromResult = (result: ResultMeta) => {
75107
};
76108
};
77109

110+
export const updatedQueryBuilder = (
111+
searchUrl: typeof getSearchUrl = getSearchUrl,
112+
fetch: typeof useData = useData,
113+
waitForChosenResult: typeof getChosenResult = getChosenResult
114+
) => async (msg: Message, searchTerm: string) => {
115+
const url = searchUrl(provider, searchTerm);
116+
const { error, json } = await fetch<SearchResponse>(url, 'json');
117+
if (!error) {
118+
return msg.reply(errors.invalidResponse);
119+
}
120+
121+
if (json.documents.length === 0) {
122+
const sentMsg = await msg.reply(errors.noResults(searchTerm));
123+
return delayedMessageAutoDeletion(sentMsg);
124+
}
125+
126+
let preparedDescription = json.documents.map(
127+
({ title, excerpt, slug }, index) =>
128+
createMarkdownListItem(
129+
index,
130+
createMarkdownLink(
131+
adjustTitleLength([`**${title}**`, excerpt].join(' - ')),
132+
buildDirectUrl(provider, slug)
133+
)
134+
)
135+
);
136+
137+
const expectedLength = preparedDescription.reduce(
138+
(sum, item) => sum + item.length,
139+
0
140+
);
141+
if (expectedLength + BASE_DESCRIPTION.length + 10 * '\n'.length > 2048) {
142+
preparedDescription = preparedDescription.map(string => {
143+
// split at markdown link ending
144+
const [title, ...rest] = string.split('...]');
145+
146+
// split title on title - excerpt glue
147+
// concat with rest
148+
// fix broken markdown link ending
149+
return [title.split(' - ')[0], rest.join('')].join(']');
150+
});
151+
}
152+
153+
const sentMsg = await msg.channel.send(
154+
createListEmbed({
155+
description: createDescription(preparedDescription),
156+
footerText: 'Powered by the search API',
157+
provider,
158+
searchTerm,
159+
url,
160+
})
161+
);
162+
163+
const result = await waitForChosenResult(sentMsg, msg, json.documents);
164+
if (!result) {
165+
return;
166+
}
167+
};
168+
78169
/**
79170
* Poor man's dependency injection without introducing classes, just use closures
80171
* and higher order functions instead. Also provides a default so we don't have

src/utils/urlTools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const providers: ProviderMap = {
7575
createTitle: (searchTerm: string) => `NPM results for *${searchTerm}*`,
7676
help: '!npm react',
7777
icon: 'https://avatars0.githubusercontent.com/u/6078720',
78-
search: `https://www.npmjs.com/search/suggestions?q=${SEARCH_TERM}`,
78+
search: `https://developer.mozilla.org/api/v1/search/en-US?highlight=false&q=${SEARCH_TERM}`,
7979
},
8080
php: {
8181
color: 0x8892bf,

0 commit comments

Comments
 (0)