123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /*
- Copyright (c) 2019,2020 Farooq Karimi Zadeh <fkz@riseup.net>
- VM-IRC is under GNU LGPL 3 or at your option any later version and comes
- without any warranty from author(s).
- */
- const ad_txt = "VM-IRC, IRC client for KaiOS";
- // ^ Advertisement text :)
- const server_addr = "irc.freenode.net";
- var mynick = null;
- var sock = null;
- var channels = null;
- var firsttime = {};
- var quit = false;
- var hasfocus = ".left";
- var users_pane_visible = true;
- var lock = null;
- // ^ this is for keeping screen on till VM-IRC is running.
- // Thanks to perry the supreme app maker for the tip :D
- var tabs;
- var curr_tab;
- var hunt = "";
- var on_links_menu = false;
- var UTF8 = {
- encode: function(s){
- return unescape(encodeURIComponent(s));
- },
- decode: function(s){
- return decodeURIComponent(escape(s));
- }
- };
- function create_tab(name, with_right) {
- tabs.set(name, with_right?{".left": -1, ".right": -1}:{".left": -1});
- $("main").append(`<div id=${name}></div>`);
- let t;
- if (with_right) {
- t = '<div class="left"></div><div class="right"></div>';
- } else {
- t = '<div class="left"></div>';
- }
- $("#" + $.escapeSelector(name)).html(t);
- if (!with_right) {
- $("#" + $.escapeSelector(name)+">.left").css("flex-basis", "100%");
- }
- }
- function moveto(dir) {
- try{
- let tabs_array = array_of_tabs();
- if (tabs_array === []) {
- return;
- }
- $("#" + $.escapeSelector(tabs_array[curr_tab])).hide();
- if (dir === "left")
- curr_tab--;
- else
- curr_tab++;
- if (curr_tab < 0)
- curr_tab += tabs_array.length;
- curr_tab %= tabs_array.length;
- $("#" + $.escapeSelector(tabs_array[curr_tab])).show();
- $("h1").text(tabs_array[curr_tab]);
- if (!is_channel(tabs_array[curr_tab]))
- hasfocus = ".left";
- move2focus_msg();
- }catch(e){
- console.log(e);
- console.log(tabs_array);
- }
- }
- function is_channel(name) {
- return ["#", "&"].includes(name[0]);
- }
- function in_tabs(name) {
- for (let tab of tabs.keys()) {
- if (tab.toUpperCase() === name.toUpperCase()) {
- return tab;
- }
- }
- return false;
- }
- function process_cmd(s) {
- s = s.split(" ");
- s[0] = s[0].toUpperCase();
- let commands = {
- "JOIN": args => {
- if (is_channel(args[0])) {
- sock.send("JOIN " + args[0] + "\r\n");
- create_tab(args[0], true);
- }
- },
- "PART": args => {
- if (tabs[curr_tab] === server_addr)
- return;
- sock.send("PART " + tabs[curr_tab] + " :" + ad_txt + "\r\n");
- commands["CLOSE"]("");
- },
- "CLOSE": args => {
- if (tabs[curr_tab] === server_addr)
- return;
- if (is_channel(tabs[curr_tab])) {
- return commands["PART"]("");
- }
- $("main").remove("#" + $.escapeSelector(tabs[curr_tab]));
- tabs.pop(curr_tab);
- moveto("left");
- }
- }
- commands["J"] = commands["JOIN"];
- if (commands[s[0]]) {
- commands[s[0]](s.slice(1));
- }
- }
- function move2focus_msg() {
- let tabs_array = Array.from(tabs.entries());
- let selector = "#"+$.escapeSelector(tabs_array[curr_tab][0])+">.left>article";
- let focus_n = tabs.get(tabs_array[curr_tab][0])[hasfocus];
- if (focus_n === -1) {
- let elm = $(selector).last()[0];
- if (elm) elm.focus();
- } else {
- $(selector)[focus_n].focus();
- selector = selector.replace(">article", "");
- $(selector).scrollTo(":focus");
- }
- }
- function redify(s) {
- return `<span style="color: red">${s}</span>`;
- }
- function html_escape(s) {
- // CREDIT: https://stackoverflow.com/questions/5251520/
- return s.replace(
- /[^0-9A-Za-z ]/g,
- c => "&#" + c.charCodeAt(0) + ";"
- );
- }
- function array_of_tabs() {
- return Array.from(tabs.keys());
- }
- function links_menu(links) {
- on_links_menu = true;
- $("#menu").empty();
- $("main").hide();
- for (let link of links) {
- let _2add=`<article tabindex=-1><a href="${link}">${link}</a></article>`;
- $("#menu").append(_2add);
- }
- $("#menu").show();
- $("#menu > article").last()[0].focus();
- }
- function return_back() {
- on_links_menu = false;
- $("#menu").hide();
- $("main").show();
- move2focus_msg();
- }
- document.onkeydown = function (e) {
- switch (e.key) {
- case "Backspace":
- if (on_links_menu) {
- e.preventDefault();
- return_back();
- }
- break;
- case "Enter":
- if (on_links_menu) {
- var link = $("article:focus").find("a")[0].href;
- window.open(link);
- return_back();
- break;
- }
- var tab_name_es = $.escapeSelector(array_of_tabs()[curr_tab]);
- var links = $("#" + tab_name_es + ">.left>article:focus>a");
- if (links.length === 0) {
- alert("There is no link to be selected");
- } else {
- links_menu(links);
- }
- break;
- case "5":
- if (hasfocus === ".left"){
- hasfocus = ".right"; // -> users pane
- } else {
- hasfocus = ".left"; // -> messages pane
- }
- break;
- case "ArrowUp":
- if (on_links_menu) {
- var selector = "#menu>article";
- var curr_elm = $(selector + ":focus").index("article");
- if (curr_elm > 0) {
- curr_elm--;
- $(selector)[curr_elm].focus();
- }
- break;
- }
- var tabs_array = Array.from(tabs.entries());
- var curr_tab_name = tabs_array[curr_tab][0];
- var selector= "#"+$.escapeSelector(curr_tab_name)+">"+hasfocus;
- var curr_elm = tabs.get(curr_tab_name)[hasfocus];
- if (curr_elm === -1) {
- curr_elm = $(selector + ">article").length - 1;
- }
- if (curr_elm != 0) {
- curr_elm--;
- $(selector + ">article")[curr_elm].focus();
- }
- var new_values = tabs.get(curr_tab_name);
- new_values[hasfocus] = curr_elm;
- tabs.set(curr_tab_name, new_values);
- move2focus_msg();
- $(selector).scrollTo("-=37px");
- break;
- case "ArrowDown":
- if (on_links_menu) {
- var selector = "#menu>article";
- var curr_elm = $(selector + ":focus").index("article");
- if (curr_elm <= $(selector).length) {
- curr_elm++;
- $(selector)[curr_elm].focus();
- }
- break;
- }
- var tabs_array = Array.from(tabs.entries());
- var curr_tab_name = tabs_array[curr_tab][0];
- var selector= "#"+$.escapeSelector(curr_tab_name)+">"+hasfocus;
- var curr_elm = tabs.get(curr_tab_name)[hasfocus];
- if (curr_elm != -1) {
- curr_elm++;
- $(selector + ">article")[curr_elm].focus();
- }
- var new_values = tabs.get(curr_tab_name);
- if (curr_elm === $(selector + ">article").length - 1)
- curr_elm = -1;
- new_values[hasfocus] = curr_elm;
- tabs.set(curr_tab_name, new_values);
- move2focus_msg();
- $(selector).scrollTo("+=32px");
- break;
- case "ArrowLeft":
- moveto("left");
- break;
- case "ArrowRight":
- moveto("right");
- break;
- case "SoftLeft":
- if (sock == null || sock.readyState != "open"){
- alert("socket not ready!");
- break;
- }
- if (tabs[curr_tab] === server_addr) {
- alert("Please move to a channel");
- break;
- }
- var msg_text = prompt("Message");
- if (!msg_text)
- break;
- if (msg_text.startsWith("/")){
- process_cmd(msg_text.replace("/", ""));
- break;
- }
- var mynick_ = redify(mynick);
- var msg_to_add = `<article tabindex=-1 class="ownmsg"><${mynick_}>${msg_text}</article>`;
- selector = "#" +$.escapeSelector(tabs[curr_tab]) + ">.left";
- $(selector).append(msg_to_add);
- $(selector + ">article").last().linky();
- msg_text = UTF8.encode(msg_text);
- sock.send("PRIVMSG "+tabs[curr_tab]+ " :" + msg_text + "\r\n");
- move2focus_msg();
- break;
- case "SoftRight":
- if (sock != null && sock.readyState === "open"){
- quit = true;
- sock.send("QUIT :" + ad_txt + "\r\n");
- sock.close();
- }
- window.close();
- break;
- case "Call":
- try{
- var towhom = prompt("To whom?");
- if (towhom == null)
- break;
- var msg_text = prompt("Message to " + towhom);
- if (msg_text == null)
- break;
-
- let target_tab = in_tabs(towhom);
- if (!target_tab) {
- target_tab = towhom;
- create_tab(target_tab, false);
- }
- let selector = "#" + $.escapeSelector(target_tab) + ">.left";
- let txt2add = redify("<" + mynick + ">") + " " + msg_text;
- $(selector).append(`<article class="ownmsg">${txt2add}</article>`);
- while (array_of_tabs()[curr_tab] != target_tab) {
- moveto("right");
- }
- // TODO
- msg_text = UTF8.encode(msg_text);
- sock.send("PRIVMSG " + towhom + " :" + msg_text + "\r\n");
- move2focus_msg();
- }catch(e){console.log(e);}
- break;
-
- case "1":
- var current_size = Number($("body").css("fontSize").replace("px", ""));
- if (current_size === 0) break;
- current_size--;
- $("body").css("fontSize", current_size.toString() + "px");
- break;
- case "2":
- $("body").css("fontSize", "12px");
- break;
- case "3":
- var current_size = Number($("body").css("fontSize").replace("px", ""));
- if (current_size === "64") break;
- current_size++;
- $("body").css("fontSize", current_size.toString() + "px");
- break;
-
- case "*":
- var curr_tab_name = array_of_tabs()[curr_tab];
- if (is_channel(curr_tab_name)) {
- let selector = "#"+$.escapeSelector(curr_tab_name)+">.left";
- let width = $(selector).css("flex-basis");
- if (width === "75%") {
- $(selector).css("flex-basis", "100%");
- $(selector.replace(".left", ".right")).css("flex-basis", "0");
- hasfocus = ".left";
- } else {
- $(selector).css("flex-basis", "75%");
- $(selector.replace(".left", ".right")).css("flex-basis", "25%");
- }
- }
- break;
-
- case "#":
- if (lock === null){
- lock = window.navigator.requestWakeLock("screen");
- alert("Screen will not turn off");
- } else {
- lock.unlock();
- lock = null;
- alert("Screen will turn off");
- // TODO for this message and the previous one:
- // better message
- }
-
- }
- hunt += e.key;
- if (hunt === "768286") {
- // DO NOT TELL ANYONE ABOUT THIS!
- // It's an easter egg ;)
- alert("Farooq is a POTATO!");
- hunt = "";
- }
- if (hunt.length > 7) {
- hunt = "";
- }
- };
- function stort() {
- tabs = new Map();
- curr_tab = 0;
- try{
- // ^ DO NOT CHANGE NAME OF THIS FUNCTION!
- var def;
- $("body").css("fontSize", "12px");
- if (mynick === null){
- def = window.localStorage.getItem("nick");
- if (["null", null].includes(def)){
- mynick = prompt("Nick (max 9 chars)");
- }else{
- mynick = prompt("Nick (max 9 chars) or press OK for " + def);
- if (mynick === ""){
- mynick = def;
- }
- }
- }
- if (mynick === null) window.close();
- window.localStorage.setItem("nick", mynick);
- if (channels === null){
- def = window.localStorage.getItem("channels");
- if (["null", null].includes(def)){
- channels = prompt("Channels(space seperated)");
- }else{
- channels = prompt("Channels(space seperated) or press OK for " + def);
- if (channels === ""){
- channels = def;
- }
- }
- }
- if (channels === null) window.close();
- window.localStorage.setItem("channels", channels);
- sock = navigator.mozTCPSocket.open(server_addr,6697,{useSecureTransport: true});
- sock.onopen = function (e) {
- e.target.send("USER vm-irc vm-irc vm-irc :VM-IRC User\r\n");
- e.target.send("NICK " + mynick + "\r\n");
- tabs.clear();
- $("main").empty();
- create_tab(server_addr, false);
- for (let channel of channels.split(" ")) {
- if (is_channel(channel)) {
- process_cmd("JOIN " + channel);
- moveto("right");
- }
- }
- };
- sock.ondata = function (e) {
- if (typeof(e.data) !== "string")
- return;
- var msgs = e.data.split("\r\n");
- for (let msg of msgs){
- if (msg.startsWith("PING")){
- e.currentTarget.send("PONG " + msg.split(" ")[1] + "\r\n");
- continue;
- }
- msg = UTF8.decode(msg);
- var token = msg.split(" ", 3);
- var theirnick = token[0].split("!")[0].replace(":", "");
- if (token[1] === "PRIVMSG"){
- try{
- var target = token[2];
- var msg_text = msg.slice(msg.search(" :") + 2, msg.length);
- msg_text = html_escape(msg_text);
- if (document.visibilityState === "hidden"){
- if (window.Notification.permission === "granted"){
- if (mynick === target || msg_text.includes(mynick)){
- var vibrate_pattern = [200, 100, 200, 100, 200];
- new window.Notification(theirnick, {body: msg_text,
- vibrate:vibrate_pattern
- });
- }
- }
-
- }
- if (array_of_tabs().includes(target)) {
- theirnick = "<" + theirnick + ">";
- } else if (target === mynick) {
- // then it's a PM
- let target_tab = in_tabs(theirnick);
- if (!target_tab) {
- target_tab = theirnick;
- create_tab(theirnick, false);
- }
- let selector="#"+$.escapeSelector(target_tab)+">.left";
- theirnick = redify(`<${theirnick}>`);
- let text2add = `<article tabindex=-1>${theirnick}${msg_text}</article>`;
- $(selector).append(text2add);
- continue;
- }
- var text = redify(theirnick) + " " + msg_text + "<br>";
- if (msg_text.search(mynick) !== -1 || target === mynick)
- text = "<b>" + text + "<b>";
- text = "<article tabindex=-1>" + text + "</article>";
- let selector = "#" + $.escapeSelector(target) + ">.left";
- $(selector).append(text);
- $(selector + ">article").last().linky();
- }catch(e){console.log(e);}
- }
- if (token[1] === "NOTICE"){
- var msg_text = msg.slice(msg.search(" :") + 2, msg.length);
- msg_text = html_escape(msg_text);
- var text=`<article tabindex=-1>-${theirnick}- ${msg_text}</article>`;
- let selector = "#" + $.escapeSelector(server_addr)+">.left";
- $(selector).append(text);
- $(selector + ">article").last().linky();
- }
- let selector;
- if (token[1] === "JOIN"){
- if (theirnick === mynick) continue;
- selector = "#" + $.escapeSelector(token[2]) + " > .left";
- $(selector).append(`<div tabindex=-1>${theirnick} joined.</div>`);
- selector = "#" + $.escapeSelector(token[2]) + " > .right";
- $(selector).append(`<div name="${theirnick}">${theirnick}</div>`);
- }
- if (token[1] === "PART"){
- selector = "#" + $.escapeSelector(token[2]) + " > .left";
- $(selector).append(`<div tabindex=-1>${theirnick} left.</div>`);
- selector = selector.replace(".left", ".right");
- selector += ">div[name=" + theirnick + "]";
- $(selector).remove();
- }
- if (token[1] === "QUIT"){
- for (let key of tabs.keys()) {
- if (is_channel(key)) {
- let key_es = $.escapeSelector(key);
- let selector ="#"+key_es+">.right";
- let elm =$(selector+">div[name="+theirnick+"]");
- if (elm.length != 0) {
- elm.remove();
- $(selector.replace(".right", ".left")).append(`<div tabindex=-1>${theirnick} has quit.`);
- }
- }
- }
- }
- if (token[1] === "353"){ // Channel user list
- msg = msg.replace(/:/g, "").split(" ");
- let theirnicks = msg.slice(5).join(" ");
- let channel = msg[4];
- let selector_msg = "#" + $.escapeSelector(channel) + ">.left";
- let selector_user = "#" + $.escapeSelector(channel) + ">.right";
- if (!firsttime[channel]){
- $(selector_msg).append("Users: ");
- firsttime[channel] = true;
- }
- $(selector_msg).append(theirnicks + " ");
- // now the user list pane
- for (let anick of theirnicks.split(" ")){
- $(selector_user).append(`<div name="${anick}">${anick}</div>`);
- }
- }
- if (token[1] === "366"){
- firsttime[msg.split(" ")[4]] = true;
- }
- if (token[1] === "332"){ // Reply to TOPIC command
- let topic = msg.slice(msg.replace(":", ".").search(":") + 1, msg.length);
- topic = html_escape(topic);
- let channel = msg.split(" ")[3];
- selector = "#" + $.escapeSelector(channel) + ">.left";
- $(selector).append(`<article tabindex=-1>Topic: ${topic}</article>`);
- $(selector + ">article").last().linky();
- }
- if (token[1] === "433"){ // Nick name already in use
- mynick += "_";
- e.currentTarget.send(`NICK ${mynick}\r\n`);
- }
- if (token[1] === "NICK"){
- var newnick = token[2].replace(":", "");
- if (theirnick === mynick) {
- mynick = newnick;
- } else {
- let txt = `<article tabindex=-1>${theirnick} changed their nick to ${newnick}`;
- var curr_tab_name_es = $.escapeSelector(array_of_tabs()[curr_tab]);
- $("#" + curr_tab_name_es + ">.left").add(txt);
- $("#" + curr_tab_name_es + ">.right").remove(`article[name=${theirnick}]`);
- txt = `<article tabindex=-1>${newnick}</article>`;
- $("#" + curr_tab_name_es + ">.right").add(txt);
- }
- }
- move2focus_msg();
- }
- };
- sock.onclose = function (e) {
- if (!quit){
- stort();
- }
- };
- }catch(E){console.log(E);}
- }
- $(stort);
|