Last active
January 7, 2016 03:38
-
-
Save chrahunt/6ad88b678838fd1218f3 to your computer and use it in GitHub Desktop.
Add speech recognition to TagPro.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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