GroupMe added as Provider (#36)

* adds groupme configuration and relative

* first pass at groupme provider

* add group id site setting w/ english translation

* rework to use bots groupme api intstead of developer, no access tokens reqd

also should catch 404 response codes

* add strings to locale yml files for i18n

* better error handling for multi-bot case

* add channel param for separate Groupme instances, include name in errorbody

* bugfix for multi bot msg forwarding

this gives us the ability to treat diff groupme instances like slack channels, lots of people use them this way for better or worse. use case is certain category posts only go to a particular GM instance

* add spec for groupme provider

* fix channel param constraint

* specify channels by groupme name, not bot api token

* fix some linting issues w/ spacing

* newline and trailing space lint fixes
This commit is contained in:
Tosin Sonuyi 2020-05-11 15:10:37 -04:00 committed by GitHub
parent ad3e73c964
commit ccbc41428b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 148 additions and 0 deletions

View File

@ -189,3 +189,15 @@ en:
flow_token: flow_token:
title: "Flow Token" title: "Flow Token"
help: "The flow token provided after creating a source for a flow into which you want to send messages." help: "The flow token provided after creating a source for a flow into which you want to send messages."
#######################################
########## GROUPME STRINGS ###########
#######################################
groupme:
title: "GroupMe"
param:
groupme_instance_name:
title: "GroupMe Instance Name"
help: "name of the Groupme instance as listed in Site Settings. use 'all' to send to all instances"
errors:
not_found: "The path you attempted to post your message to was not found. Check the Bot ID in Site Settings."
instance_names_issue: "instance names incorrectly formatted or not provided"

View File

@ -78,6 +78,14 @@ en:
chat_integration_flowdock_enabled: "Enable the Flowdock chat integration provider" chat_integration_flowdock_enabled: "Enable the Flowdock chat integration provider"
chat_integration_flowdock_excerpt_length: "Flowdock post excerpt length" chat_integration_flowdock_excerpt_length: "Flowdock post excerpt length"
#######################################
########## GROUPME SETTINGS ##########
#######################################
chat_integration_groupme_enabled: "Enable the Groupme chat integration provider"
chat_integration_groupme_excerpt_length: "Groupme post excerpt length"
chat_integration_groupme_bot_ids: "*required* Bot IDs, seperated by ',' if there are multiple"
chat_integration_groupme_instance_names: " *required* Name of the GroupMe chat, seperated by ',' if there are multiple (same order as Bot IDs)"
chat_integration: chat_integration:
all_categories: "(all categories)" all_categories: "(all categories)"
@ -252,3 +260,10 @@ en:
####################################### #######################################
flowdock: flowdock:
message_title: "posted" message_title: "posted"
#######################################
########### GROUPME STRINGS ##########
#######################################
groupme:
message_title: "posted"

View File

@ -117,3 +117,16 @@ chat_integration:
default: false default: false
chat_integration_flowdock_excerpt_length: chat_integration_flowdock_excerpt_length:
default: 400 default: 400
#######################################
########## GROUPME SETTINGS ##########
#######################################
chat_integration_groupme_enabled:
default: false
chat_integration_groupme_excerpt_length:
default: 400
chat_integration_groupme_bot_ids:
default: ''
chat_integration_groupme_instance_names:
default: ''

View File

@ -100,3 +100,4 @@ require_relative "provider/zulip/zulip_provider"
require_relative "provider/rocketchat/rocketchat_provider" require_relative "provider/rocketchat/rocketchat_provider"
require_relative "provider/gitter/gitter_provider" require_relative "provider/gitter/gitter_provider"
require_relative "provider/flowdock/flowdock_provider" require_relative "provider/flowdock/flowdock_provider"
require_relative "provider/groupme/groupme_provider"

View File

@ -0,0 +1,78 @@
# frozen_string_literal: true
module DiscourseChat::Provider::GroupmeProvider
PROVIDER_NAME = "groupme".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_groupme_enabled
CHANNEL_PARAMETERS = [
{ key: "groupme_instance_name", regex: '[\s\S]*', unique: true }
]
def self.generate_groupme_message(post)
display_name = "@#{post.user.username}"
full_name = post.user.name || ""
if !(full_name.strip.empty?) && (full_name.strip.gsub(' ', '_').casecmp(post.user.username) != 0) && (full_name.strip.gsub(' ', '').casecmp(post.user.username) != 0)
display_name = "#{full_name} @#{post.user.username}"
end
topic = post.topic
category = ''
if topic.category&.uncategorized?
category = "[#{I18n.t('uncategorized_category_name')}]"
elsif topic.category
category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]"
end
pre_post_text = "#{display_name}: #{topic.title}(#{post.full_url}) #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}"
data = {
text: "#{pre_post_text} - #{post.excerpt(SiteSetting.chat_integration_groupme_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)}"
}
data
end
def self.send_via_webhook(message, channel)
# loop through all the bot IDs
last_error_raised = nil
num_errors = 0
# split on commas, but remove leading/trailing spaces
bot_ids = SiteSetting.chat_integration_groupme_bot_ids.split(/\s*,\s*/)
instance_names = SiteSetting.chat_integration_groupme_instance_names.split(/\s*,\s*/)
unless instance_names.length() == bot_ids.length()
instance_names = [I18n.t('chat_integration.provider.groupme.errors.instance_names_issue')] * bot_ids.length()
end
name_to_id = Hash[instance_names.zip(bot_ids)]
user_input_channel = channel.data['groupme_instance_name'].strip
unless user_input_channel.eql? 'all'
instance_names = [user_input_channel]
end
instance_names.each { |instance_name|
bot_id = name_to_id["#{instance_name}"]
uri = URI("https://api.groupme.com/v3/bots/post")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
message[:bot_id] = bot_id
req.body = message.to_json
response = http.request(req)
unless response.kind_of? Net::HTTPSuccess
num_errors += 1
if response.code.to_s == '404'
error_key = 'chat_integration.provider.groupme.errors.not_found'
else
error_key = nil
end
last_error_raised = { error_key: error_key, groupme_name: instance_name, bot_id: bot_id, request: req.body, response_code: response.code, response_body: response.body }
end
}
if last_error_raised
successfully_sent = instance_names.length() - num_errors
last_error_raised[:success_rate] = "#{successfully_sent}/#{instance_names.length()}"
raise ::DiscourseChat::ProviderError.new info: last_error_raised
end
end
def self.trigger_notification(post, channel)
data_package = generate_groupme_message(post)
self.send_via_webhook(data_package, channel)
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe DiscourseChat::Provider::GroupmeProvider do
let(:post) { Fabricate(:post) }
describe '.trigger_notifications' do
before do
SiteSetting.chat_integration_groupme_enabled = true
SiteSetting.chat_integration_groupme_bot_ids = '1a2b3c4d5e6f7g'
end
let(:chan1) { DiscourseChat::Channel.create!(provider: 'groupme', data: { groupme_bot_id: '1a2b3c4d5e6f7g' }) }
it 'sends a request' do
stub1 = stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return(status: 200)
described_class.trigger_notification(post, chan1)
expect(stub1).to have_been_requested.once
end
it 'handles errors correctly' do
stub1 = stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return(status: 404, body: "{ \"error\": \"Not Found\"}")
expect(stub1).to have_been_requested.times(0)
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
expect(stub1).to have_been_requested.once
end
end
end