Skip to content

Commit

Permalink
feat: TS, audio
Browse files Browse the repository at this point in the history
  • Loading branch information
TheEVolk committed Jan 29, 2023
1 parent 2a2b31d commit 3e33a04
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 103 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
**/node_modules/**
package-lock.json
package-lock.json
lib
9 changes: 9 additions & 0 deletions examples/audio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import YandexAliceClient from '../lib/index.js';

const client = new YandexAliceClient();
await client.connect();

const response = await client.sendText('hello world', true);
console.log(response);

await client.close();
4 changes: 2 additions & 2 deletions examples/base.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import YandexAliceClient from '../src/index.js';
import YandexAliceClient from '../lib/index.js';

const client = new YandexAliceClient();
await client.connect();

const response = await client.sendText('hello world');
const { response } = await client.sendText('hello world');
console.log(response.card.text);

await client.close();
4 changes: 2 additions & 2 deletions examples/readline.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import YandexAliceClient from '../src/index.js';
import YandexAliceClient from '../lib/index.js';

const rl = readline.createInterface({ input, output });

const client = new YandexAliceClient();
await client.connect();

rl.on('line', async (line) => {
const response = await client.sendText(line.toString());
const { response } = await client.sendText(line.toString());
console.log('[A]', response.card.text);
});
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@
"dependencies": {
"uuid": "^9.0.0",
"ws": "^8.12.0"
},
"devDependencies": {
"@types/node": "^18.11.18",
"typescript": "^4.9.4"
},
"scripts": {
"build": "tsc --project tsconfig.json"
}
}
98 changes: 0 additions & 98 deletions src/index.js

This file was deleted.

140 changes: 140 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { v4 } from 'uuid';
import WebSocket from 'ws';
import type { IAliceActiveRequest, IAliceClientOptions, IAliceResponse, IAliceResponseDirective, IAliceSendTextResponse, IAliceStreamcontrol, IAliceStreamcontrolResponse } from './types';

export default class YandexAliceClient {
private ws: WebSocket;
private readonly requests = new Map<string, IAliceActiveRequest>();
private buffers = [];
private stream: string;

public constructor(private readonly options: IAliceClientOptions = {}) {
this.options.server = options.server || 'wss://uniproxy.alice.ya.ru/uni.ws';
}

public connect(): Promise<void> {
this.ws = new WebSocket(this.options.server);
this.ws.on('message', this.onMessage.bind(this));

return new Promise((resolve, reject) => {
this.ws.on('open', resolve);
this.ws.on('error', reject);
});
}

private onBuffer(data: Buffer) {
this.buffers.push(data.subarray(4));
console.log('got buffer', data.length);
}

private onMessage(data: Buffer) {
let response: { directive: IAliceResponseDirective } | IAliceStreamcontrolResponse;
try {
response = JSON.parse(data.toString());
} catch (_) {
this.onBuffer(data);
return;
}

const streamcontrol = response['streamcontrol'] as IAliceStreamcontrol;
const directive = response['directive'] as IAliceResponseDirective;

const messageId = directive?.header.refMessageId || this.stream;
const request = this.requests.get(messageId);
if (!request) {
console.warn('no request');
return;
}

if (directive) {
request.directives.push(directive);
request.needs.delete(directive.header.name);

if (directive.header.name === 'Speak') {
this.stream = directive.header.refMessageId;
}
}

if (streamcontrol) {
const buffer = Buffer.concat(this.buffers);
request.audio = buffer;
request.needs.delete('audio');
}

if (request.needs.size > 0) {
return;
}

this.requests.delete(messageId);
request.resolve({
directives: request.directives,
audio: request.audio
});
}

async sendText(text: string, isTTS = false): Promise<IAliceSendTextResponse> {
const response = await this.send({
header: {
namespace: 'Vins',
name: 'TextInput',
messageId: v4()
},
payload: {
request: {
voice_session: !!isTTS,
event: {
type: 'text_input',
text
}
},
application: this.getApplication(),
header: {
request_id: v4()
}
}
});

return {
response: response.directives[0].payload.response,
audio: response.audio
};
}

getApplication() {
return {
app_id: "aliced",
app_version: "1.2.3",
os_version: "5.0",
platform: "android",
uuid: v4(),
lang: "ru-RU",
client_time: new Date().toDateString(),
timezone: "Europe/Moscow",
timestamp: Math.floor(Date.now() / 1e3).toString(),
};
}

public send(event): Promise<IAliceResponse> {
return new Promise((resolve, reject) => {
const requestId = event.header.messageId;
const needs = new Set(['VinsResponse']);
if (event.payload.request.voice_session) {
needs.add('audio');
}

this.requests.set(requestId, {
at: new Date(),
needs: new Set(['VinsResponse']),
directives: [],
resolve,
reject
});

this.ws.send(JSON.stringify({ event }));
});
}

close() {
this.ws.close();
}
}
57 changes: 57 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export interface IAliceClientOptions {
/** @default 'wss://uniproxy.alice.ya.ru/uni.ws' */
server?: string;
}

export interface IAliceActiveRequest {
at: Date;
needs: Set<string>;
directives: IAliceResponseDirective[];
audio?: Buffer;
resolve: (data) => void;
reject: (reason) => void;
}

export interface IAliceRequestEvent {

}

// response

export interface IAliceResponsePayloadResponse {

}

export interface IAliceResponsePayload {
response: IAliceResponsePayloadResponse;
}

export interface IAliceResponseHeader {
name: 'Speak' | 'VinsResponse';
messageId: string;
refMessageId: string;
namespace: 'Vins' | 'TTS';
}

export interface IAliceResponseDirective {
header: IAliceResponseHeader;
payload: IAliceResponsePayload;
}

export interface IAliceResponse {
directives: IAliceResponseDirective[];
audio?: Buffer;
}

export interface IAliceStreamcontrol {
messageId: string;
}

export interface IAliceStreamcontrolResponse {
streamcontrol: IAliceStreamcontrol;
}

export interface IAliceSendTextResponse {
response: IAliceResponsePayloadResponse;
audio?: Buffer;
}
22 changes: 22 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./lib",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
}
}

0 comments on commit 3e33a04

Please sign in to comment.