From ccbc41428b7e41598b8010d333cdca0ec0492998 Mon Sep 17 00:00:00 2001 From: Tosin Sonuyi <13176059+weallwegot@users.noreply.github.com> Date: Mon, 11 May 2020 15:10:37 -0400 Subject: [PATCH] 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 --- config/locales/client.en.yml | 12 +++ config/locales/server.en.yml | 15 ++++ config/settings.yml | 13 ++++ lib/discourse_chat/provider.rb | 1 + .../provider/groupme/groupme_provider.rb | 78 +++++++++++++++++++ .../groupme/groupme_provider_spect.rb | 29 +++++++ 6 files changed, 148 insertions(+) create mode 100644 lib/discourse_chat/provider/groupme/groupme_provider.rb create mode 100644 spec/lib/discourse_chat/provider/groupme/groupme_provider_spect.rb diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0659168..d9943f0 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -189,3 +189,15 @@ en: flow_token: title: "Flow Token" 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" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2775f88..860e5d2 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -78,6 +78,14 @@ en: chat_integration_flowdock_enabled: "Enable the Flowdock chat integration provider" 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: all_categories: "(all categories)" @@ -252,3 +260,10 @@ en: ####################################### flowdock: message_title: "posted" + + + ####################################### + ########### GROUPME STRINGS ########## + ####################################### + groupme: + message_title: "posted" \ No newline at end of file diff --git a/config/settings.yml b/config/settings.yml index c402d19..41f4c0a 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -117,3 +117,16 @@ chat_integration: default: false chat_integration_flowdock_excerpt_length: 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: '' \ No newline at end of file diff --git a/lib/discourse_chat/provider.rb b/lib/discourse_chat/provider.rb index 33e6b8e..9a38050 100644 --- a/lib/discourse_chat/provider.rb +++ b/lib/discourse_chat/provider.rb @@ -100,3 +100,4 @@ require_relative "provider/zulip/zulip_provider" require_relative "provider/rocketchat/rocketchat_provider" require_relative "provider/gitter/gitter_provider" require_relative "provider/flowdock/flowdock_provider" +require_relative "provider/groupme/groupme_provider" diff --git a/lib/discourse_chat/provider/groupme/groupme_provider.rb b/lib/discourse_chat/provider/groupme/groupme_provider.rb new file mode 100644 index 0000000..03213da --- /dev/null +++ b/lib/discourse_chat/provider/groupme/groupme_provider.rb @@ -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 diff --git a/spec/lib/discourse_chat/provider/groupme/groupme_provider_spect.rb b/spec/lib/discourse_chat/provider/groupme/groupme_provider_spect.rb new file mode 100644 index 0000000..19497a8 --- /dev/null +++ b/spec/lib/discourse_chat/provider/groupme/groupme_provider_spect.rb @@ -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