Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump acorn from 7.1.0 to 7.1.1 #4

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions .eslintrc.js

This file was deleted.

21 changes: 21 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"env": { "es6": true, "node": true },
"extends": "eslint:recommended",
"parserOptions": { "sourceType": "module", "ecmaVersion": 2018 },
"rules": {
"indent": [2, 2, { "SwitchCase": 1 }],
"linebreak-style": ["error", "windows"],
"quotes": ["error", "single"],
"semi": ["error", "never"],
"no-console": "off",
"comma-dangle": ["error", "always-multiline"],
"eqeqeq": "error",
"no-var": "error",
"prefer-const": "error",
"comma-style": ["error", "last"],
"no-multiple-empty-lines": [1, { "max": 1 }],
"no-spaced-func": "error",
"keyword-spacing": "error",
"space-before-blocks": "error"
}
}
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
language: node_js
node_js:
- 6.11.1
- 8
- 10
- 'lts/*'
- 'stable'
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,37 @@

[![Build Status](https://travis-ci.org/yuta0801/youtube-live-chat.svg?branch=master)](https://travis-ci.org/yuta0801/youtube-live-chat)

A library for get YouTube live chats
A library for get YouTube live chat messages

## Demo

```js
const YouTube = require('youtube-live-chat');
const LiveChat = require('youtube-live-chat')

const yt = new YouTube('CHANNEL_ID_IS_HERE', 'APIKEY_IS_HERE');
const chat = new LiveChat('CHANNEL_ID_IS_HERE', 'APIKEY_IS_HERE')

yt.on('ready', () => {
console.log('ready!')
yt.listen(1000)
})
chat.listen()

yt.on('message', data => {
chat.on('message', data => {
console.log(data.snippet.displayMessage)
})

yt.on('error', error => {
// API request error
chat.on('error', error => {
console.error(error)
})
```

## Requirement

- events ^1.1.1
- request ^2.81.0
// some useful messages for debugging
chat.on('warn', warn => {
console.warn(warn)
})
```

## Install

```
$ npm install --save youtube-live-chat
$ npm install youtube-live-chat # using npm
$ yarn add youtube-live-chat # using yarn
```

## License
Expand Down
211 changes: 137 additions & 74 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const request = require('request')
const {EventEmitter} = require('events')
const qs = require('querystring')
const fetch = require('node-fetch')
const { EventEmitter } = require('events')

/**
* The main hub for acquire live chat with the YouTube Date API.
* @extends {EventEmitter}
*/
class YouTube extends EventEmitter {
class LiveChat extends EventEmitter {
/**
* @param {string} ChannelID ID of the channel to acquire with
* @param {string} APIKey You'r API key
Expand All @@ -14,105 +14,168 @@ class YouTube extends EventEmitter {
super()
this.id = channelId
this.key = apiKey
this.getLive()
}

getLive() {
const url = 'https://www.googleapis.com/youtube/v3/search'+
'?eventType=live'+
'&part=id'+
`&channelId=${this.id}`+
'&type=video'+
`&key=${this.key}`
this.request(url, data => {
if (!data.items[0])
this.emit('error', 'Can not find live.')
else {
this.liveId = data.items[0].id.videoId
this.getChatId()
}
/**
* @returns {Promise<string[]>}
* @private
*/
async getLiveIds() {
const data = await this.fetch('search', {
eventType: 'live',
part: 'id',
channelId: this.id,
type: 'video',
key: this.key,
})
if (!data)
this.emit('warn', `Failed fetch live stream for channel ${this.id}`)
else if (!data.items.length)
this.emit('warn', `No live stream found for channel ${this.id}`)
return data ? data.items.map(item => item.id.videoId) : []
}

getChatId() {
if (!this.liveId) return this.emit('error', 'Live id is invalid.')
const url = 'https://www.googleapis.com/youtube/v3/videos'+
'?part=liveStreamingDetails'+
`&id=${this.liveId}`+
`&key=${this.key}`
this.request(url, data => {
/**
* @param {string[]} liveIds
* @returns {Promise<string[]>}
* @private
*/
async getChatIds(liveIds) {
const chatIds = []
for (const liveId of liveIds) {
const data = await this.fetch('videos', {
part: 'liveStreamingDetails',
id: liveId,
key: this.key,
})
if (!data)
this.emit('warn', `Failed fetch live stream for stream ${liveId}`)
if (!data.items.length)
this.emit('error', 'Can not find chat.')
else {
this.chatId = data.items[0].liveStreamingDetails.activeLiveChatId
this.emit('ready')
}
})
this.emit('warn', `No live chat found for stream ${liveId}`)
else chatIds.push(data.items[0].liveStreamingDetails.activeLiveChatId)
}
if (!chatIds.length) this.emit('warn', 'No live chat found')
return chatIds
}

/**
* Gets live chat messages.
* See {@link https://developers.google.com/youtube/v3/live/docs/liveChatMessages/list#response|docs}
* @return {object}
* @param {string[]} chatIds
* @private
*/
getChat() {
if (!this.chatId) return this.emit('error', 'Chat id is invalid.')
const url = 'https://www.googleapis.com/youtube/v3/liveChat/messages'+
`?liveChatId=${this.chatId}`+
'&part=id,snippet,authorDetails'+
'&maxResults=2000'+
`&key=${this.key}`
this.request(url, data => {
this.emit('json', data)
})
async getMessages(chatIds) {
for (const chatId of chatIds) {
const messages = await this.fetch('liveChat/messages', {
liveChatId: chatId,
part: 'id,snippet,authorDetails',
maxResults: '2000',
key: this.key,
})
if (!messages)
this.emit('warn', `Failed fetch live chat messages for ${chatId}`)
else this.emit('json', messages)
}
}

request(url, callback) {
request({
url: url,
method: 'GET',
json: true,
}, (error, response, data) => {
if (error)
this.emit('error', error)
else if (response.statusCode !== 200)
this.emit('error', data)
else
callback(data)
})
/**
* @param {string} endpoint
* @param {Object.<string, string>} [params]
* @private
*/
async fetch(endpoint, params) {
try {
const url = 'https://www.googleapis.com/youtube/v3/' + endpoint
const query = params ? '?' + qs.stringify(params) : ''
const res = await fetch(url + query)
const data = await res.json()
if (!res.ok) throw data
return data
} catch (error) {
this.emit('error', error)
return null
}
}

/**
* Gets live chat messages at regular intervals.
* @param {number} delay Interval to get live chat messages
* @fires YouTube#message
* @private
*/
listen(delay) {
let lastRead = 0, time = 0
this.interval = setInterval(() => this.getChat(), delay)
handler() {
this.handled = true
this.on('json', data => {
for (const item of data.items) {
time = new Date(item.snippet.publishedAt).getTime()
if (lastRead < time) {
lastRead = time
const time = new Date(item.snippet.publishedAt).getTime()
if (this.lastRead < time) {
this.lastRead = time
/**
* Emitted whenever a new message is recepted.
* See {@link https://developers.google.com/youtube/v3/live/docs/liveChatMessages#resource|docs}
* @event YouTube#message
* @type {object}
*/
* Emitted whenever a new message is recepted.
* See {@link https://developers.google.com/youtube/v3/live/docs/liveChatMessages#resource}
* @event LiveChat#message
* @type {object}
*/
this.emit('message', item)
}
}
})
}

/**
* Stops getting live chat messages at regular intervals.
* Listening options
* @typedef {Object} ListenOptions
* @property {number} [interval] Interval to get live chat messages. Default is 1000ms.
* @property {function} [liveFilter] Filter to select live stream. Default is _all lives_.
*/

/**
* Gets live chat messages at regular intervals.
* @param {ListenOptions} [options] Listening options
* @fires LiveChat#message
*/
async listen(options) {
options = Object.assign(
{
interval: 1000,
liveFilter: c => c,
ignorePastMessages: true,
},
options,
)

if (options.ignorePastMessages) this.lastRead = Date.now()

if (!this.handled) this.handler()
if (this.interval) this.stop()

const liveIds = await this.getLiveIds()
const chatIds = await this.getChatIds(liveIds)

// hold data for restart
this.options = options
this.chatIds = chatIds

this.interval = setInterval(
() => this.getMessages(options.liveFilter(chatIds)),
options.interval,
)
}

/**
* Stops getting live chat messages
*/
stop() {
clearInterval(this.interval)
}

/**
* Restarts getting live chat messages
* @param {ListenOptions} [options] Listening options. Default is options last passed to listen method_.
*/
restart(options = this.options) {
if (this.options.ignorePastMessages) this.lastRead = Date.now()

this.interval = setInterval(
() => this.getMessages(options.liveFilter(this.chatIds)),
options.interval,
)
}
}

module.exports = YouTube
module.exports = LiveChat
Loading