├── .gitignore ├── src └── ru_slack │ ├── exceptions.cr │ ├── rest_apis │ └── users.cr │ ├── client.cr │ └── rtm_connector.cr └── shard.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /.crystal 2 | /app.cr 3 | 4 | -------------------------------------------------------------------------------- /src/ru_slack/exceptions.cr: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RuSlack 4 | class APIException < Exception 5 | end 6 | class ReconnectException < Exception 7 | end 8 | end 9 | 10 | -------------------------------------------------------------------------------- /src/ru_slack/rest_apis/users.cr: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "../exceptions.cr" 4 | 5 | module RuSlack 6 | module RESTAPIs 7 | module Users 8 | def users_list 9 | end 10 | end 11 | end 12 | end 13 | 14 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: quizbot 2 | version: 0.1.0 3 | 4 | authors: 5 | - ru_shalm 6 | 7 | # description: | 8 | # Short description of quizbot 9 | 10 | # dependencies: 11 | # pg: 12 | # github: will/crystal-pg 13 | # version: "~> 0.5" 14 | 15 | # development_dependencies: 16 | # webmock: 17 | # github: manastech/webmock 18 | 19 | # license: MIT 20 | -------------------------------------------------------------------------------- /src/ru_slack/client.cr: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "uri" 4 | require "json" 5 | require "http/client" 6 | require "http/params" 7 | require "./exceptions.cr" 8 | require "./rtm_connector.cr" 9 | 10 | module RuSlack 11 | class Client 12 | SLACK_API_BASE = "https://slack.com/api" 13 | 14 | def initialize(token : String) 15 | @token = token 16 | end 17 | 18 | getter :token 19 | 20 | def rtm 21 | @rtm ||= RTMConnector.new(self) 22 | end 23 | 24 | def users_list 25 | request_get("/users.list") 26 | end 27 | 28 | macro define_request_methods(types) 29 | {% for name in types %} 30 | def request_{{name}}(path : String, params : Hash | Nil = nil ) 31 | if params.is_a?(Nil) 32 | params = {} of String => String | Int32 | Nil 33 | end 34 | params["token"] = @token.to_s 35 | url = "#{SLACK_API_BASE}#{path}" 36 | {% if name.id == "get" %} 37 | http_params = HTTP::Params.build do |builder| 38 | params.each do |key, value| 39 | builder.add key, value.to_s 40 | end 41 | end 42 | url += "?#{http_params.to_s}" 43 | puts url.inspect 44 | response = HTTP::Client.{{name}}(url) 45 | {% elsif name.id == "post" %} 46 | response = HTTP::Client.{{name}}_form(url, params) 47 | {% end %} 48 | JSON.parse(response.body) 49 | end 50 | {% end %} 51 | end 52 | 53 | define_request_methods([get, post]) 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /src/ru_slack/rtm_connector.cr: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "uri" 4 | require "json" 5 | require "http/client" 6 | require "http/web_socket" 7 | require "./exceptions.cr" 8 | require "./client.cr" 9 | 10 | module RuSlack 11 | class RTMConnector 12 | @ws : HTTP::WebSocket? 13 | 14 | def initialize(client : Client) 15 | @client = client 16 | @on_message_block = {} of String => Array(JSON::Any -> ) 17 | @ws = nil 18 | @name = "" 19 | @connecting = Channel(Bool).new 20 | @pong_time = 0 21 | end 22 | 23 | def start 24 | loop do 25 | begin 26 | url = request_rtm_url 27 | @pong_time = Time.now.epoch 28 | @ws = ws = HTTP::WebSocket.new(URI.parse(url)) 29 | ws.on_message(&-> receive_message(String)) 30 | ws.on_close(&-> on_close(String)) 31 | ping_sender 32 | ws.run 33 | rescue e : ReconnectException 34 | @connecting.close 35 | puts "disconnected: #{e.inspect}" 36 | sleep 5 37 | @connecting = Channel(Bool).new 38 | puts "retry" 39 | end 40 | end 41 | end 42 | 43 | def on(type : Symbol, &block : JSON::Any ->) 44 | @on_message_block[type.to_s] ||= [] of (JSON::Any ->) 45 | @on_message_block[type.to_s].push(block) 46 | end 47 | 48 | def post(channel : String, text : String) 49 | ws = @ws 50 | if ws.is_a?(HTTP::WebSocket) 51 | ws.send({ 52 | id: Time.now.epoch, 53 | type: "message", 54 | channel: channel, 55 | text: text 56 | }.to_json) 57 | end 58 | end 59 | 60 | def request_rtm_url 61 | response = HTTP::Client.get("https://slack.com/api/rtm.start?token=#{@client.token}") 62 | json = JSON.parse(response.body) 63 | raise APIException.new(response.body) unless json["ok"] 64 | @name = json["self"]["name"].to_s 65 | puts json["url"].to_s 66 | json["url"].to_s 67 | end 68 | 69 | def receive_message(m : String) 70 | json = JSON.parse(m) 71 | puts json.inspect 72 | type = json["type"]?.to_s 73 | if json["reply_to"]? 74 | case type 75 | when "pong" 76 | @pong_time = json["reply_to"].to_s.to_i 77 | end 78 | else 79 | if json.size > 0 80 | if @on_message_block.has_key?(type) 81 | @on_message_block[type].each do |block| 82 | block.call(json) 83 | end 84 | else 85 | print "[missing] " 86 | puts type 87 | end 88 | end 89 | end 90 | end 91 | 92 | def on_close(m : String) 93 | raise ReconnectException.new("on_close") 94 | end 95 | 96 | def ping_sender 97 | spawn do 98 | loop do 99 | sleep 3 100 | break if @connecting.closed? 101 | if Time.now.epoch - @pong_time < 10 102 | ws = @ws 103 | if ws 104 | ws.send({ 105 | id: Time.now.epoch, 106 | type: "ping" 107 | }.to_json) 108 | end 109 | else 110 | raise ReconnectException.new("not receive pong message") 111 | end 112 | end 113 | end 114 | end 115 | end 116 | end 117 | 118 | --------------------------------------------------------------------------------