@anmiles/google-api-wrapper 1.0.1 → 2.0.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/CHANGELOG.md +4 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/lib/api/__tests__/apiHelpers.ts +18 -0
- package/src/lib/api/__tests__/calendar.test.ts +116 -0
- package/src/lib/api/__tests__/shared.test.ts +73 -0
- package/src/lib/api/__tests__/youtube.test.ts +99 -0
- package/src/lib/api/calendar.ts +26 -0
- package/src/lib/api/shared.ts +44 -0
- package/src/lib/api/youtube.ts +22 -0
- package/src/lib/__tests__/data.test.ts +0 -154
- package/src/lib/data.ts +0 -81
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.0](../../tags/v2.0.0) - 2023-03-12
|
|
9
|
+
### Changed
|
|
10
|
+
- Split APIs
|
|
11
|
+
|
|
8
12
|
## [1.0.0](../../tags/v1.0.0) - 2023-03-12
|
|
9
13
|
### Changed
|
|
10
14
|
- First release
|
package/README.md
CHANGED
|
@@ -25,10 +25,10 @@ login("username");
|
|
|
25
25
|
``` js
|
|
26
26
|
/* videos.js */
|
|
27
27
|
|
|
28
|
-
import { getProfiles,
|
|
28
|
+
import { getProfiles, youtube } from '@anmiles/google-api-wrapper';
|
|
29
29
|
|
|
30
30
|
getProfiles().map(async (profile) => {
|
|
31
|
-
const videos = await
|
|
31
|
+
const videos = await youtube.getPlaylistItems(profile, { playlistId : 'LL', part : [ 'snippet' ], maxResults : 50 });
|
|
32
32
|
videos.forEach((video) => console.log(`Downloaded: ${video.snippet?.title}`));
|
|
33
33
|
});
|
|
34
34
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const getAPI = <T>(items: Array<Array<T> | null>, pageTokens: Array<string | undefined>) => ({
|
|
2
|
+
list : jest.fn().mockImplementation(async ({ pageToken }: {pageToken?: string}) => {
|
|
3
|
+
const index = pageTokens.indexOf(pageToken);
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
data : {
|
|
7
|
+
items : items[index],
|
|
8
|
+
nextPageToken : pageTokens[index + 1],
|
|
9
|
+
pageInfo : !items[index] ? null : {
|
|
10
|
+
totalResults : items.reduce((sum, list) => sum + (list?.length || 0), 0),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}),
|
|
15
|
+
update : jest.fn(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export default { getAPI };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { google } from 'googleapis';
|
|
3
|
+
import auth from '../../auth';
|
|
4
|
+
import calendar from '../calendar';
|
|
5
|
+
import shared from '../shared';
|
|
6
|
+
import apiHelpers from './apiHelpers';
|
|
7
|
+
|
|
8
|
+
const original = jest.requireActual('../calendar').default as typeof calendar;
|
|
9
|
+
jest.mock<Partial<typeof calendar>>('../calendar', () => ({
|
|
10
|
+
getAPI : jest.fn().mockImplementation(async () => ({ events : api })),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock<Partial<typeof shared>>('../shared', () => ({
|
|
14
|
+
getItems : jest.fn().mockImplementation(async () => events),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock<Partial<typeof fs>>('fs', () => ({
|
|
18
|
+
writeFileSync : jest.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock('googleapis', () => ({
|
|
22
|
+
google : {
|
|
23
|
+
calendar : jest.fn().mockImplementation(() => ({ events : api })),
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock<Partial<typeof auth>>('../../auth', () => ({
|
|
28
|
+
getAuth : jest.fn().mockImplementation(() => googleAuth),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const profile = 'username';
|
|
32
|
+
|
|
33
|
+
const googleAuth = {
|
|
34
|
+
setCredentials : jest.fn(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const events: Array<{ summary?: string, source?: { url?: string, title?: string} }> = [
|
|
38
|
+
{ summary : 'event 1', source : { title : 'source 1', url : 'https://example.com' } },
|
|
39
|
+
{ summary : 'event 2', source : { title : 'source 2', url : undefined } },
|
|
40
|
+
{ summary : 'event 3', source : { title : undefined, url : undefined } },
|
|
41
|
+
{ summary : 'event 4', source : undefined },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const eventsResponse = [
|
|
45
|
+
[ events[0], events[1] ],
|
|
46
|
+
null,
|
|
47
|
+
[ events[2], events[3] ],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const pageTokens = [
|
|
51
|
+
undefined,
|
|
52
|
+
'token1',
|
|
53
|
+
'token2',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const api = apiHelpers.getAPI(eventsResponse, pageTokens);
|
|
57
|
+
const args = { timeMin : '2010-01-01T00:00:00', timeMax : '2019-12-31T23:59:59' };
|
|
58
|
+
|
|
59
|
+
describe('src/lib/api/calendar', () => {
|
|
60
|
+
describe('getAPI', () => {
|
|
61
|
+
it('should call getAuth', async () => {
|
|
62
|
+
await original.getAPI(profile);
|
|
63
|
+
|
|
64
|
+
expect(auth.getAuth).toBeCalledWith(profile);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should get calendar api', async () => {
|
|
68
|
+
await original.getAPI(profile);
|
|
69
|
+
|
|
70
|
+
expect(google.calendar).toBeCalledWith({ version : 'v3', auth : googleAuth });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return calendar api', async () => {
|
|
74
|
+
const result = await original.getAPI(profile);
|
|
75
|
+
|
|
76
|
+
expect(result).toEqual({ events : api });
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getEvents', () => {
|
|
81
|
+
it('should get api', async () => {
|
|
82
|
+
await original.getEvents(profile, args);
|
|
83
|
+
|
|
84
|
+
expect(calendar.getAPI).toBeCalledWith(profile);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should get items', async () => {
|
|
88
|
+
await original.getEvents(profile, args);
|
|
89
|
+
|
|
90
|
+
expect(shared.getItems).toBeCalledWith(api, args);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return events', async () => {
|
|
94
|
+
const result = await original.getEvents(profile, args);
|
|
95
|
+
|
|
96
|
+
expect(result).toEqual(events);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('setEvent', () => {
|
|
101
|
+
const eventId = 'eventId';
|
|
102
|
+
const updateArgs = { requestBody : { summary : 'summary' } };
|
|
103
|
+
|
|
104
|
+
it('should get api', async () => {
|
|
105
|
+
await original.setEvent(profile, eventId, updateArgs);
|
|
106
|
+
|
|
107
|
+
expect(calendar.getAPI).toBeCalledWith(profile);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should set items', async () => {
|
|
111
|
+
await original.setEvent(profile, eventId, updateArgs);
|
|
112
|
+
|
|
113
|
+
expect(api.update).toBeCalledWith({ eventId, ...updateArgs });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import logger from '../../logger';
|
|
2
|
+
import sleep from '../../sleep';
|
|
3
|
+
import shared from '../shared';
|
|
4
|
+
import apiHelpers from './apiHelpers';
|
|
5
|
+
|
|
6
|
+
const original = jest.requireActual('../shared').default as typeof shared;
|
|
7
|
+
jest.mock<Partial<typeof shared>>('../shared', () => ({
|
|
8
|
+
getItems : jest.fn().mockImplementation(async () => items),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock<Partial<typeof logger>>('../../logger', () => ({
|
|
12
|
+
log : jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock<Partial<typeof sleep>>('../../sleep', () => ({
|
|
16
|
+
sleep : jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const items: Array<{ data: string}> = [
|
|
20
|
+
{ data : 'first' },
|
|
21
|
+
{ data : 'second' },
|
|
22
|
+
{ data : 'third' },
|
|
23
|
+
{ data : 'forth' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const response = [
|
|
27
|
+
[ items[0], items[1] ],
|
|
28
|
+
null,
|
|
29
|
+
[ items[2], items[3] ],
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const pageTokens = [
|
|
33
|
+
undefined,
|
|
34
|
+
'token1',
|
|
35
|
+
'token2',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const api = apiHelpers.getAPI(response, pageTokens);
|
|
39
|
+
const args = { key : 'value' };
|
|
40
|
+
|
|
41
|
+
describe('src/lib/api/shared', () => {
|
|
42
|
+
describe('getItems', () => {
|
|
43
|
+
it('should call API list method for each page', async () => {
|
|
44
|
+
await original.getItems(api, args);
|
|
45
|
+
|
|
46
|
+
pageTokens.forEach((pageToken) => {
|
|
47
|
+
expect(api.list).toBeCalledWith({ ...args, pageToken });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should output progress', async () => {
|
|
52
|
+
await original.getItems(api, args);
|
|
53
|
+
|
|
54
|
+
expect(logger.log).toBeCalledTimes(response.length);
|
|
55
|
+
expect(logger.log).toBeCalledWith('Getting items (2 of 4)...');
|
|
56
|
+
expect(logger.log).toBeCalledWith('Getting items (2 of many)...');
|
|
57
|
+
expect(logger.log).toBeCalledWith('Getting items (4 of 4)...');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should sleep after reach request', async () => {
|
|
61
|
+
await original.getItems(api, args);
|
|
62
|
+
|
|
63
|
+
expect(sleep.sleep).toBeCalledTimes(response.length);
|
|
64
|
+
expect(sleep.sleep).toBeCalledWith(300);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return items data', async () => {
|
|
68
|
+
const items = await original.getItems(api, args);
|
|
69
|
+
|
|
70
|
+
expect(items).toEqual(items);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { google } from 'googleapis';
|
|
3
|
+
import auth from '../../auth';
|
|
4
|
+
import youtube from '../youtube';
|
|
5
|
+
import shared from '../shared';
|
|
6
|
+
import apiHelpers from './apiHelpers';
|
|
7
|
+
|
|
8
|
+
const original = jest.requireActual('../youtube').default as typeof youtube;
|
|
9
|
+
jest.mock<Partial<typeof youtube>>('../youtube', () => ({
|
|
10
|
+
getAPI : jest.fn().mockImplementation(async () => ({ playlistItems : api })),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock<Partial<typeof shared>>('../shared', () => ({
|
|
14
|
+
getItems : jest.fn().mockImplementation(async () => playlistItems),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock<Partial<typeof fs>>('fs', () => ({
|
|
18
|
+
writeFileSync : jest.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock('googleapis', () => ({
|
|
22
|
+
google : {
|
|
23
|
+
youtube : jest.fn().mockImplementation(() => ({ playlistItems : api })),
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock<Partial<typeof auth>>('../../auth', () => ({
|
|
28
|
+
getAuth : jest.fn().mockImplementation(() => googleAuth),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const profile = 'username';
|
|
32
|
+
|
|
33
|
+
const googleAuth = {
|
|
34
|
+
setCredentials : jest.fn(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const playlistItems: Array<{ snippet?: { title?: string, resourceId?: { videoId?: string } } }> = [
|
|
38
|
+
{ snippet : { title : 'video1', resourceId : { videoId : 'video1Id' } } },
|
|
39
|
+
{ snippet : { title : 'video2', resourceId : { videoId : undefined } } },
|
|
40
|
+
{ snippet : { title : undefined, resourceId : undefined } },
|
|
41
|
+
{ snippet : undefined },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const playlistItemsResponse = [
|
|
45
|
+
[ playlistItems[0], playlistItems[1] ],
|
|
46
|
+
null,
|
|
47
|
+
[ playlistItems[2], playlistItems[3] ],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const pageTokens = [
|
|
51
|
+
undefined,
|
|
52
|
+
'token1',
|
|
53
|
+
'token2',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const api = apiHelpers.getAPI(playlistItemsResponse, pageTokens);
|
|
57
|
+
const args = { playlistId : 'LL', part : [ 'snippet' ], maxResults : 50 };
|
|
58
|
+
|
|
59
|
+
describe('src/lib/api/youtube', () => {
|
|
60
|
+
describe('getAPI', () => {
|
|
61
|
+
it('should call getAuth', async () => {
|
|
62
|
+
await original.getAPI(profile);
|
|
63
|
+
|
|
64
|
+
expect(auth.getAuth).toBeCalledWith(profile);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should get youtube api', async () => {
|
|
68
|
+
await original.getAPI(profile);
|
|
69
|
+
|
|
70
|
+
expect(google.youtube).toBeCalledWith({ version : 'v3', auth : googleAuth });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return youtube api', async () => {
|
|
74
|
+
const result = await original.getAPI(profile);
|
|
75
|
+
|
|
76
|
+
expect(result).toEqual({ playlistItems : api });
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getPlaylistItems', () => {
|
|
81
|
+
it('should get api', async () => {
|
|
82
|
+
await original.getPlaylistItems(profile, args);
|
|
83
|
+
|
|
84
|
+
expect(youtube.getAPI).toBeCalledWith(profile);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should call getItems', async () => {
|
|
88
|
+
await original.getPlaylistItems(profile, args);
|
|
89
|
+
|
|
90
|
+
expect(shared.getItems).toBeCalledWith(api, args);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return videos', async () => {
|
|
94
|
+
const result = await original.getPlaylistItems(profile, args);
|
|
95
|
+
|
|
96
|
+
expect(result).toEqual(playlistItems);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import type GoogleApis from 'googleapis';
|
|
3
|
+
import { getAuth } from '../auth';
|
|
4
|
+
import { getItems } from './shared';
|
|
5
|
+
import calendar from './calendar';
|
|
6
|
+
|
|
7
|
+
export { getAPI, getEvents, setEvent };
|
|
8
|
+
export default { getAPI, getEvents, setEvent };
|
|
9
|
+
|
|
10
|
+
async function getAPI(profile: string): Promise<GoogleApis.calendar_v3.Calendar> {
|
|
11
|
+
const googleAuth = await getAuth(profile);
|
|
12
|
+
|
|
13
|
+
return google.calendar({
|
|
14
|
+
version : 'v3',
|
|
15
|
+
auth : googleAuth,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function getEvents(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Events$List) {
|
|
19
|
+
const api = await calendar.getAPI(profile);
|
|
20
|
+
return getItems(api.events, args);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function setEvent(profile: string, eventId: string | undefined, args: GoogleApis.calendar_v3.Params$Resource$Events$Update) {
|
|
24
|
+
const api = await calendar.getAPI(profile);
|
|
25
|
+
api.events.update({ eventId, ...args });
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type GoogleApis from 'googleapis';
|
|
2
|
+
import { log } from '../logger';
|
|
3
|
+
import { sleep } from '../sleep';
|
|
4
|
+
|
|
5
|
+
export { getItems };
|
|
6
|
+
export default { getItems };
|
|
7
|
+
|
|
8
|
+
type CommonApi<TArgs, TResponse> = {
|
|
9
|
+
list: (
|
|
10
|
+
params: TArgs & {pageToken: string | undefined},
|
|
11
|
+
options?: GoogleApis.Common.MethodOptions | undefined
|
|
12
|
+
) => Promise<GoogleApis.Common.GaxiosResponse<TResponse>>
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type CommonResponse<TItem> = {
|
|
16
|
+
items?: TItem[],
|
|
17
|
+
pageInfo?: {
|
|
18
|
+
totalResults?: number | null | undefined
|
|
19
|
+
},
|
|
20
|
+
nextPageToken?: string | null | undefined
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const requestInterval = 300;
|
|
24
|
+
|
|
25
|
+
async function getItems<
|
|
26
|
+
TApi extends CommonApi<TArgs, TResponse>,
|
|
27
|
+
TItem,
|
|
28
|
+
TArgs,
|
|
29
|
+
TResponse extends CommonResponse<TItem>
|
|
30
|
+
>(api: TApi, args: TArgs): Promise<TItem[]> {
|
|
31
|
+
const items: TItem[] = [];
|
|
32
|
+
|
|
33
|
+
let pageToken: string | null | undefined = undefined;
|
|
34
|
+
|
|
35
|
+
do {
|
|
36
|
+
const response: GoogleApis.Common.GaxiosResponse<TResponse> = await api.list({ ...args, pageToken });
|
|
37
|
+
response.data.items?.forEach((item) => items.push(item));
|
|
38
|
+
log(`Getting items (${items.length} of ${response.data.pageInfo?.totalResults || 'many'})...`);
|
|
39
|
+
pageToken = response.data.nextPageToken;
|
|
40
|
+
await sleep(requestInterval);
|
|
41
|
+
} while (pageToken);
|
|
42
|
+
|
|
43
|
+
return items;
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import type GoogleApis from 'googleapis';
|
|
3
|
+
import { getAuth } from '../auth';
|
|
4
|
+
import { getItems } from './shared';
|
|
5
|
+
import youtube from './youtube';
|
|
6
|
+
|
|
7
|
+
export { getAPI, getPlaylistItems };
|
|
8
|
+
export default { getAPI, getPlaylistItems };
|
|
9
|
+
|
|
10
|
+
async function getAPI(profile: string): Promise<GoogleApis.youtube_v3.Youtube> {
|
|
11
|
+
const googleAuth = await getAuth(profile);
|
|
12
|
+
|
|
13
|
+
return google.youtube({
|
|
14
|
+
version : 'v3',
|
|
15
|
+
auth : googleAuth,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getPlaylistItems(profile: string, args: GoogleApis.youtube_v3.Params$Resource$Playlistitems$List) {
|
|
20
|
+
const api = await youtube.getAPI(profile);
|
|
21
|
+
return getItems(api.playlistItems, args);
|
|
22
|
+
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import auth from '../auth';
|
|
3
|
-
import data from '../data';
|
|
4
|
-
import logger from '../logger';
|
|
5
|
-
import sleep from '../sleep';
|
|
6
|
-
|
|
7
|
-
const original = jest.requireActual('../data').default as typeof data;
|
|
8
|
-
jest.mock<Partial<typeof data>>('../data', () => ({
|
|
9
|
-
getItems : jest.fn().mockImplementation(async () => videosList),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
jest.mock<Partial<typeof fs>>('fs', () => ({
|
|
13
|
-
writeFileSync : jest.fn(),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
jest.mock('googleapis', () => ({
|
|
17
|
-
google : {
|
|
18
|
-
calendar : jest.fn().mockImplementation(() => ({ events : eventsAPI })),
|
|
19
|
-
youtube : jest.fn().mockImplementation(() => ({ playlistItems : videosAPI })),
|
|
20
|
-
},
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
jest.mock<Partial<typeof auth>>('../auth', () => ({
|
|
24
|
-
getAuth : jest.fn().mockImplementation(() => googleAuth),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
jest.mock<Partial<typeof logger>>('../logger', () => ({
|
|
28
|
-
log : jest.fn(),
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
jest.mock<Partial<typeof sleep>>('../sleep', () => ({
|
|
32
|
-
sleep : jest.fn(),
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
const profile = 'username';
|
|
36
|
-
|
|
37
|
-
const googleAuth = {
|
|
38
|
-
setCredentials : jest.fn(),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const videosList: Array<{ snippet?: { title?: string, resourceId?: { videoId?: string } } }> = [
|
|
42
|
-
{ snippet : { title : 'video1', resourceId : { videoId : 'video1Id' } } },
|
|
43
|
-
{ snippet : { title : 'video2', resourceId : { videoId : undefined } } },
|
|
44
|
-
{ snippet : { title : undefined, resourceId : undefined } },
|
|
45
|
-
{ snippet : undefined },
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const videos = [
|
|
49
|
-
[ videosList[0], videosList[1] ],
|
|
50
|
-
null,
|
|
51
|
-
[ videosList[2], videosList[3] ],
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
const eventsList: Array<{ snippet?: { title?: string, resourceId?: { videoId?: string } } }> = [
|
|
55
|
-
{ snippet : { title : 'video1', resourceId : { videoId : 'video1Id' } } },
|
|
56
|
-
{ snippet : { title : 'video2', resourceId : { videoId : undefined } } },
|
|
57
|
-
{ snippet : { title : undefined, resourceId : undefined } },
|
|
58
|
-
{ snippet : undefined },
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
const events = [
|
|
62
|
-
[ eventsList[0], eventsList[1] ],
|
|
63
|
-
null,
|
|
64
|
-
[ eventsList[2], eventsList[3] ],
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
const pageTokens = [
|
|
68
|
-
undefined,
|
|
69
|
-
'token1',
|
|
70
|
-
'token2',
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const getAPI = <T>(items: Array<Array<T> | null>) => ({
|
|
74
|
-
list : jest.fn().mockImplementation(async ({ pageToken }: {pageToken?: string}) => {
|
|
75
|
-
const index = pageTokens.indexOf(pageToken);
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
data : {
|
|
79
|
-
items : items[index],
|
|
80
|
-
nextPageToken : pageTokens[index + 1],
|
|
81
|
-
pageInfo : !items[index] ? null : {
|
|
82
|
-
totalResults : items.reduce((sum, list) => sum + (list?.length || 0), 0),
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
}),
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const eventsAPI = getAPI(events);
|
|
90
|
-
const videosAPI = getAPI(videos);
|
|
91
|
-
|
|
92
|
-
const args = { playlistId : 'LL', part : [ 'snippet' ], maxResults : 50 };
|
|
93
|
-
|
|
94
|
-
describe('src/lib/data', () => {
|
|
95
|
-
describe('getItems', () => {
|
|
96
|
-
it('should call API list method for each page', async () => {
|
|
97
|
-
await original.getItems(videosAPI, args);
|
|
98
|
-
|
|
99
|
-
pageTokens.forEach((pageToken) => {
|
|
100
|
-
expect(videosAPI.list).toBeCalledWith({ ...args, pageToken });
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should output progress', async () => {
|
|
105
|
-
await original.getItems(videosAPI, args);
|
|
106
|
-
|
|
107
|
-
expect(logger.log).toBeCalledTimes(videos.length);
|
|
108
|
-
expect(logger.log).toBeCalledWith('Getting items (2 of 4)...');
|
|
109
|
-
expect(logger.log).toBeCalledWith('Getting items (2 of many)...');
|
|
110
|
-
expect(logger.log).toBeCalledWith('Getting items (4 of 4)...');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should sleep after reach request', async () => {
|
|
114
|
-
await original.getItems(videosAPI, args);
|
|
115
|
-
|
|
116
|
-
expect(sleep.sleep).toBeCalledTimes(videos.length);
|
|
117
|
-
expect(sleep.sleep).toBeCalledWith(300);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return items data', async () => {
|
|
121
|
-
const items = await original.getItems(videosAPI, args);
|
|
122
|
-
|
|
123
|
-
expect(items).toEqual(videosList);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
describe('getEvents', () => {
|
|
128
|
-
it('should call getItems', async () => {
|
|
129
|
-
await original.getEvents(profile, args);
|
|
130
|
-
|
|
131
|
-
expect(data.getItems).toBeCalledWith(eventsAPI, args);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should return events', async () => {
|
|
135
|
-
const events = await original.getEvents(profile, args);
|
|
136
|
-
|
|
137
|
-
expect(events).toEqual(eventsList);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('getVideos', () => {
|
|
142
|
-
it('should call getItems', async () => {
|
|
143
|
-
await original.getVideos(profile, args);
|
|
144
|
-
|
|
145
|
-
expect(data.getItems).toBeCalledWith(videosAPI, args);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should return videos', async () => {
|
|
149
|
-
const videos = await original.getVideos(profile, args);
|
|
150
|
-
|
|
151
|
-
expect(videos).toEqual(videosList);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
package/src/lib/data.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { google } from 'googleapis';
|
|
2
|
-
import type GoogleApis from 'googleapis';
|
|
3
|
-
import { getAuth } from './auth';
|
|
4
|
-
import data from './data';
|
|
5
|
-
import { log } from './logger';
|
|
6
|
-
import { sleep } from './sleep';
|
|
7
|
-
|
|
8
|
-
export { getItems, getCalendarAPI, getYoutubeAPI, getEvents, getVideos, setEvent };
|
|
9
|
-
export default { getItems, getCalendarAPI, getYoutubeAPI, getEvents, getVideos, setEvent };
|
|
10
|
-
|
|
11
|
-
type CommonApi<TArgs, TResponse> = {
|
|
12
|
-
list: (
|
|
13
|
-
params: TArgs & {pageToken: string | undefined},
|
|
14
|
-
options?: GoogleApis.Common.MethodOptions | undefined
|
|
15
|
-
) => Promise<GoogleApis.Common.GaxiosResponse<TResponse>>
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type CommonResponse<TItem> = {
|
|
19
|
-
items?: TItem[],
|
|
20
|
-
pageInfo?: {
|
|
21
|
-
totalResults?: number | null | undefined
|
|
22
|
-
},
|
|
23
|
-
nextPageToken?: string | null | undefined
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const requestInterval = 300;
|
|
27
|
-
|
|
28
|
-
async function getItems<
|
|
29
|
-
TApi extends CommonApi<TArgs, TResponse>,
|
|
30
|
-
TItem,
|
|
31
|
-
TArgs,
|
|
32
|
-
TResponse extends CommonResponse<TItem>
|
|
33
|
-
>(api: TApi, args: TArgs): Promise<TItem[]> {
|
|
34
|
-
const items: TItem[] = [];
|
|
35
|
-
|
|
36
|
-
let pageToken: string | null | undefined = undefined;
|
|
37
|
-
|
|
38
|
-
do {
|
|
39
|
-
const response: GoogleApis.Common.GaxiosResponse<TResponse> = await api.list({ ...args, pageToken });
|
|
40
|
-
response.data.items?.forEach((item) => items.push(item));
|
|
41
|
-
log(`Getting items (${items.length} of ${response.data.pageInfo?.totalResults || 'many'})...`);
|
|
42
|
-
pageToken = response.data.nextPageToken;
|
|
43
|
-
|
|
44
|
-
await sleep(requestInterval);
|
|
45
|
-
} while (pageToken);
|
|
46
|
-
|
|
47
|
-
return items;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function getCalendarAPI(profile: string): Promise<GoogleApis.calendar_v3.Calendar> {
|
|
51
|
-
const googleAuth = await getAuth(profile);
|
|
52
|
-
|
|
53
|
-
return google.calendar({
|
|
54
|
-
version : 'v3',
|
|
55
|
-
auth : googleAuth,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function getYoutubeAPI(profile: string): Promise<GoogleApis.youtube_v3.Youtube> {
|
|
60
|
-
const googleAuth = await getAuth(profile);
|
|
61
|
-
|
|
62
|
-
return google.youtube({
|
|
63
|
-
version : 'v3',
|
|
64
|
-
auth : googleAuth,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function getEvents(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Events$List) {
|
|
69
|
-
const api = await data.getCalendarAPI(profile);
|
|
70
|
-
return getItems(api.events, args);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function getVideos(profile: string, args: GoogleApis.youtube_v3.Params$Resource$Playlistitems$List) {
|
|
74
|
-
const api = await data.getYoutubeAPI(profile);
|
|
75
|
-
return getItems(api.playlistItems, args);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function setEvent(profile: string, eventId: string | undefined, args: GoogleApis.calendar_v3.Params$Resource$Events$Update) {
|
|
79
|
-
const api = await data.getCalendarAPI(profile);
|
|
80
|
-
api.events.update({ eventId, ...args });
|
|
81
|
-
}
|