customer_service_im 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []