customer_service_im 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +18 -0
  5. data/app/channels/application_cable/channel.rb +4 -0
  6. data/app/channels/application_cable/connection.rb +21 -0
  7. data/app/channels/concerns/customer_service_im/connection.rb +26 -0
  8. data/app/channels/customer_service_im/conversations_channel.rb +12 -0
  9. data/app/channels/customer_service_im/messages_channel.rb +15 -0
  10. data/app/controllers/customer_service_im/application_controller.rb +49 -0
  11. data/app/controllers/customer_service_im/conversations_controller.rb +34 -0
  12. data/app/controllers/customer_service_im/messages_controller.rb +65 -0
  13. data/app/jobs/customer_service_im/application_job.rb +4 -0
  14. data/app/mailers/customer_service_im/application_mailer.rb +6 -0
  15. data/app/models/customer_service_im/application_record.rb +5 -0
  16. data/app/models/customer_service_im/concerns/customer.rb +75 -0
  17. data/app/models/customer_service_im/conversation.rb +49 -0
  18. data/app/models/customer_service_im/message.rb +103 -0
  19. data/app/models/customer_service_im/staff.rb +63 -0
  20. data/app/views/customer_service_im/conversations/_conversation.json.jbuilder +19 -0
  21. data/app/views/customer_service_im/conversations/_message.json.jbuilder +3 -0
  22. data/app/views/customer_service_im/conversations/index.json.jbuilder +5 -0
  23. data/app/views/customer_service_im/conversations/messages.json.jbuilder +3 -0
  24. data/app/views/customer_service_im/messages/_message.json.jbuilder +14 -0
  25. data/app/views/customer_service_im/messages/index.json.jbuilder +1 -0
  26. data/config/cable.yml +10 -0
  27. data/config/initializers/tx_asr.rb +28 -0
  28. data/config/routes.rb +23 -0
  29. data/db/migrate/20210927065745_create_customer_service_im_staffs.rb +10 -0
  30. data/db/migrate/20210927074031_create_customer_service_im_conversations.rb +12 -0
  31. data/db/migrate/20210927074557_create_customer_service_im_messages.rb +13 -0
  32. data/db/migrate/20210928084420_add_state_to_customer_service_im_messages.rb +5 -0
  33. data/db/migrate/20210928085514_add_last_messaged_on_to_customer_service_im_conversations.rb +5 -0
  34. data/db/migrate/20210930025215_add_unread_message_count_to_customer_service_im_conversations.rb +6 -0
  35. data/lib/customer_service_im/core_ext.rb +11 -0
  36. data/lib/customer_service_im/engine.rb +6 -0
  37. data/lib/customer_service_im/version.rb +3 -0
  38. data/lib/customer_service_im.rb +18 -0
  39. data/lib/generators/customer_service_im/install/USAGE +8 -0
  40. data/lib/generators/customer_service_im/install/install_generator.rb +7 -0
  41. data/lib/generators/customer_service_im/install/templates/initializer.rb +6 -0
  42. data/lib/tasks/customer_service_im_tasks.rake +4 -0
  43. data/lib/tsr_recognize_error.rb +2 -0
  44. metadata +178 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: badd62e6ced06b0e47fde103ffd45abfcf5861ee90882333d77bbda79707138f
4
+ data.tar.gz: 34b68749a124182c75ac86c8970ef695e1b3f0ff685f25c6dce0f66d02bb7924
5
+ SHA512:
6
+ metadata.gz: e0f45825d365993e55b5180d90bbc77d738f2d1156d7b876755142f10730c605b9fb55a7b891441df5ff8d9bc4ea40d5f9d914c298fee2ed8043921cb920ed4b
7
+ data.tar.gz: 83df77a1cafe32031d7581b1e62eaee6cb741eaf9feb2a51a66d1465147d1ecc78f3bd4c2e8ac6ac370d6aba9174461904e6f5cdcd6a6106889c67bf561ef844
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 ian
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # CustomerServiceIm
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'customer_service_im'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install customer_service_im
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,21 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ identified_by :current_user
4
+
5
+ def connect
6
+ self.current_user = find_user
7
+ end
8
+
9
+ private
10
+
11
+ def find_user
12
+ payload, = JWT.decode(request.params[:token], CustomerServiceIm.jwt_secret, true, { algorithm: "HS256" })
13
+
14
+ payload["data"]["user_type"].classify.constantize.find(payload["data"]["user_id"])
15
+ rescue JWT::DecodeError => e
16
+ logger = Logger.new Rails.root.join("log/jwt.log")
17
+ logger.error("JWT Decode Error: #{e}")
18
+ raise e
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+
2
+ module CustomerServiceIm
3
+ module Connection
4
+ extend ActiveSupport::Concern
5
+
6
+ def included(base)
7
+ base.class_eval do
8
+ identified_by :current_user
9
+ end
10
+ end
11
+
12
+ def connect
13
+ self.current_user = find_user
14
+ end
15
+
16
+ def find_user
17
+ payload, = JWT.decode(request.params[:token], CustomerServiceIm.jwt_secret, true, { algorithm: "HS256" })
18
+
19
+ payload["data"]["user_type"].classify.constantize.find(payload["data"]["user_id"])
20
+ rescue JWT::DecodeError => e
21
+ logger = Logger.new Rails.root.join("log/jwt.log")
22
+ logger.error("JWT Decode Error: #{e}")
23
+ raise e
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ module CustomerServiceIm
2
+ class ConversationsChannel < ApplicationCable::Channel
3
+ def subscribed
4
+ # stream_from "some_channel"
5
+ stream_from "customer_service_im/conversations_channel"
6
+ end
7
+
8
+ def unsubscribed
9
+ # Any cleanup needed when channel is unsubscribed
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module CustomerServiceIm
2
+ class MessagesChannel < ApplicationCable::Channel
3
+ def subscribed
4
+ # stream_from "some_channel"
5
+ current_user.online!
6
+
7
+ conversation = Conversation.find(params[:conversation_id])
8
+ stream_for conversation
9
+ end
10
+
11
+ def unsubscribed
12
+ # Any cleanup needed when channel is unsubscribed
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ module CustomerServiceIm
2
+ class ApplicationController < ActionController::API
3
+ include ActionController::ImplicitRender
4
+
5
+ def render_500(errors: nil)
6
+ render json: { errors: errors }, status: :internal_server_error
7
+ end
8
+
9
+ def render_422(errors: nil)
10
+ render json: { errors: errors }, status: :unprocessable_entity
11
+ end
12
+
13
+ def render_200(data)
14
+ render json: data
15
+ end
16
+
17
+ private
18
+
19
+ def token_user(user)
20
+ payload = { user_type: user.class.name, user_id: user.id }
21
+ JWT.encode(payload, hmac_secret, 'HS256')
22
+ end
23
+
24
+ def hmac_secret
25
+ CustomerServiceIm.jwt_secret
26
+ end
27
+
28
+ def client_has_valid_token?
29
+ !!logged_user
30
+ end
31
+
32
+ def logged_user
33
+ begin
34
+ token = request.headers[:token]
35
+ payload, = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
36
+ rescue JWT::DecodeError => e
37
+ logger = Logger.new Rails.root.join("log/jwt.log")
38
+ logger.error("JWT Decode Error: #{e}")
39
+ raise e
40
+ end
41
+
42
+ payload["data"]["user_type"].classify.constantize.find(payload["data"]["user_id"])
43
+ end
44
+
45
+ def authenticate_user!
46
+ render json: { error: 'Unauthorized' }, status: :unauthorized if !client_has_valid_token?
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module CustomerServiceIm
3
+ class ConversationsController < ApplicationController
4
+ before_action :set_conversation, only: [:messages, :all_messages_read]
5
+
6
+ def index
7
+ @conversations = Conversation.order('last_messaged_on desc')
8
+ @total_count = @conversations.size
9
+ end
10
+
11
+ def messages
12
+ @messages = @conversation.messages
13
+ .order('created_at desc')
14
+ .id_less_then(params[:min_id])
15
+ .tap do |records|
16
+ @total_count = records.size
17
+ end
18
+ .page(params[:page] || 1)
19
+ .per(params[:per] || 8)
20
+ .reverse
21
+ end
22
+
23
+ def all_messages_read
24
+ @conversation.all_messages_read!
25
+ head :no_content
26
+ end
27
+
28
+ private
29
+
30
+ def set_conversation
31
+ @conversation = Conversation.find(params[:id])
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,65 @@
1
+ module CustomerServiceIm
2
+ class MessagesController < ApplicationController
3
+ before_action :set_conversation, only: [ :index ]
4
+ before_action :set_message, only: [ :receive ]
5
+
6
+ def index
7
+ @messages = @conversation.messages.order("created_at desc")
8
+ end
9
+
10
+ def create
11
+ @message = Message.new(message_params.merge( user: logged_user, receiver_type: "Staff" ))
12
+
13
+ if @message.save
14
+
15
+ render_200 message: @message
16
+ else
17
+ render_422 errors: @message.errors
18
+ end
19
+ end
20
+
21
+ def reply
22
+ @message = Message.new(message_params.merge( staff: logged_user, receiver_type: "User" ))
23
+
24
+ if @message.save
25
+ render_200 message: @message
26
+ else
27
+ render_422 errors: @message.errors
28
+ end
29
+ end
30
+
31
+ def receive
32
+ if @message.received!
33
+ render_200 message: @message
34
+ else
35
+ render_422 @message.errors
36
+ end
37
+ end
38
+
39
+ def read
40
+ if @message.read!
41
+ render_200 message: @message
42
+ else
43
+ render_422 errors: @message.errors
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def message_params
50
+ params.require(:message).permit(
51
+ :conversation_id,
52
+ :content,
53
+ :attachment
54
+ )
55
+ end
56
+
57
+ def set_conversation
58
+ @conversation = Conversation.find(message_params[:conversation_id])
59
+ end
60
+
61
+ def set_message
62
+ @message = Message.find(params[:id])
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,4 @@
1
+ module CustomerServiceIm
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module CustomerServiceIm
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module CustomerServiceIm
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,75 @@
1
+
2
+ module CustomerServiceIm::Concerns::Customer
3
+ extend ActiveSupport::Concern
4
+
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ has_many :conversations, class_name: "CustomerServiceIm::Conversation"
8
+ has_many :messages, class_name: "CustomerServiceIm::Message"
9
+ end
10
+
11
+ klass.extend ClassMethods
12
+ end
13
+
14
+ def join_conversation(conversation)
15
+ conversation.update(user: self)
16
+ end
17
+
18
+ def online!
19
+ redis.sadd online_customers_redis_key, as_redis_record
20
+ end
21
+
22
+ def offline!
23
+ redis.sadd online_customers_redis_key, as_redis_record
24
+ end
25
+
26
+ # example "User:1"
27
+ def as_redis_record
28
+ "#{self.class.name}/#{self.id}"
29
+ end
30
+
31
+ def online?
32
+ id.in? self.class.online_customers_ids
33
+ end
34
+
35
+ def offline?
36
+ !online?
37
+ end
38
+
39
+ def send_message(content, attachment)
40
+ messages.create(
41
+ conversation: conversation,
42
+ user: self
43
+ )
44
+ end
45
+
46
+ def online_customers_redis_key
47
+ self.class.online_customers_redis_key
48
+ end
49
+
50
+ def redis
51
+ self.class.redis
52
+ end
53
+
54
+ module ClassMethods
55
+ def online_customers
56
+ where(id: online_customers_ids)
57
+ end
58
+
59
+ def online_customers_ids
60
+ online_customer_list_in_redis&.map{|str| str.split("/").second.to_i}
61
+ end
62
+
63
+ def redis
64
+ @redis ||= Redis.current
65
+ end
66
+
67
+ def online_customers_redis_key
68
+ "#{CustomerServiceIm.appid}_online_customers"
69
+ end
70
+
71
+ def online_customer_list_in_redis
72
+ redis.smembers online_customers_redis_key
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,49 @@
1
+ module CustomerServiceIm
2
+ class Conversation < ApplicationRecord
3
+ has_many :messages
4
+ belongs_to :user, polymorphic: true
5
+ belongs_to :staff
6
+
7
+ validates_presence_of :title
8
+
9
+ def last_message
10
+ messages.last
11
+ end
12
+
13
+ def message_created!(message)
14
+ with_lock do
15
+ increment_unread_message_count(message)
16
+ update_last_messaged_on(message)
17
+ broadcast_to_channel
18
+
19
+ save!
20
+ end
21
+ end
22
+
23
+ def all_messages_read!
24
+ messages.unread.includes(:user).update(state: :read)
25
+ update(staff_unread_message_count: 0)
26
+ broadcast_to_channel
27
+ end
28
+
29
+ def broadcast_to_channel
30
+ conversation_data = JSON.parse(ApplicationController.render partial: "customer_service_im/conversations/conversation", locals: { conversation: self })
31
+ ActionCable.server.broadcast "customer_service_im/conversations_channel", conversation_data
32
+ end
33
+
34
+ # @param [CustomerServiceIm::Message] message
35
+ def increment_unread_message_count(message)
36
+ if message.from_user?
37
+ increment(:staff_unread_message_count)
38
+ elsif message.from_staff?
39
+ increment(:user_unread_message_count)
40
+ else
41
+ # never arrived
42
+ end
43
+ end
44
+
45
+ def update_last_messaged_on(message)
46
+ self.last_messaged_on = message.created_at
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,103 @@
1
+ module CustomerServiceIm
2
+ class Message < ApplicationRecord
3
+ belongs_to :user, polymorphic: true
4
+ belongs_to :conversation
5
+ belongs_to :staff, optional: true
6
+
7
+ before_validation :set_default_receiver_type, on: :create
8
+ before_validation :set_user, on: :create
9
+
10
+ after_create :broadcast_to_conversation #, if: -> { target_receivable? }
11
+ after_create :notice_conversation_message_created
12
+
13
+ after_create_commit :speech_recognize, if: ->{ from_user? && speech_unrecognized? }
14
+
15
+ has_one_attached :attachment
16
+
17
+ scope :from_user, -> {
18
+ where("receiver_type = 'Staff'")
19
+ }
20
+
21
+ scope :from_staff, -> {
22
+ where("receiver_type = 'User'")
23
+ }
24
+
25
+ scope :id_less_then, ->( id ){
26
+ where('id < ?', id) if id.present?
27
+ }
28
+
29
+ scope :unread, ->{
30
+ where.not(state: "read")
31
+ }
32
+
33
+ enum state: {
34
+ pending: "pending",
35
+ sent: "sent",
36
+ received: "received",
37
+ read: "read"
38
+ }, _default: "pending"
39
+
40
+ def from_user?
41
+ receiver_type === 'Staff'
42
+ end
43
+
44
+ def from_staff?
45
+ receiver_type === 'User'
46
+ end
47
+
48
+ def target_receivable?
49
+ (from_user? && conversation.staff&.online?) || (from_staff? && user&.online?)
50
+ end
51
+
52
+ def broadcast_to_conversation
53
+ message_data = JSON.parse(ApplicationController.render partial: "customer_service_im/messages/message", locals: { message: self})
54
+ CustomerServiceIm::MessagesChannel.broadcast_to(conversation, message_data)
55
+
56
+ if target_receivable?
57
+ sent!
58
+ end
59
+ end
60
+
61
+ def read!
62
+ conversation.message_read!(self)
63
+ super
64
+ end
65
+
66
+ def attachment_url
67
+ return if !attachment.blob
68
+
69
+ Rails.application.routes.url_helpers.rails_blob_url(attachment)
70
+ end
71
+
72
+ def speech_unrecognized?
73
+ attachment.blob.present? && content.blank?
74
+ end
75
+
76
+ def speech_recognize
77
+ result = TxAsr::SentenceRecognition.from(attachment_url)
78
+
79
+ if result.success?
80
+ update( content: result.data["Result"].presence || "[空白语音]" )
81
+ broadcast_to_conversation
82
+ else
83
+ logger = Logger.new Rails.root.join("log/speech_recognition.log")
84
+ logger.error result.errors
85
+ raise TsrRecognizeError.new(result.errors)
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def set_default_receiver_type
92
+ self.receiver_type ||= "Staff"
93
+ end
94
+
95
+ def notice_conversation_message_created
96
+ conversation.message_created!(self)
97
+ end
98
+
99
+ def set_user
100
+ self.user ||= conversation.user
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,63 @@
1
+ module CustomerServiceIm
2
+ class Staff < ApplicationRecord
3
+ has_many :conversations
4
+ has_many :messages
5
+
6
+ validates_presence_of :name
7
+
8
+ def join_conversation(conversation)
9
+ conversation.update(staff: self)
10
+
11
+ conversation.messages.from_user.each do |message|
12
+ message.update(staff: self)
13
+ message.broadcast_to_conversation
14
+ end
15
+ end
16
+
17
+ def self.online_staffs
18
+ where(id: online_staffs_ids)
19
+ end
20
+
21
+ def online!
22
+ redis.sadd self.class.online_staff_redis_key, as_redis_record
23
+ end
24
+
25
+ def offline!
26
+ redis.srem self.class.online_staff_redis_key, as_redis_record
27
+ end
28
+
29
+ def online?
30
+ id.in? self.class.online_staffs_ids
31
+ end
32
+
33
+ def offline?
34
+ !online?
35
+ end
36
+
37
+ private
38
+
39
+ def self.online_staffs_ids
40
+ online_staff_list_in_redis&.map{|str| str.split("/").second.to_i}
41
+ end
42
+
43
+ def self.online_staff_list_in_redis
44
+ redis.smembers online_staff_redis_key
45
+ end
46
+
47
+ def self.online_staff_redis_key
48
+ "#{CustomerServiceIm.appid}_online_staffs"
49
+ end
50
+
51
+ def self.redis
52
+ @redis ||= Redis.current
53
+ end
54
+
55
+ def as_redis_record
56
+ "#{self.class.name}/#{self.id}"
57
+ end
58
+
59
+ def redis
60
+ self.class.redis
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+
2
+ json.extract! conversation, *CustomerServiceIm::Conversation.column_names
3
+
4
+ json.user conversation.user
5
+ json.staff conversation.staff
6
+
7
+ if conversation.last_message
8
+ json.last_message do
9
+ json.extract! conversation.last_message, *CustomerServiceIm::Message.column_names
10
+
11
+ created_at = if conversation.last_message.created_at
12
+ conversation.last_message.created_at.strftime("%H:%M")
13
+ else
14
+ conversation.last_message.created_at.strftime("%m.%d")
15
+ end
16
+
17
+ json.created_at created_at
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+
2
+ json.extract! message, *CustomerServiceIm::Message.column_names
3
+ json.attachment_url message.attachment_url
@@ -0,0 +1,5 @@
1
+ json.total_count @total_count
2
+
3
+ json.conversations do
4
+ json.array! @conversations, partial: 'conversation', as: :conversation
5
+ end
@@ -0,0 +1,3 @@
1
+ json.messages do
2
+ json.array! @messages, partial: "message", as: :message
3
+ end
@@ -0,0 +1,14 @@
1
+
2
+ json.extract! message, *CustomerServiceIm::Message.column_names
3
+
4
+ json.user message.user
5
+ json.staff message.staff
6
+ json.attachment_url message.attachment_url
7
+
8
+ created_at = if message.created_at.today?
9
+ message.created_at.strftime("%H:%M")
10
+ else
11
+ message.created_at.strftime("%m-%d")
12
+ end
13
+
14
+ json.created_at created_at
@@ -0,0 +1 @@
1
+ json.array! @messages, partial: "message", as: :message
data/config/cable.yml ADDED
@@ -0,0 +1,10 @@
1
+ development:
2
+ adapter: redis
3
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
4
+ test:
5
+ adapter: test
6
+
7
+ production:
8
+ adapter: redis
9
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10
+ channel_prefix: im_production
@@ -0,0 +1,28 @@
1
+
2
+ TxAsr.setup do |config|
3
+
4
+ # TODO 需要将 secret_key 和 secret_id 改为你实际项目中的配置
5
+ config.secret_key = "JL6amhMkqfDVWGjuoRp1DI8j48wTnunQ"
6
+ config.secret_id = "AKIDta1B5i49yXpqb5HCqELBk35qJWS7VcS1"
7
+ config.region = "ap-guangzhou"
8
+
9
+
10
+ # 声音格式: 支持 mp3 和 wav
11
+ config.voice_format = "mp3"
12
+
13
+ # 可在腾讯云后台查看。如果没有新建项目,那就是使用的默认项目,默认项目 id 为 0
14
+ config.project_id = 0
15
+
16
+ # API 版本号,截止 gem 发布前,下面的值为文档给出的值
17
+ config.version = "2019-06-14"
18
+
19
+ # source type —— 语音来源: 可选 0: url, 1: post body
20
+ config.source_type = 0
21
+
22
+ # 过滤语气词: 0 不过滤,1 部分过滤,2 严格过滤
23
+ config.filter_model = 1
24
+
25
+ # 默认声道数: 1 为单声道,2 为双声道,默认为单声道
26
+ # config.channel_num = 1
27
+
28
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,23 @@
1
+ CustomerServiceIm::Engine.routes.draw do
2
+ mount ActionCable.server => "/cable"
3
+
4
+ resources :conversations, defaults: { format: :json } do
5
+ member do
6
+ get :messages
7
+ put :all_messages_read
8
+ end
9
+ end
10
+
11
+ resources :messages, defaults: { format: :json } do
12
+ member do
13
+ put :receive
14
+ patch :receive
15
+ put :read
16
+ end
17
+
18
+ collection do
19
+ post :create
20
+ post :reply
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ class CreateCustomerServiceImStaffs < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :customer_service_im_staffs do |t|
4
+ t.string :name
5
+ t.string :state
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ class CreateCustomerServiceImConversations < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :customer_service_im_conversations do |t|
4
+ t.string :title
5
+ t.string :state
6
+ t.integer :staff_id
7
+ t.references :user, polymorphic: true, null: false
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCustomerServiceImMessages < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :customer_service_im_messages do |t|
4
+ t.string :content
5
+ t.string :receiver_type
6
+ t.references :user, polymorphic: true, null: false
7
+ t.integer :staff_id
8
+ t.integer :conversation_id
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class AddStateToCustomerServiceImMessages < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_column :customer_service_im_messages, :state, :string
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddLastMessagedOnToCustomerServiceImConversations < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_column :customer_service_im_conversations, :last_messaged_on, :datetime
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddUnreadMessageCountToCustomerServiceImConversations < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_column :customer_service_im_conversations, :user_unread_message_count, :integer
4
+ add_column :customer_service_im_conversations, :staff_unread_message_count, :integer
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+
2
+ String.class_eval do
3
+
4
+ # convert string like "User:1" to User.find(1)
5
+ def to_record
6
+ if self.match(/^[a-zA-Z]+\/\d+$/)
7
+ data = self.split(":")
8
+ data[0].classify.constantize.find(data[1])
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module CustomerServiceIm
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CustomerServiceIm
4
+ config.generators.api_only = true
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module CustomerServiceIm
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,18 @@
1
+ require "customer_service_im/version"
2
+ require "customer_service_im/engine"
3
+ require "customer_service_im/core_ext"
4
+ require 'shoulda'
5
+ require "jwt"
6
+ require 'jbuilder'
7
+ require "tx_asr"
8
+
9
+ module CustomerServiceIm
10
+ # Your code goes here...
11
+ mattr_accessor :appid, :app_secret, :jwt_secret
12
+
13
+ def self.setup
14
+ if block_given?
15
+ yield self
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate install Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,7 @@
1
+ class CustomerServiceIm::InstallGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ def copy_initializer
5
+ copy_file "initializer.rb", "config/initializers/customer_service_im.rb"
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+
2
+ CustomerServiceIm.setup do |config|
3
+ config.jwt_secret = Rails.application.secret_key_base
4
+ config.appid = "xxx"
5
+ config.app_secret = "x1x"
6
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :customer_service_im do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,2 @@
1
+
2
+ class TsrRecognizeError < StandardError; end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: customer_service_im
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.1.4
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.1.4.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.1.4
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.1.4.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: shoulda
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '4.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: jwt
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: kaminari-activerecord
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: jbuilder
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: tx_asr
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description: Description of CustomerServiceIm.
104
+ email:
105
+ - ianlynxk@gmail.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - MIT-LICENSE
111
+ - README.md
112
+ - Rakefile
113
+ - app/channels/application_cable/channel.rb
114
+ - app/channels/application_cable/connection.rb
115
+ - app/channels/concerns/customer_service_im/connection.rb
116
+ - app/channels/customer_service_im/conversations_channel.rb
117
+ - app/channels/customer_service_im/messages_channel.rb
118
+ - app/controllers/customer_service_im/application_controller.rb
119
+ - app/controllers/customer_service_im/conversations_controller.rb
120
+ - app/controllers/customer_service_im/messages_controller.rb
121
+ - app/jobs/customer_service_im/application_job.rb
122
+ - app/mailers/customer_service_im/application_mailer.rb
123
+ - app/models/customer_service_im/application_record.rb
124
+ - app/models/customer_service_im/concerns/customer.rb
125
+ - app/models/customer_service_im/conversation.rb
126
+ - app/models/customer_service_im/message.rb
127
+ - app/models/customer_service_im/staff.rb
128
+ - app/views/customer_service_im/conversations/_conversation.json.jbuilder
129
+ - app/views/customer_service_im/conversations/_message.json.jbuilder
130
+ - app/views/customer_service_im/conversations/index.json.jbuilder
131
+ - app/views/customer_service_im/conversations/messages.json.jbuilder
132
+ - app/views/customer_service_im/messages/_message.json.jbuilder
133
+ - app/views/customer_service_im/messages/index.json.jbuilder
134
+ - config/cable.yml
135
+ - config/initializers/tx_asr.rb
136
+ - config/routes.rb
137
+ - db/migrate/20210927065745_create_customer_service_im_staffs.rb
138
+ - db/migrate/20210927074031_create_customer_service_im_conversations.rb
139
+ - db/migrate/20210927074557_create_customer_service_im_messages.rb
140
+ - db/migrate/20210928084420_add_state_to_customer_service_im_messages.rb
141
+ - db/migrate/20210928085514_add_last_messaged_on_to_customer_service_im_conversations.rb
142
+ - db/migrate/20210930025215_add_unread_message_count_to_customer_service_im_conversations.rb
143
+ - lib/customer_service_im.rb
144
+ - lib/customer_service_im/core_ext.rb
145
+ - lib/customer_service_im/engine.rb
146
+ - lib/customer_service_im/version.rb
147
+ - lib/generators/customer_service_im/install/USAGE
148
+ - lib/generators/customer_service_im/install/install_generator.rb
149
+ - lib/generators/customer_service_im/install/templates/initializer.rb
150
+ - lib/tasks/customer_service_im_tasks.rake
151
+ - lib/tsr_recognize_error.rb
152
+ homepage: https://github.com/bkyz/customer_service_im
153
+ licenses:
154
+ - MIT
155
+ metadata:
156
+ homepage_uri: https://github.com/bkyz/customer_service_im
157
+ source_code_uri: https://github.com/bkyz/customer_service_im
158
+ changelog_uri: https://github.com/bkyz/customer_service_im/CHANGELOG.md
159
+ post_install_message:
160
+ rdoc_options: []
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ requirements: []
174
+ rubygems_version: 3.2.15
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: Summary of CustomerServiceIm.
178
+ test_files: []