From 6a73d58574c850f3e6fd53e6782e6cd0aebbff4d Mon Sep 17 00:00:00 2001 From: Yessiest Date: Sun, 30 Apr 2023 22:52:32 +0400 Subject: [PATCH] basic messaging works --- client.rb | 77 +++++++++++++++++++++++++++ proto.rb | 66 ++++++++++++++++++----- server.rb | 153 +++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 268 insertions(+), 28 deletions(-) create mode 100755 client.rb diff --git a/client.rb b/client.rb new file mode 100755 index 0000000..9781b2b --- /dev/null +++ b/client.rb @@ -0,0 +1,77 @@ +#!/usr/bin/ruby +require 'readline' +require 'net/http' +require 'json' +require 'uri' +puts "Connecting to server #{ARGV[0]} on port #{ARGV[1]}" +def get(path) + where = URI("http://#{ARGV[0]}:#{ARGV[1]}/#{path}") + JSON.parse(Net::HTTP.get(where)) +end + +def post(path,data) + where = URI("http://#{ARGV[0]}:#{ARGV[1]}/#{path}") + Net::HTTP.post(where,data.to_json) +end + +version = get("version")["version"] +puts "Server reported version: #{version}" +print "Nickname> " +nickname = $stdin.gets.strip +puts "Trying to log in..." +res = get("/user/exists?protocol_id=#{"heimdall-"+nickname}") +puts "Account exists! exiting" if res["exists"] +return if res["exists"] +puts "Creating account..." +test = post("/user/new",{username: nickname, protocol_id: "heimdall-"+nickname}) +unless test.kind_of? Net::HTTPOK then + puts "Something went wrong! exiting" + exit +end +puts "Your id is: heimdall-#{nickname}" +Thread.new do + while true do + sleep 1 + messages = get("/user/read?protocol_id=#{"heimdall-"+nickname}") + if messages["error"] then + puts "Error: #{messages["error"]}" + next + end + messages["messages"].each { |x| + puts "#{x["from"]} says: #{x["content"]}" + } + end +end + +target = nil + +at_exit do + post("/user/delete",{ + "protocol_id"=> "heimdall-"+nickname + }) +end + +while buf = Readline.readline("> ", true) + if buf == "/help" then + puts "Commands:" + puts "/help - this message" + puts "/send - direct messages to somebody" + puts "/exit - quit program" + end + if buf == "/exit" then + post("/user/delete",{ + "protocol_id"=> "heimdall-"+nickname + }) + exit + end + if target then + post("/user/send", { + "to" => target, + "content" => buf, + "from" => "heimdall-"+nickname + }) + end + if buf.match(/^\/send .*$/) then + target = buf.match(/^\/send ([^\s]*)$/)[1] + end +end diff --git a/proto.rb b/proto.rb index d8ed3ad..5d39a6b 100644 --- a/proto.rb +++ b/proto.rb @@ -1,6 +1,12 @@ UIDS = {} module Heimdall + VERSION = "0.1 alpha" + attr_reader :VERSION + + class ProtocolError < StandardError + end + class UID def initialize @UID_prefix = "abstract" if not @UID_prefix @@ -22,34 +28,47 @@ module Heimdall @users[user.protoid] = user end def get(protoid) - return @users[user.protoid] + raise ProtocolError, "user not found" if not @users[protoid] + return @users[protoid] + end + def delete(protoid) + @users.delete protoid end end class User < UID - def initialize(username, protoid) - @username = username - @protoid = protoid + def initialize(data) + @username = data["username"] + @protoid = data["protocol_id"] @UID_prefix = "user" + @direct = DirectChannel.new super() end attr_reader :username attr_reader :protoid - end - - class Server < UID - def initialize - @UID_prefix = "server" - super() - end + attr_reader :direct end class Channel < UID def initialize @UID_prefix = "channel" + @messages = MsgStack.new + @read = 0 super() end - + def send(msg) + @messages.push(msg) + @read = @read+1 + end + def get(n = 1) + raise Heimdall::ProtocolError, "Invalid number of messages" if n < 0 + return @messages.pull(n) + end + def read() + messages = @messages.pull(@read) + @read = 0 + return messages + end end class DirectChannel < Channel @@ -57,7 +76,6 @@ module Heimdall @UID_prefix = "dchannel" super() end - end class ServerChannel < Channel @@ -68,16 +86,36 @@ module Heimdall end class Message < UID - def initialize + def initialize(data) + @content = data["content"] + @from = data["from"] + @to = data["to"] @UID_prefix = "message" super() end + def to_struct + return { + "content" => @content, + "from" => @from, + "to" => @to + } + end + attr_reader :content + attr_reader :from + attr_reader :to end class MsgStack < UID def initialize @UID_prefix = "msgstack" + @messages = [] super() end + def push(msg) + @messages.append(msg) + end + def pull(n) + @messages.last n + end end end diff --git a/server.rb b/server.rb index 9d90403..b4fa739 100644 --- a/server.rb +++ b/server.rb @@ -4,41 +4,166 @@ require "json" Users = Heimdall::UserCache.new -def require_keys(dict,key_dict) +def _require_keys(dict,key_dict) raise KeyError, "not a dict" unless dict.kind_of? Hash key_dict.each_pair { |k,v| - unless (dict.has_key? k) and (dict[k].kind_of? v) then + unless (dict.has_key? k.to_s) and (dict[k.to_s].kind_of? v) then raise KeyError, "key #{k} of type #{v} required" end } end +def _send_json(res,data,code: 200) + res.body = JSON::fast_generate(data) + res['Content-Type'] = "application/json" + res.status = code +end + +def _throw_error(res,error) + _send_json(res,{ + error: "#{error}" + },code: 400) +end + +def _parse_json(body,key_dict) + data = JSON::Parser.new(body).parse + _require_keys(data,key_dict) + return data +end + + server = Hyde::Server.new Port: 8000 do path "user" do + preprocess do |ctx| + puts ctx.request.query.inspect + puts ctx.request.body.inspect + end + + get "exists" do |ctx| + req,res = ctx.request,ctx.response + begin + _require_keys(req.query, { + protocol_id: String + }) + _send_json(res,{ + exists: (Users.get(req.query["protocol_id"]) != nil) + }) + rescue KeyError => keyerror + _throw_error(res,keyerror) + rescue Heimdall::ProtocolError => protoerr + _send_json(res, { + exists: false + }) + end + end + post "new" do |ctx| req,res = ctx.request,ctx.response begin - data = JSON::Parser.new(req.body).parse - require_keys(data,{ + data = _parse_json(req.body,{ username: String, protocol_id: String }) - Users.add(Heimdall::User.new data[:username], data[:protocol_id]) + new_user = Heimdall::User.new(data) + Users.add(new_user) + _send_json(res,{ + uid: new_user.UID + }) rescue JSON::ParserError => jsonerror - res.body = JSON::fast_generate({ - error: "#{jsonerror}" - }) - res['Content-Type'] = "application/json" - res.status = 400 + _throw_error(res,jsonerror) rescue KeyError => keyerror - res.body = JSON::fast_generate({ - error: "#{keyerror}" + _throw_error(res,keyerror) + end + end + + post "send" do |ctx| + req,res = ctx.request,ctx.response + begin + data = _parse_json(req.body, { + content: String, + from: String, + to: String }) - res['Content-Type'] = "application/json" - res.status = 400 + new_message = Heimdall::Message.new(data) + Users.get(new_message.to).direct.send(new_message) + _send_json(res,{ + uid: new_message.UID + }) + rescue JSON::ParserError => jsonerror + _throw_error(res,jsonerror) + rescue KeyError => keyerror + _throw_error(res,keyerror) + rescue Heimdall::ProtocolError => protoerr + _throw_error(res,protoerr) + end + end + + get "get" do |ctx| + req,res = ctx.request,ctx.response + begin + _require_keys(req.query,{ + n: Integer, + protocol_id: String + }) + number = req.query[:n] + id = req.query["protocol_id"] + user = Users.get(id) + messages = user.direct.get(number) + _send_json(res, { + messages: messages.map { |x| + x = x.to_struct + x["username"] = Users.get(x["from"]).username + x + } + }) + rescue Heimdall::ProtocolError => protoerr + _throw_error(res,protoerr) + end + end + + get "read" do |ctx| + req,res = ctx.request,ctx.response + begin + _require_keys(req.query,{ + protocol_id: String + }) + id = req.query["protocol_id"] + user = Users.get(id) + messages = user.direct.read + _send_json(res, { + messages: messages.map { |x| + x = x.to_struct + x["username"] = Users.get(x["from"]).username + x + } + }) + rescue Heimdall::ProtocolError => protoerr + _throw_error(res,protoerr) + end + end + + post "delete" do |ctx| + req,res = ctx.request, ctx.response + begin + data = _parse_json(req.body,{ + protocol_id: String + }) + id = data["protocol_id"] + _send_json(res, { + uid: Users.get(id).UID + }) + Users.delete(id) + rescue Heimdall::ProtocolError => protoerr + _throw_error(res,protoerr) end end end + + get "version" do |ctx| + ctx.response.body = "{\"version\":\"#{Heimdall::VERSION}\"}" + ctx.response['Content-Type'] = "application/json" + end + end server.start