Ssh-chat

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Привет, Хабр. Console chat отличная вещь, но для для фронтендеров, а что если вы хотите такой же, но для бэкэнда. Если да, то эта статья для вас. Но какой инструмент часто используют в бэкенде, Правильно ssh, так что представляю sshchat


Как это будет выглядеть


Где-то на сервере крутиться программа на ноде.
Как только кто-то хочет, подключится к чату он вводит:


ssh server -p 8022

После этого система спрашивает пароль и сверяет его с паролем в специальном файле. Если пароль совпал, то подключаем к чату(юзер получает 100 предыдущих сообщений и все остальные видят что он подключился)
Дальше он принимает сообщения других, и может написать своё.
Вот с сообщениями поинтереснее:


@box{@color(red){Red text in box}}

Отправит красный текст в коробке.


Приступим


Для работы с ssh мы будем использовать https://www.npmjs.com/package/ssh2
Для форматирования используем chalk и boxen
Так что установим их


npm i ssh2 chalk boxen

Теперь сам код одна из самых важных частей это парсер сообщений:
https://github.com/maximmasterr/ssh-server/blob/master/parserExec.js


// Подключаем chalk и boxen
const chalk = require('chalk');
const boxen = require('boxen');

// Здесь прописаны методы которые мы сможем использовать через @
// Функции принимают 2 аргумента то что в скобках и текс в фигурных скобках
let methods = {
  color: function(args, text) {
    return chalk.keyword(args)(text);
  },

  bold: function(args, text) {
    return chalk.bold(text);
  },

  underline: function(args, text) {
    return chalk.underline(text);
  },

  hex: function(args, text) {
    return chalk.hex(args)(text);
  },

  box: function(args, text) {
    return boxen(text, {
      borderStyle: 'round',
      padding: 1,
      borderColor: 'blueBright'
    });
  }
};

// Сам парсер 
function parseAndExecute(str) {
  let pos = 0;
  let stage = 0;
  let nS = '';
  let bufs = ['', '', '', ''];
  let level = 0;

  while (pos < str.length) {
    let symbol = str[pos];
    pos++;

    if (symbol == '\\' && '(){}@'.indexOf(str[pos]) !== -1) {
      bufs[stage] += str[pos];
      pos++;
      continue;
    }

    if (stage == 0 && symbol == '@') {
      stage++;
      nS += bufs[0];
      bufs[0] = '';
      continue;
    } else if (stage >= 1) {
      if (symbol == '(')
        if (stage < 2) {
          stage = 2;
        } else {
          level++;
        }

      if (symbol == ')' && stage >= 2 && level > 0) level--;

      if (symbol == '{')
        if (stage != 3) {
          stage = 3;
        } else {
          level++;
        }

      if (symbol == '}') {
        if (level == 0) {
          bufs[3] += '}';

          nS += methods[bufs[1]](bufs[2].slice(1, -1), parseAndExecute(bufs[3].slice(1, -1)));

          bufs = ['', '', '', ''];
          stage = 0;
          continue;
        } else {
          level--;
        }
      }
    }
    bufs[stage] += symbol;
  }
  return nS + bufs[0];
}

module.exports.parseAndExecute = parseAndExecute;

Форматирование:
https://github.com/maximmasterr/ssh-server/blob/master/format.js


const chalk = require('chalk');
const { parseAndExecute } = require('./parserExec')

// Стилизуем ник(Генерируем цвет и делаем жирным)
function getNick(nick) {
  let hash = 0;
  for (var i = 0; i < nick.length; i++) hash += nick.charCodeAt(i) - 32;

  return chalk.hsv((hash + 160) % 360, 90, 90)(chalk.bold(nick));
}

module.exports.format = function(nick, message) {
  const nickSpace = '\r  ' + ' '.repeat(nick.length);
  nick = getNick(nick) + ': ';

  message = message.replace(/\\n/gm, '\n'); // Заменяем \n новыми строками
  message = parseAndExecute(message) // Парсим

  // Добавлям к каждой новой строке отступ
  message = message
    .split('\n')
    .map((e, i) => '' + (i !== 0 ? nickSpace : '') + e)
    .join('\n');

  return nick + message;
};

Методы для отправки сообщения всем пользователям и сохранения 100 сообщений:
https://github.com/maximmasterr/ssh-server/blob/master/broadcaster.js


let listeners = []; // Все пользователи
let cache = new Array(100).fill('') // Кэш 

// Добавления и удаление подписчиков
module.exports.addListener = write => listeners.push(write) - 1;
module.exports.delListener = id => listeners.splice(id, 1);

// Отправляем сообщение
module.exports.broadcast = msg => {

  cache.shift()
  cache.push(msg)
  process.stdout.write(msg)
  listeners.forEach(wr => wr(msg));
}

// Получаем кэш
module.exports.getCache = ()=>cache.join('\r\033[1K')

Лобби, создание сервера и авторизация
https://github.com/maximmasterr/ssh-server/blob/master/lobby.js


const { Server } = require('ssh2');
const { readFileSync } = require('fs');

const hostKey = readFileSync('./ssh'); // Читаем ключ
const users = JSON.parse(readFileSync('./users.json')); // Юзеры

let connectionCallback = () => {};

module.exports.createServer = function createServer({ lobby }) {
  // Создаём сервер
  const server = new Server(
    {
      banner: lobby, // Баннер встречает до ввода пароля
      hostKeys: [hostKey]
    },
    function(client) {
      nick = '';
      client
        .on('authentication', ctx => {  // Авторизация
          if (ctx.method !== 'password') return ctx.reject();
          if (ctx.password !== users[ctx.username]) ctx.reject();
          nick = ctx.username;
          ctx.accept();
        })
        .on('ready', function() {
          connectionCallback(client, nick);
        });
    }
  );

  return server
};

module.exports.setConnectCallback = callback => { // Устанавливает колбэк при подключении
  connectionCallback = callback;
};

Различные методы:
https://github.com/maximmasterr/ssh-server/blob/master/utils.js


const { createInterface } = require('readline');

module.exports.getStream = function(client, onStream, onEnd){
  client  // Получает стрим и клиента
    .on('session', function(accept, reject) {
      accept()
        .on('pty', accept => accept & accept())
        .on('shell', accept => onStream(accept()));
    })
    .on('end', () => onEnd());
}

// Создаём коммуникатор 
module.exports.getCommunicator = function(stream, onMessage, onEnd){

  let readline = createInterface({ // Интерфейс для считывания строк
    input: stream,
    output: stream,
    prompt: '> ',
    historySize: 0,
    terminal: true
  })
  readline.prompt()

  readline.on('close', ()=>{
    radline = null;
    onEnd()
    stream.end()
  })

  readline.on('line', (msg)=>{
    stream.write('\033[s\033[1A\033[1K\r')
    onMessage(msg)
    readline.prompt()
  })

  // Метод для записи сообщения
  return msg=>{
    stream.write('\033[1K\r' + msg)
    readline.prompt()
  }
}

А теперь объединим
https://github.com/maximmasterr/ssh-server/blob/master/index.js


const { createServer, setConnectCallback } = require('./lobby');
const { getStream, getCommunicator } = require('./utils');
const { addListener, delListener, broadcast, getCache } = require('./broadcaster');
const { format, getNick } = require('./format');

//  Функция создания сервера 
module.exports = function({ lobby = 'Hi' } = {}) {
  const server = createServer({
    lobby
  });

  setConnectCallback((client, nick) => { // Ожидание соединения
    console.log('Client authenticated!');
    let id = null;
    getStream( // Получаем стрим
      client,
      stream => {
        const write = getCommunicator( // И интерфейс
          stream,
          msg => {
            if (msg == '') return;
            try {
              broadcast(format(nick, msg) + '\n'); // Как только получим сообщение, отправим его всем
            } catch (e) {}
          },
          () => {}
        );

        id = addListener(write); // Слушаем сообщения
        write('\033c' + getCache()); // Отправляем кэш
        broadcast(getNick(nick) + ' connected\n'); // Сообщаем о подключении
      },
      () => {

        delListener(id);
        broadcast(getNick(nick) + ' disconnected\n') // Сообщаем об отключении
      }
    );
  });

  server.listen(8022);
};

И финальный этап пример сервера:


const chat = require('.')

chat({})

Так же в файле users.json описаны юзеры и их пароли


Выводы


Вот так можно написать не самый простой чат в ssh
Для такого чата не нужно писать клиент, он обладает возможностями оформление, и его может развернуть любой желающий
Что ещё можно сделать:


  • Добавить возможность создания своих функций оформления
  • Добавить поддержку markdown
  • Добавить поддержку ботов


    Финальный репозиторий: https://github.com/maximmasterr/ssh-server


Источник: https://habr.com/ru/post/466633/


Интересные статьи

Интересные статьи

Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
Ваш сайт работает на 1С-Битрикс? Каждому клиенту вы даёте собственную скидку или назначаете персональную цену на товар? Со временем в вашей 1С сложилась непростая логика ценообразования и формирования...
Как-то у нас исторически сложилось, что Менеджеры сидят в Битрикс КП, а Разработчики в Jira. Менеджеры привыкли ставить и решать задачи через КП, Разработчики — через Джиру.
Если в вашей компании хотя бы два сотрудника, отвечающих за работу со сделками в Битрикс24, рано или поздно возникает вопрос распределения лидов между ними.
Тема статьи навеяна результатами наблюдений за методикой создания шаблонов различными разработчиками, чьи проекты попадали мне на поддержку. Порой разобраться в, казалось бы, такой простой сущности ка...