Skip to content

Instantly share code, notes, and snippets.

@chrahunt
Last active January 7, 2016 03:38
Show Gist options
  • Save chrahunt/6ad88b678838fd1218f3 to your computer and use it in GitHub Desktop.
Save chrahunt/6ad88b678838fd1218f3 to your computer and use it in GitHub Desktop.
Add speech recognition to TagPro.
<!DOCTYPE html>
<html>
<head></head>
<body>
<script>
console.log("Listener: Loading page.");
var TAGPRO_URLS = [
"http://tagpro-\\w*\\.koalabeast\\.com:\\d+",
"http://tagpro.gg:\\d+",
"http://maptest\\d?.newcompte.fr:\\d+"
];
// Takes regexes for URLs.
function InnerFrame(urls) {
var initialized = false;
this.listeners = {};
var self = this;
window.addEventListener("message", function (event) {
if (!initialized) {
// TODO: Check for URLs.
var message = event.data;
if (message.name === "init") {
console.log("Listener: Initializing 2-way.");
self.target = event.source;
self.origin = event.origin;
initialized = true;
}
} else if (event.source === self.target) {
var message = event.data;
var name = message.name;
console.log("Listener: Message received '" + name + "'");
if (self.listeners.hasOwnProperty(name)) {
console.log("Calling listeners.");
self.listeners[name].forEach(function (fn) {
fn(message.data);
});
} else {
console.log("Listener: No listeners set.");
}
} else {
console.warn("Listener: Sender not recognized for message.");
}
}, false);
}
InnerFrame.prototype.send = function(name, data) {
var arg = {
name: name
};
if (typeof data !== "undefined") {
arg.data = data;
}
this.target.postMessage(arg, this.origin);
};
InnerFrame.prototype.on = function(name, fn) {
console.log("Listener: Adding function for '" + name + "'");
if (!this.listeners.hasOwnProperty(name)) {
this.listeners[name] = [];
}
this.listeners[name].push(fn);
};
InnerFrame.prototype.removeListener = function(name, fn) {
if (this.listeners.hasOwnProperty(name)) {
var i = this.listeners[name].indexOf(fn);
if (i !== -1) {
this.listeners[name].splice(i, 1);
}
}
};
var self = new InnerFrame(TAGPRO_URLS);
// Multiline Function String - Nate Ferrero - Public Domain
// http://stackoverflow.com/a/14496573/1698058
function heredoc (f) {
return f.toString().match(/\/\*\s*([\s\S]*?)\s*\*\//m)[1];
}
var grammar = heredoc(function(){/*
#JSGF V1.0;
grammar terms;
public <powerup> = tagpro | juke juice | rolling bomb;
*/});
// Initialize web speech.
var recognition, status, error, start_timestamp;
if (!window.webkitSpeechRecognition) {
status = "error";
error = "no_api";
} else {
status = "idle";
var ignore_onend = false;
recognition = new webkitSpeechRecognition();
var speechRecognitionList = new webkitSpeechGrammarList();
speechRecognitionList.addFromString(grammar, 1);
recognition.grammars = speechRecognitionList;
recognition.continuous = true;
recognition.interimResults = true;
recognition.onstart = function() {
status = "listening";
self.send("sr.start");
};
recognition.onerror = function(event) {
if (event.error == 'no-speech') {
self.send("sr.error", "no_speech");
status = "sr.error";
ignore_onend = true;
}
if (event.error == 'audio-capture') {
self.send("sr.error", "no_mic");
status = "sr.error";
ignore_onend = true;
}
if (event.error == 'not-allowed') {
if (event.timeStamp - start_timestamp < 100) {
self.send("sr.error", "mic_blocked");
} else {
self.send("sr.error", "mic_denied");
}
status = "sr.error";
ignore_onend = true;
}
};
recognition.onend = function() {
if (ignore_onend) {
return;
}
status = "idle";
self.send("sr.end");
};
var final_transcript = '';
recognition.onresult = function(event) {
var interim_transcript = '';
for (var i = event.resultIndex; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
final_transcript += event.results[i][0].transcript;
} else {
interim_transcript += event.results[i][0].transcript;
}
}
console.log("Listener: Final: " + final_transcript);
console.log("Listener: Interim: " + interim_transcript);
self.send("sr.result", {
final_transcript: final_transcript,
interim_transcript: interim_transcript
});
};
}
// Communication initialization.
self.on("sr.start", function () {
console.log("Listener: Starting recognition.");
start_timestamp = Date.now();
final_transcript = "";
recognition.start();
});
self.on("sr.stop", function () {
console.log("Listener: Stopping recognition.");
recognition.stop();
});
self.on("sr.status", function () {
console.log("Listener: Status.");
});
self.on("sr.abort", function () {
console.log("Listener: Aborting recognition.");
recognition.abort();
});
</script>
</body>
</html>
// ==UserScript==
// @name TagPro Talk
// @namespace http://reddit.com/user/snaps_
// @description Talk in TagPro
// @require https://gist.github.com/chrahunt/4843f0258c516882eea0/raw/loopback.user.js
// @require https://craig.global.ssl.fastly.net/js/mousetrap/mousetrap.min.js
// @downloadURL https://gist.github.com/chrahunt/todo
// @include http://tagpro-*.koalabeast.com:*
// @include http://maptest*.newcompte.fr:*
// @include http://tangent.jukejuice.com:*
// @license MIT
// @author snaps
// @version 0.1.0
// @grant GM_getResourceURL
// @run-at document-start
// ==/UserScript==
var HTTPS_LINK = "https://rawgit.com/chrahunt/6ad88b678838fd1218f3/raw/9a2a66b109859bc601998ef4003cd39f6433b07c/listen.html";
var ORIGIN_REGEX = /^(.*?:\/\/.*?)\//;
// 2-way frame communication, implements protocol.
function Frame(url) {
var iframe = document.createElement("iframe");
iframe.src = url;
iframe.style.display = "none";
this.origin = url.match(ORIGIN_REGEX)[1];
this.listeners = {};
var self = this;
// Introduce self.
iframe.addEventListener("load", function () {
self.target = iframe.contentWindow;
self.send("init");
}, false);
window.addEventListener("message", function (event) {
var origin = event.origin || event.originalEvent.origin;
if (origin !== self.origin || event.source !== self.target)
return;
var message = event.data;
var name = message.name;
console.log("Content: Message received: '" + name + "'");
if (self.listeners.hasOwnProperty(name)) {
console.log("Content: Calling listeners.");
self.listeners[name].forEach(function (fn) {
fn(message.data);
});
} else {
console.log("Content: No listeners set");
}
});
document.body.appendChild(iframe);
}
Frame.prototype.send = function(name, data) {
var arg = {
name: name
};
if (typeof data !== "undefined") {
arg.data = data;
}
this.target.postMessage(arg, this.origin);
};
Frame.prototype.on = function(name, fn) {
if (!this.listeners.hasOwnProperty(name)) {
this.listeners[name] = [];
}
this.listeners[name].push(fn);
};
Frame.prototype.removeListener = function(name, fn) {
if (this.listeners.hasOwnProperty(name)) {
var i = this.listeners[name].indexOf(fn);
if (i !== -1) {
this.listeners[name].splice(i, 1);
}
}
};
function Messenger(socket) {
this.socket = socket;
}
Messenger.prototype.send = function(msg) {
this.socket.emit("chat", {
message: msg.substring(0, 70),
toAll: true
});
};
// Listen for things to happen with these keys
function KeyState(start, reset, send) {
this.listening = false;
}
KeyState.prototype.onstart = function(fn) {
// body...
};
function showInfo(msg) {
tagpro.socket.emit("local:chat", {
to: "all",
from: null,
message: msg
});
}
// When key is pressed, start listening. As words come in, display in chat element look-alike.
// When done, allow pressing enter to send.
// or escape to stop
// override tagpro hotkey functionality when active
// stop listening only when disabled
// if text at end of input, flash red.
setTimeout(function setup() {
// Wait for TagPro to get chat socket
if (typeof tagpro == "undefined" || !tagpro.socket) {
setTimeout(setup, 500);
return;
}
var messenger = new Messenger(tagpro.socket);
var frame = new Frame(HTTPS_LINK);
var recognizing = false;
var output = "";
Mousetrap.bind("ctrl+z", function (e) {
if (recognizing) {
console.log("Content: Stopping recognition.");
frame.send("sr.stop");
if (output !== "") {
console.log("Sending message: " + output);
messenger.send(output);
output = "";
}
recognizing = false;
} else {
console.log("Content: Starting recognition.");
frame.send("sr.start");
recognizing = true;
}
return false;
});
var input = $("<div>");
var first = $("<span>");
first.css({color: "white"});
var second = $("<span>");
second.css({color: "gray"});
input.append(first);
input.append(second);
$("body").append(input);
frame.on("sr.result", function (result) {
first.text(result.final_transcript);
output = result.final_transcript;
second.text(result.interim_transcript);
});
frame.on("sr.error", function (result) {
console.log("SR error: %o", result);
});
// initialize key listener
// on keypress
}, 50);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment