Skip to content

Commit f037952

Browse files
tarun7singhPyvesB
andauthored
[Youtube] Added channel view count and subscriber count badges (#6333)
Co-authored-by: Pierre-Yves B <PyvesDev@gmail.com>
1 parent a67ea4b commit f037952

11 files changed

Lines changed: 175 additions & 50 deletions

services/youtube/youtube-base.js

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,26 @@ const documentation = `
1010
<code>https://www.youtube.com/t/terms</code></p>`
1111

1212
const schema = Joi.object({
13-
items: Joi.array()
14-
.items(
15-
Joi.object({
16-
statistics: Joi.object({
13+
pageInfo: Joi.object({
14+
totalResults: nonNegativeInteger,
15+
resultsPerPage: nonNegativeInteger,
16+
}).required(),
17+
items: Joi.array().items(
18+
Joi.object({
19+
statistics: Joi.alternatives(
20+
Joi.object({
1721
viewCount: nonNegativeInteger,
1822
likeCount: nonNegativeInteger,
1923
dislikeCount: nonNegativeInteger,
2024
commentCount: nonNegativeInteger,
21-
}).required(),
22-
})
23-
)
24-
.required(),
25+
}),
26+
Joi.object({
27+
viewCount: nonNegativeInteger,
28+
subscriberCount: nonNegativeInteger,
29+
})
30+
),
31+
})
32+
),
2533
}).required()
2634

2735
class YouTubeBase extends BaseJsonService {
@@ -39,38 +47,49 @@ class YouTubeBase extends BaseJsonService {
3947
namedLogo: 'youtube',
4048
}
4149

42-
static renderSingleStat({ statistics, statisticName, videoId }) {
50+
static renderSingleStat({ statistics, statisticName, id }) {
4351
return {
4452
label: `${statisticName}s`,
4553
message: metric(statistics[`${statisticName}Count`]),
4654
style: 'social',
47-
link: `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`,
55+
link: `https://www.youtube.com/${this.type}/${encodeURIComponent(id)}`,
4856
}
4957
}
5058

51-
async fetch({ videoId }) {
59+
async fetch({ id }) {
5260
return this._requestJson(
5361
this.authHelper.withQueryStringAuth(
5462
{ passKey: 'key' },
5563
{
5664
schema,
57-
url: 'https://www.googleapis.com/youtube/v3/videos',
65+
url: `https://www.googleapis.com/youtube/v3/${this.constructor.type}s`,
5866
options: {
59-
qs: { id: videoId, part: 'statistics' },
67+
qs: { id, part: 'statistics' },
6068
},
6169
}
6270
)
6371
)
6472
}
6573

66-
async handle({ videoId }, queryParams) {
67-
const json = await this.fetch({ videoId })
68-
if (json.items.length === 0) {
69-
throw new NotFound({ prettyMessage: 'video not found' })
74+
async handle({ channelId, videoId }, queryParams) {
75+
const id = channelId || videoId
76+
const json = await this.fetch({ id })
77+
if (json.pageInfo.totalResults === 0) {
78+
throw new NotFound({
79+
prettyMessage: `${this.constructor.type} not found`,
80+
})
7081
}
7182
const statistics = json.items[0].statistics
72-
return this.constructor.render({ statistics, videoId }, queryParams)
83+
return this.constructor.render({ statistics, id }, queryParams)
7384
}
7485
}
7586

76-
module.exports = { documentation, YouTubeBase }
87+
class YouTubeVideoBase extends YouTubeBase {
88+
static type = 'video'
89+
}
90+
91+
class YouTubeChannelBase extends YouTubeBase {
92+
static type = 'channel'
93+
}
94+
95+
module.exports = { documentation, YouTubeVideoBase, YouTubeChannelBase }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict'
2+
3+
const { documentation, YouTubeChannelBase } = require('./youtube-base')
4+
5+
module.exports = class YouTubeChannelViews extends YouTubeChannelBase {
6+
static route = {
7+
base: 'youtube/channel/views',
8+
pattern: ':channelId',
9+
}
10+
11+
static get examples() {
12+
const preview = this.render({
13+
statistics: { viewCount: 30543 },
14+
id: 'UC8butISFwT-Wl7EV0hUK0BQ',
15+
})
16+
// link[] is not allowed in examples
17+
delete preview.link
18+
return [
19+
{
20+
title: 'YouTube Channel Views',
21+
namedParams: { channelId: 'UC8butISFwT-Wl7EV0hUK0BQ' },
22+
staticPreview: preview,
23+
documentation,
24+
},
25+
]
26+
}
27+
28+
static render({ statistics, id }) {
29+
return super.renderSingleStat({ statistics, statisticName: 'view', id })
30+
}
31+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
const t = (module.exports = require('../tester').createServiceTester())
4+
const { noToken } = require('../test-helpers')
5+
const { isMetric } = require('../test-validators')
6+
const noYouTubeToken = noToken(require('./youtube-channel-views.service'))
7+
8+
t.create('channel view count')
9+
.skipWhen(noYouTubeToken)
10+
.get('/UC8butISFwT-Wl7EV0hUK0BQ.json')
11+
.expectBadge({
12+
label: 'views',
13+
message: isMetric,
14+
color: 'red',
15+
link: ['https://www.youtube.com/channel/UC8butISFwT-Wl7EV0hUK0BQ'],
16+
})
17+
18+
t.create('channel not found')
19+
.skipWhen(noYouTubeToken)
20+
.get('/doesnotexist.json')
21+
.expectBadge({
22+
label: 'youtube',
23+
message: 'channel not found',
24+
color: 'red',
25+
})
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict'
22

3-
const { documentation, YouTubeBase } = require('./youtube-base')
3+
const { documentation, YouTubeVideoBase } = require('./youtube-base')
44

5-
module.exports = class YouTubeComments extends YouTubeBase {
5+
module.exports = class YouTubeComments extends YouTubeVideoBase {
66
static route = {
77
base: 'youtube/comments',
88
pattern: ':videoId',
@@ -11,7 +11,7 @@ module.exports = class YouTubeComments extends YouTubeBase {
1111
static get examples() {
1212
const preview = this.render({
1313
statistics: { commentCount: 209 },
14-
videoId: 'wGJHwc5ksMA',
14+
id: 'wGJHwc5ksMA',
1515
})
1616
// link[] is not allowed in examples
1717
delete preview.link
@@ -25,11 +25,7 @@ module.exports = class YouTubeComments extends YouTubeBase {
2525
]
2626
}
2727

28-
static render({ statistics, videoId }) {
29-
return super.renderSingleStat({
30-
statistics,
31-
statisticName: 'comment',
32-
videoId,
33-
})
28+
static render({ statistics, id }) {
29+
return super.renderSingleStat({ statistics, statisticName: 'comment', id })
3430
}
3531
}

services/youtube/youtube-comments.tester.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ t.create('video comment count')
1212
label: 'comments',
1313
message: isMetric,
1414
color: 'red',
15-
link: ['https://www.youtube.com/watch?v=wGJHwc5ksMA'],
15+
link: ['https://www.youtube.com/video/wGJHwc5ksMA'],
1616
})
1717

1818
t.create('video not found')

services/youtube/youtube-likes.service.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const Joi = require('joi')
44
const { metric } = require('../text-formatters')
5-
const { documentation, YouTubeBase } = require('./youtube-base')
5+
const { documentation, YouTubeVideoBase } = require('./youtube-base')
66

77
const documentationWithDislikes = `
88
${documentation}
@@ -16,7 +16,7 @@ const queryParamSchema = Joi.object({
1616
withDislikes: Joi.equal(''),
1717
}).required()
1818

19-
module.exports = class YouTubeLikes extends YouTubeBase {
19+
module.exports = class YouTubeLikes extends YouTubeVideoBase {
2020
static route = {
2121
base: 'youtube/likes',
2222
pattern: ':videoId',
@@ -26,16 +26,14 @@ module.exports = class YouTubeLikes extends YouTubeBase {
2626
static get examples() {
2727
const previewLikes = this.render({
2828
statistics: { likeCount: 7 },
29-
videoId: 'abBdk8bSPKU',
29+
id: 'abBdk8bSPKU',
3030
})
3131
const previewVotes = this.render(
3232
{
3333
statistics: { likeCount: 10236, dislikeCount: 396 },
34-
videoId: 'pU9Q6oiQNd0',
34+
id: 'pU9Q6oiQNd0',
3535
},
36-
{
37-
withDislikes: '',
38-
}
36+
{ withDislikes: '' }
3937
)
4038
// link[] is not allowed in examples
4139
delete previewLikes.link
@@ -59,11 +57,11 @@ module.exports = class YouTubeLikes extends YouTubeBase {
5957
]
6058
}
6159

62-
static render({ statistics, videoId }, queryParams) {
60+
static render({ statistics, id }, queryParams) {
6361
let renderedBadge = super.renderSingleStat({
6462
statistics,
6563
statisticName: 'like',
66-
videoId,
64+
id,
6765
})
6866
if (queryParams && typeof queryParams.withDislikes !== 'undefined') {
6967
renderedBadge = {

services/youtube/youtube-likes.tester.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ t.create('video like count')
1313
label: 'likes',
1414
message: isMetric,
1515
color: 'red',
16-
link: ['https://www.youtube.com/watch?v=pU9Q6oiQNd0'],
16+
link: ['https://www.youtube.com/video/pU9Q6oiQNd0'],
1717
})
1818

1919
t.create('video vote count')
@@ -25,7 +25,7 @@ t.create('video vote count')
2525
/^([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) 👍 ([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) 👎$/
2626
),
2727
color: 'red',
28-
link: ['https://www.youtube.com/watch?v=pU9Q6oiQNd0'],
28+
link: ['https://www.youtube.com/video/pU9Q6oiQNd0'],
2929
})
3030

3131
t.create('video not found')
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict'
2+
3+
const { documentation, YouTubeChannelBase } = require('./youtube-base')
4+
5+
module.exports = class YouTubeSubscribes extends YouTubeChannelBase {
6+
static route = {
7+
base: 'youtube/channel/subscribers',
8+
pattern: ':channelId',
9+
}
10+
11+
static get examples() {
12+
const preview = this.render({
13+
statistics: { subscriberCount: 14577 },
14+
id: 'UC8butISFwT-Wl7EV0hUK0BQ',
15+
})
16+
// link[] is not allowed in examples
17+
delete preview.link
18+
return [
19+
{
20+
title: 'YouTube Channel Subscribers',
21+
namedParams: { channelId: 'UC8butISFwT-Wl7EV0hUK0BQ' },
22+
staticPreview: preview,
23+
documentation,
24+
},
25+
]
26+
}
27+
28+
static render({ statistics, id }) {
29+
return super.renderSingleStat({
30+
statistics,
31+
statisticName: 'subscriber',
32+
id,
33+
})
34+
}
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
const t = (module.exports = require('../tester').createServiceTester())
4+
const { noToken } = require('../test-helpers')
5+
const { isMetric } = require('../test-validators')
6+
const noYouTubeToken = noToken(require('./youtube-subscribers.service'))
7+
8+
t.create('subscriber count')
9+
.skipWhen(noYouTubeToken)
10+
.get('/UC8butISFwT-Wl7EV0hUK0BQ.json')
11+
.expectBadge({
12+
label: 'subscribers',
13+
message: isMetric,
14+
color: 'red',
15+
link: ['https://www.youtube.com/channel/UC8butISFwT-Wl7EV0hUK0BQ'],
16+
})
17+
18+
t.create('channel not found')
19+
.skipWhen(noYouTubeToken)
20+
.get('/doesnotexist.json')
21+
.expectBadge({
22+
label: 'youtube',
23+
message: 'channel not found',
24+
color: 'red',
25+
})
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict'
22

3-
const { documentation, YouTubeBase } = require('./youtube-base')
3+
const { documentation, YouTubeVideoBase } = require('./youtube-base')
44

5-
module.exports = class YouTubeViews extends YouTubeBase {
5+
module.exports = class YouTubeViews extends YouTubeVideoBase {
66
static route = {
77
base: 'youtube/views',
88
pattern: ':videoId',
@@ -11,7 +11,7 @@ module.exports = class YouTubeViews extends YouTubeBase {
1111
static get examples() {
1212
const preview = this.render({
1313
statistics: { viewCount: 14577 },
14-
videoId: 'abBdk8bSPKU',
14+
id: 'abBdk8bSPKU',
1515
})
1616
// link[] is not allowed in examples
1717
delete preview.link
@@ -25,11 +25,7 @@ module.exports = class YouTubeViews extends YouTubeBase {
2525
]
2626
}
2727

28-
static render({ statistics, videoId }) {
29-
return super.renderSingleStat({
30-
statistics,
31-
statisticName: 'view',
32-
videoId,
33-
})
28+
static render({ statistics, id }) {
29+
return super.renderSingleStat({ statistics, statisticName: 'view', id })
3430
}
3531
}

0 commit comments

Comments
 (0)