This repository has been archived by the owner on Mar 28, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathactions.js
170 lines (148 loc) · 5.37 KB
/
actions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
const {dialogflow, Suggestions, LinkOutSuggestion, MediaObject, Image} = require('actions-on-google');
const ytSearch = require('@nodef/yt-search');
const natural = require('natural');
const ytdl = require('ytdl-core');
const urlParse = require('url').parse;
const https = require('https');
const query = require('./query');
const message = require('./message');
// Global variables
const app = dialogflow();
const store = new Map();
// Check if URL is accessible.
function httpsAccess(url) {
var opt = Object.assign(urlParse(url), {method: 'HEAD'});
return new Promise((fres, frej) => https.request(opt, res => {
res.resume(); fres(res.statusCode>=200 && res.statusCode<400);
}).on('error', frej).end());
};
// Get video URL from id.
function videoUrl(id) {
return `https://www.youtube.com/watch?v=${id}`;
};
// Check if audio URL ok.
function audioOk(url) {
return !url.includes('gcr=us');
};
// Get M4A URL from info.
function audioUrl(inf) {
for(var f of inf.formats)
if(/audio.*?mp4/.test(f.type) && audioOk(f.url)) return f.url;
for(var f of inf.formats)
if(/audio/.test(f.type) && audioOk(f.url)) return f.url;
var url = inf.formats[0].url;
return audioOk(url)? url:null;
};
// Get search videos data.
function audioSearch(vids, o) {
var z = [], match = 0;
for(var v of vids) {
if(!v.url.startsWith('/watch')) continue;
var channel = v.userName||v.channelName;
if(o.channel) match = natural.LevenshteinDistance(o.channel, channel, {search: true}).distance;
z.push({id: v.videoId, title: v.title, channel, thumbnail: null, audio: null, match});
}
return o.channel? z.sort((a, b) => a.match-b.match):z;
};
// Get related videos data.
function audioRelated(vids) {
var z = [], match = 0;
for(var v of vids)
z.push({id: v.id, title: v.title, channel: v.author, thumbnail: v.iurlhq||v.iurlmq, audio: null, match});
return z;
};
// Get current video data.
function audioCurrent(inf, url) {
return {id: inf.video_id, title: inf.title, channel: inf.author.name, thumbnail: inf.thumbnail_url, audio: url, match: 0};
};
// Search youtube, return promise.
function youtubeSearch(qry) {
console.log('-youtubeSearch:', qry);
return new Promise((fres, frej) => ytSearch(qry, (err, ans) => {
return err? frej(err):fres(ans);
}));
};
// Get YouTube audio for current.
async function youtubeAudio(vids) {
var inf = null, url = null;
console.log('-youtubeAudio:', vids.length);
for(var i=0, I=vids.length; i<I; i++) {
inf = await ytdl.getInfo(videoUrl(vids[i].id), {format: 'm4a'});
url = audioUrl(inf);
if(url) break;
}
vids.splice(0, i+1);
return {inf, url};
};
// Check if audio surface.
function isAudioSurface(conv) {
if(conv.surface.capabilities.has('actions.capability.MEDIA_RESPONSE_AUDIO')) return true;
conv.ask('Sorry, this device does not support audio playback.');
console.log('-isAudioSurface:', false);
return false;
};
// Play audio from current.
function playAudio(conv, o) {
console.log('-playAudio:', o);
conv.ask(`Playing "${o.title}" by "${o.channel}"`);
conv.ask(new Suggestions(['Again', 'Next', 'Related']));
conv.ask(new LinkOutSuggestion({name: 'Video', url: videoUrl(o.id)}));
return conv.ask(new MediaObject({
name: o.title, url: o.audio, description: o.title,
icon: new Image({url: o.thumbnail, alt: o.title}),
}));
};
// Play audio from list.
async function playList(conv) {
var s = store.get(conv.id);
console.log('-playList:', s.list.length, s.related.length);
var {inf, url} = await youtubeAudio(s.list);
if(url==null) { var {inf, url} = await youtubeAudio(s.list=s.related); }
if(url==null) return conv.ask('Could\'nt find anything to play!');
s.current = audioCurrent(inf, url);
s.related = audioRelated(inf.related_videos);
return playAudio(conv, s.current);
};
// Intent "Default Fallback Intent"
async function defaultFallback(conv) {
console.log('@defaultFallback:', conv.query, conv.id);
return conv.ask(conv.body.queryResult.fulfillmentText+' Anything else?');
};
// Intent "Default Welcome Intent"
async function defaultWelcome(conv) {
console.log('@defaultWelcome:', conv.query, conv.id);
return conv.ask(conv.body.queryResult.fulfillmentText);
};
// Intent "Play Option"
async function playOption(conv, o) {
var opt = o.a_option, s = store.get(conv.id);
var mediaStatus = conv.arguments.get('MEDIA_STATUS');
console.log('@playOption:', opt, mediaStatus, conv.id);
if(s==null) return conv.ask('You have not played anything yet.');
if(opt==='again') return playAudio(conv, s.current);
if(opt==='related') s.list = s.related;
return playList(conv);
};
// Intent "Play"
async function play(conv, o) {
var {query, channel} = o;
if(!isAudioSurface(conv)) return;
console.log('@play:', `"${query}" by ${channel}`, conv.id);
var list = audioSearch((await youtubeSearch(query)).videos, {channel});
if(!list.length) return conv.close(`I could'nt find anything for "${query}".`);;
store.set(conv.id, {list, current: null, related: []});
return playList(conv);
};
// Intent "Stop"
async function stop(conv) {
console.log('@stop:', conv.id);
store.delete(conv.id);
conv.close(conv.body.queryResult.fulfillmentText);
};
app.intent('Default Fallback Intent', defaultFallback);
app.intent('Default Welcome Intent', defaultWelcome);
app.intent('Play Option', playOption);
app.intent('Play Video', play);
app.intent('Play', play);
app.intent('Stop', stop);
module.exports = app;