forked from cory/tildefriends
Work in progress modular, resumable, multi-protocol chat client.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3228 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
379
packages/cory/chat/chat.js
Normal file
379
packages/cory/chat/chat.js
Normal file
@ -0,0 +1,379 @@
|
||||
"use strict";
|
||||
|
||||
var gFocus = true;
|
||||
var gUnread = 0;
|
||||
var gPresence = {};
|
||||
let gSessions = {};
|
||||
let gCurrentConversation;
|
||||
|
||||
function updateTitle() {
|
||||
terminal.setTitle((gUnread ? "(" + gUnread.toString() + ") " : "") + "Chat");
|
||||
}
|
||||
|
||||
let kAccountsKey = JSON.stringify(["accounts", core.user.name]);
|
||||
|
||||
function runCommand(data) {
|
||||
if (data.action == "addAccount") {
|
||||
addAccount();
|
||||
} else if (data.action == "deleteAccount") {
|
||||
deleteAccount(data.id);
|
||||
} else if (data.action == "updateAccount") {
|
||||
delete data.action;
|
||||
let id = data.id;
|
||||
delete data.id;
|
||||
configureAccount(id, data);
|
||||
} else if (data.action == "connect") {
|
||||
connect(data.id);
|
||||
} else if (data.action == "disconnect") {
|
||||
disconnect(data.id);
|
||||
} else if (data.action == "window") {
|
||||
gCurrentConversation = gSessions[data.account].conversations[data.conversation];
|
||||
updateConversation();
|
||||
updateWindows();
|
||||
}
|
||||
}
|
||||
|
||||
function addAccount() {
|
||||
return database.get(kAccountsKey).then(function(data) {
|
||||
let accounts = data ? JSON.parse(data) : [];
|
||||
let id = 0;
|
||||
for (var i in accounts) {
|
||||
id = Math.max(id, accounts[i].id + 1);
|
||||
}
|
||||
accounts.push({name: "unnamed", id: id});
|
||||
return database.set(kAccountsKey, JSON.stringify(accounts));
|
||||
}).then(updateWindows);
|
||||
}
|
||||
|
||||
core.register("submit", function(data) {
|
||||
if (data.value.submit == "Save Account") {
|
||||
let id = data.value.id;
|
||||
delete data.value.id;
|
||||
delete data.value.submit;
|
||||
configureAccount(id, data.value);
|
||||
}
|
||||
});
|
||||
|
||||
function configureAccount(id, updates) {
|
||||
return Promise.all([database.get(kAccountsKey), core.getPackages()]).then(function(results) {
|
||||
let accounts = results[0] ? JSON.parse(results[0]) : [];
|
||||
let packages = results[1];
|
||||
let account;
|
||||
let accountIndex;
|
||||
for (let i in accounts) {
|
||||
if (accounts[i].id == id) {
|
||||
account = accounts[i];
|
||||
accountIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
|
||||
if (updates) {
|
||||
for (let i in updates) {
|
||||
account[i] = updates[i];
|
||||
}
|
||||
promises.push(database.set(kAccountsKey, JSON.stringify(accounts)));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(function() {
|
||||
terminal.clear();
|
||||
terminal.print(JSON.stringify(account));
|
||||
terminal.print({input: "hidden", value: id, name: "id"});
|
||||
terminal.print({input: "text", value: account.name, name: "name"});
|
||||
terminal.print({command: "/command " + JSON.stringify({action: "deleteAccount", id: id}), value: "delete account"});
|
||||
terminal.print({command: "/command " + JSON.stringify({action: "connect", id: id}), value: "connect"});
|
||||
terminal.print({command: "/command " + JSON.stringify({action: "disconnect", id: id}), value: "disconnect"});
|
||||
|
||||
if (!account.type) {
|
||||
terminal.print("Pick account type:");
|
||||
for (let i in packages) {
|
||||
let app = packages[i];
|
||||
if (app.manifest && app.manifest.chat) {
|
||||
terminal.print({command: "/command " + JSON.stringify({action: "updateAccount", id: id, type: app.name}), value: app.name});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let schema;
|
||||
for (let i in packages) {
|
||||
let app = packages[i];
|
||||
if (app.name == account.type && app.manifest && app.manifest.chat) {
|
||||
schema = app.manifest.chat.settings;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (schema) {
|
||||
for (var i in schema) {
|
||||
let field = schema[i];
|
||||
terminal.print({input: field.type, name: field.name, value: account[field.name] || field.default});
|
||||
}
|
||||
}
|
||||
}
|
||||
terminal.print({input: "submit", value: "Save Account", name: "submit"});
|
||||
}).then(updateWindows);
|
||||
}).catch(function(error) {
|
||||
print("whoops", error);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAccount(id) {
|
||||
return database.get(kAccountsKey).then(function(data) {
|
||||
let accounts = data ? JSON.parse(data) : [];
|
||||
for (var i = 0; i < accounts.length; i++) {
|
||||
if (accounts[i] && (!accounts[i].id || accounts[i].id == id)) {
|
||||
accounts.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return database.set(kAccountsKey, JSON.stringify(accounts));
|
||||
}).then(terminal.clear).then(updateWindows);
|
||||
}
|
||||
|
||||
function connect(id) {
|
||||
return database.get(kAccountsKey).then(function(data) {
|
||||
let accounts = data ? JSON.parse(data) : [];
|
||||
let account;
|
||||
for (var i = 0; i < accounts.length; i++) {
|
||||
if (accounts[i] && (!accounts[i].id || accounts[i].id == id)) {
|
||||
account = accounts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (account) {
|
||||
let self = {account: account};
|
||||
let options = {callback: chatCallback.bind(self)};
|
||||
for (var i in account) {
|
||||
options[i] = account[i];
|
||||
}
|
||||
return core.getService("chat", account.type).then(function(service) {
|
||||
return service.postMessage(options).then(function(sessions) {
|
||||
let session = sessions[0];
|
||||
self.session = session;
|
||||
gSessions[id] = session;
|
||||
session.conversations = {};
|
||||
getConversation(session, {});
|
||||
session.getConversations().then(function(conversations) {
|
||||
print(conversations);
|
||||
for (let j in conversations) {
|
||||
getConversation(session, {conversation: conversations[j]});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}).catch(function(error) {
|
||||
print(error);
|
||||
});
|
||||
}
|
||||
|
||||
function disconnect(id) {
|
||||
gSessions[id].disconnect();
|
||||
}
|
||||
|
||||
function updateWindows() {
|
||||
database.get(kAccountsKey).then(function(data) {
|
||||
let accounts = data ? JSON.parse(data) : [];
|
||||
|
||||
terminal.cork();
|
||||
terminal.select("windows");
|
||||
terminal.clear();
|
||||
terminal.print({style: "font-size: x-large", value: "Windows"});
|
||||
for (let i in accounts) {
|
||||
let account = accounts[i];
|
||||
terminal.print({style: "font-size: large", command: "/command " + JSON.stringify({action: "updateAccount", id: account.id}), value: account.name});
|
||||
if (gSessions[account.id] && gSessions[account.id].conversations) {
|
||||
let conversations = gSessions[account.id].conversations;
|
||||
for (let j in conversations) {
|
||||
terminal.print({
|
||||
command: "/command " + JSON.stringify({action: "window", account: account.id, conversation: j}),
|
||||
value: j ? j : "<service>",
|
||||
style: (conversations[j] == gCurrentConversation ? "font-weight: bold; " : "") + "color: white",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
terminal.print({style: "color: yellow", command: "/command " + JSON.stringify({action: "addAccount"}), value: "add account"});
|
||||
terminal.select("terminal");
|
||||
terminal.uncork();
|
||||
}).catch(function(error) {
|
||||
print(error);
|
||||
});
|
||||
}
|
||||
|
||||
function updateConversation() {
|
||||
if (gCurrentConversation) {
|
||||
Promise.all([
|
||||
gCurrentConversation.session.getHistory(gCurrentConversation.name),
|
||||
gCurrentConversation.session.getParticipants(gCurrentConversation.name),
|
||||
]).then(function(data) {
|
||||
print(data);
|
||||
let history = data[0];
|
||||
let participants = data[1];
|
||||
gCurrentConversation.messages = history;
|
||||
gCurrentConversation.participants = participants;
|
||||
terminal.cork();
|
||||
terminal.select("terminal");
|
||||
terminal.clear();
|
||||
for (var i in gCurrentConversation.messages) {
|
||||
printMessage(gCurrentConversation.messages[i]);
|
||||
}
|
||||
updateUsers();
|
||||
terminal.uncork();
|
||||
}).catch(function(error) {
|
||||
terminal.print(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateUsers() {
|
||||
terminal.cork();
|
||||
terminal.select("users");
|
||||
terminal.clear();
|
||||
terminal.print({style: "font-size: x-large", value: "Users"});
|
||||
if (gCurrentConversation) {
|
||||
for (var i in gCurrentConversation.participants) {
|
||||
terminal.print(gCurrentConversation.participants[i]);
|
||||
}
|
||||
}
|
||||
terminal.select("terminal");
|
||||
terminal.uncork();
|
||||
}
|
||||
|
||||
terminal.cork();
|
||||
terminal.split([
|
||||
{type: "horizontal", children: [
|
||||
{name: "windows", basis: "2in", grow: "0", shrink: "0"},
|
||||
{name: "terminal", grow: "1"},
|
||||
{name: "users", basis: "2in", grow: "0", shrink: "0"},
|
||||
]},
|
||||
]);
|
||||
updateTitle();
|
||||
updateWindows();
|
||||
updateUsers();
|
||||
terminal.setEcho(false);
|
||||
terminal.select("terminal");
|
||||
terminal.print("~Friends Chat");
|
||||
terminal.uncork();
|
||||
|
||||
function getConversation(session, message) {
|
||||
let result;
|
||||
for (var i in gSessions) {
|
||||
if (session == gSessions[i]) {
|
||||
let key = message.conversation || message.from || "";
|
||||
if (!session.conversations[key]) {
|
||||
session.conversations[key] = {
|
||||
session: session,
|
||||
name: key,
|
||||
messages: [],
|
||||
sendMessage: function(message) {
|
||||
return session.sendMessage(key, message);
|
||||
},
|
||||
};
|
||||
updateWindows();
|
||||
}
|
||||
result = session.conversations[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result && !gCurrentConversation) {
|
||||
gCurrentConversation = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function chatCallback(event) {
|
||||
print(event);
|
||||
if (event.action == "message") {
|
||||
let conversation = getConversation(this.session, event);
|
||||
if (conversation == gCurrentConversation) {
|
||||
printMessage(event);
|
||||
}
|
||||
conversation.messages.push(event);
|
||||
|
||||
if (!gFocus) {
|
||||
gUnread++;
|
||||
updateTitle();
|
||||
}
|
||||
} else {
|
||||
terminal.print("Unhandled event: ", JSON.stringify(event));
|
||||
}
|
||||
};
|
||||
|
||||
core.register("onInput", function(input) {
|
||||
if (input.substring(0, "/command ".length) == "/command ") {
|
||||
runCommand(JSON.parse(input.substring("/command ".length)));
|
||||
} else if (gCurrentConversation) {
|
||||
gCurrentConversation.sendMessage(input).catch(function(error) {
|
||||
terminal.print("Message not sent: ", error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function niceTime(lastTime, thisTime) {
|
||||
if (!lastTime) {
|
||||
return thisTime;
|
||||
}
|
||||
let result = [];
|
||||
let lastParts = lastTime.split(" ");
|
||||
let thisParts = thisTime.split(" ");
|
||||
for (let i = 0; i < thisParts.length; i++) {
|
||||
if (thisParts[i] !== lastParts[i]) {
|
||||
result.push(thisParts[i]);
|
||||
}
|
||||
}
|
||||
return result.join(" ");
|
||||
}
|
||||
|
||||
function formatMessage(message) {
|
||||
var result;
|
||||
if (typeof message == "string") {
|
||||
result = [];
|
||||
var regex = /(\w+:\/*\S+?)(?=(?:[\.!?])?(?:$|\s))/gi;
|
||||
var match;
|
||||
var lastIndex = 0;
|
||||
while ((match = regex.exec(message)) !== null) {
|
||||
result.push({class: "base1", value: message.substring(lastIndex, match.index)});
|
||||
result.push({href: match[0]});
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
result.push({class: "base1", value: message.substring(lastIndex)});
|
||||
} else {
|
||||
result = message;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var lastTimestamp = null;
|
||||
function printMessage(message) {
|
||||
var now = message.timestamp || new Date().toString();
|
||||
var from = message.from || "unknown";
|
||||
|
||||
terminal.print(
|
||||
{class: "base0", value: niceTime(lastTimestamp, now)},
|
||||
" ",
|
||||
{class: "base00", value: "<"},
|
||||
{class: "base3", value: from},
|
||||
{class: "base00", value: ">"},
|
||||
" ",
|
||||
formatMessage(message.message));
|
||||
lastTimestamp = now;
|
||||
}
|
||||
|
||||
core.register("focus", function() {
|
||||
gFocus = true;
|
||||
gUnread = 0;
|
||||
updateTitle();
|
||||
});
|
||||
|
||||
core.register("blur", function() {
|
||||
gFocus = false;
|
||||
});
|
||||
|
||||
// Connect all accounts on start.
|
||||
Promise.all([database.get(kAccountsKey), core.getPackages()]).then(function(results) {
|
||||
let accounts = results[0] ? JSON.parse(results[0]) : [];
|
||||
for (let i in accounts) {
|
||||
connect(accounts[i].id);
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user