From b9c4c2cb0cfdb321c351c826c2b8ed5655088e12 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 19 Jul 2021 14:53:55 -0500 Subject: [PATCH] Notify admin if a user goes over 500 messages in 30 days Notify only once per day (using expiring redis key). Can notify MUC or user, always sends directed presence first so it will join MUC if not joined. Ignore all messages direct to the component, mostly to throw out live messages from MUC if we join that to notify. --- config.dhall.sample | 1 + lib/customer.rb | 2 +- lib/customer_usage.rb | 30 +++++++++++++++++++++++++++++- sgx_jmp.rb | 33 +++++++++++++++++++++------------ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/config.dhall.sample b/config.dhall.sample index 40a1cc5..9b87d0a 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -39,6 +39,7 @@ xep0157 = [ { var = "support-addresses", value = "xmpp:+14169938000@cheogram.com" } ], + notify_admin = "muc_or_user@example.com", sip_host = "sip.jmp.chat", plans = [ { diff --git a/lib/customer.rb b/lib/customer.rb index 48dc8b1..7af60af 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -49,7 +49,7 @@ class Customer def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan, :currency, :merchant_account, :plan_name def_delegators :@sgx, :register!, :registered? - def_delegator :@usage, :report, :usage_report + def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage def initialize( customer_id, diff --git a/lib/customer_usage.rb b/lib/customer_usage.rb index b43dc8e..7da46de 100644 --- a/lib/customer_usage.rb +++ b/lib/customer_usage.rb @@ -7,7 +7,7 @@ class CustomerUsage @customer_id = customer_id end - def report(range) + def usage_report(range) EMPromise.all([ messages_by_day(range), minutes_by_day(range) @@ -16,6 +16,34 @@ class CustomerUsage end end + def expire_message_usage + today = Time.now.utc.to_date + REDIS.zremrangebylex( + "jmp_customer_outbound_messages-#{@customer_id}", + "-", + # Store message counts per day for 1 year + "[#{(today << 12).strftime('%Y%m%d')}" + ) + end + + def incr_message_usage(amount=1) + today = Time.now.utc.to_date + EMPromise.all([ + expire_message_usage, + REDIS.zincrby( + "jmp_customer_outbound_messages-#{@customer_id}", + amount, + today.strftime("%Y%m%d") + ) + ]) + end + + def message_usage(range) + messages_by_day(range).then do |by_day| + by_day.values.sum + end + end + def messages_by_day(range) EMPromise.all(range.first.downto(range.last).map { |day| REDIS.zscore( diff --git a/sgx_jmp.rb b/sgx_jmp.rb index bda5f96..7f7c0c3 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -163,28 +163,37 @@ before nil, to: /\Acustomer_/, from: /(\A|@)#{CONFIG[:sgx]}(\/|\Z)/ do |s| halt end +# Ignore messages to component +# Especially if we have the component join MUC for notifications +message(to: /\A#{CONFIG[:component][:jid]}\Z/) {} + message do |m| sentry_hub = new_sentry_hub(m, name: "message") + today = Time.now.utc.to_date Customer.for_jid(m.from.stripped).then { |customer| sentry_hub.current_scope.set_user( id: customer.customer_id, jid: m.from.stripped.to_s ) - today = Time.now.utc.to_date EMPromise.all([ - REDIS.zremrangebylex( - "jmp_customer_outbound_messages-#{customer.customer_id}", - "-", - # Store message counts per day for 1 year - "[#{(today << 12).strftime('%Y%m%d')}" - ), - REDIS.zincrby( - "jmp_customer_outbound_messages-#{customer.customer_id}", - 1, - today.strftime("%Y%m%d") - ), + customer, + customer.incr_message_usage, + REDIS.exists("jmp_usage_notify-#{customer.customer_id}"), customer.stanza_from(m) ]) + }.then { |(customer, _, already, _)| + next if already == 1 + + customer.message_usage((today..(today - 30))).then do |usage| + next unless usage > 500 + + BLATHER.join(CONFIG[:notify_admin], "sgx-jmp") + BLATHER.say( + CONFIG[:notify_admin], + "#{customer.customer_id} has used #{usage} messages since #{today - 30}" + ) + REDIS.set("jmp_usage_notify-#{customer.customer_id}", ex: 60 * 60 * 24) + end }.catch { |e| panic(e, sentry_hub) } end -- 2.45.2