Files
leadchat/app/services/social_link_parser.rb
Shivam Mishra e5107604a0 feat: account enrichment using context.dev [UPM-27] (#13978)
## Account branding enrichment during signup

This PR does the following

### Replace Firecrawl with Context.dev

Switches the enterprise brand lookup from Firecrawl to Context.dev for
better data quality, built-in caching, and automatic filtering of
free/disposable email providers. The service interface changes from URL
to email input to match Context.dev's email endpoint. OSS still falls
back to basic HTML scraping with a normalized output shape across both
paths.

The enterprise path intentionally does not fall back to HTML scraping on
failure — speed matters more than completeness. We want the user on the
editable onboarding form fast, and a slow fallback scrape is worse than
letting them fill it in.

Requires `CONTEXT_DEV_API_KEY` in Super Admin → App Config. Without it,
falls back to OSS HTML scraping.

### Add job to enrich account details

After account creation, `Account::BrandingEnrichmentJob` looks up the
signup email and pre-fills the account name, colors, logos, social
links, and industry into `custom_attributes['brand_info']`.

The job signals completion via a short-lived Redis key (30s TTL) + an
ActionCable broadcast (`account.enrichment_completed`). The Redis key
lets the frontend distinguish "still running" from "finished with no
results."
2026-04-08 11:16:52 +05:30

66 lines
1.7 KiB
Ruby

module SocialLinkParser
extend ActiveSupport::Concern
SOCIAL_DOMAIN_MAP = {
whatsapp: %w[wa.me api.whatsapp.com],
line: %w[line.me],
facebook: %w[facebook.com fb.com fb.me],
instagram: %w[instagram.com],
telegram: %w[t.me telegram.me],
tiktok: %w[tiktok.com]
}.freeze
private
def extract_social_from_links(links)
handles = {}
SOCIAL_DOMAIN_MAP.each do |platform, domains|
handles[platform] = find_social_handle(links, platform, domains)
end
handles
end
def find_social_handle(links, platform, domains)
matching_links = links.select do |l|
uri = URI.parse(l)
domains.any? { |d| match_social_domain?(uri.host, d) }
rescue URI::InvalidURIError
false
end
matching_links.each do |link|
handle = parse_social_handle(platform, link)
return handle if handle.present?
end
nil
end
def match_social_domain?(host, domain)
return false if host.blank?
host == domain || host.end_with?(".#{domain}")
end
SHARE_PATH_PREFIXES = %w[sharer share intent dialog].freeze
def parse_social_handle(platform, link)
uri = URI.parse(link)
return extract_whatsapp_phone(uri) if platform == :whatsapp
handle = uri.path.to_s.delete_prefix('/').delete_suffix('/')
return nil if handle.blank?
return nil if SHARE_PATH_PREFIXES.any? { |prefix| handle.start_with?(prefix) }
handle.presence
rescue URI::InvalidURIError
nil
end
# wa.me/1234567890 or api.whatsapp.com/send?phone=1234567890
def extract_whatsapp_phone(uri)
phone = CGI.parse(uri.query.to_s)['phone']&.first
phone = uri.path.to_s.delete_prefix('/').delete_suffix('/') if phone.blank?
phone.presence&.gsub(/[^\d]/, '')
end
end