Eric-Guo/wechat

View on GitHub
lib/wechat/api_loader.rb

Summary

Maintainability
C
1 day
Test Coverage
C
73%
# frozen_string_literal: true

require 'active_support/core_ext/object/blank'

module Wechat
  module ApiLoader
    def self.with(options)
      account = options[:account] || :default
      c = ApiLoader.config(account)

      token_file = options[:token_file] || c.access_token.presence || '/var/tmp/wechat_access_token'
      js_token_file = options[:js_token_file] || c.jsapi_ticket.presence || '/var/tmp/wechat_jsapi_ticket'
      type = options[:type] || c.type

      network_setting = Wechat::NetworkSetting.new(c.timeout, c.skip_verify_ssl, c.proxy_url, c.proxy_port, c.proxy_username, c.proxy_password)
      if c.appid && c.secret && token_file.present?
        if type == 'mp'
          qcloud_env = options[:qcloud_env] || c.qcloud_env
          qcloud_token_file = options[:qcloud_token_file] || c.qcloud_token_file.presence || '/var/tmp/qcloud_access_token'
          qcloud_token_lifespan = options[:qcloud_token_lifespan] || c.qcloud_token_lifespan
          qcloud_setting = Wechat::Qcloud::Setting.new(qcloud_env, qcloud_token_file, qcloud_token_lifespan)
          Wechat::MpApi.new(c.appid, c.secret, token_file, network_setting, js_token_file, qcloud_setting)
        else
          Wechat::Api.new(c.appid, c.secret, token_file, network_setting, js_token_file)
        end
      elsif c.corpid && c.corpsecret && token_file.present?
        Wechat::CorpApi.new(c.corpid, c.corpsecret, token_file, c.agentid, network_setting, js_token_file)
      else
        raise 'Need create ~/.wechat.yml with wechat appid and secret or running at rails root folder so wechat can read config/wechat.yml'
      end
    end

    @configs = nil

    def self.config(account = :default)
      account = :default if account.nil?
      @configs ||= loading_config!
      @configs[account.to_sym] || raise("Wechat configuration for #{account} is missing.")
    end

    def self.reload_config!
      @configs = loading_config!
    end

    def self.load_yaml(result)
      if YAML.respond_to?(:unsafe_load)
        YAML.unsafe_load(result)
      else
        begin
          YAML.safe_load(result, aliases: true)
        rescue ArgumentError
          YAML.safe_load(result)
        end
      end
    end

    private_class_method def self.loading_config!
      configs = config_from_file || config_from_environment
      configs.merge!(config_from_db)

      configs.transform_keys! do |key|
        key.to_sym
      rescue StandardError
        key
      end
      configs.each do |key, cfg|
        raise "wrong wechat configuration format for #{key}" unless cfg.is_a?(Hash)

        cfg.transform_keys! do |sub_key|
          sub_key.to_sym
        rescue StandardError
          sub_key
        end
      end

      if defined?(::Rails)
        configs.each_value do |cfg|
          cfg[:access_token] ||= Rails.root.try(:join, 'tmp/access_token').try(:to_path)
          cfg[:jsapi_ticket] ||= Rails.root.try(:join, 'tmp/jsapi_ticket').try(:to_path)
          cfg[:qcloud_token] ||= Rails.root.try(:join, 'tmp/qcloud_token').try(:to_path)
        end
      end

      configs.each_value do |cfg|
        cfg[:timeout] ||= 20
        cfg[:qcloud_token_lifespan] ||= 7200
        cfg[:have_session_class] ||= class_exists?('WechatSession')
        cfg[:oauth2_cookie_duration] ||= 3600 # 1 hour
      end

      # create config object using raw config data
      cfg_objs = {}
      configs.each do |account, cfg|
        cfg_objs[account] = OpenStruct.new(cfg)
      end
      cfg_objs
    end

    private_class_method def self.config_from_db
      return {} unless class_exists?('WechatConfig')

      environment = defined?(::Rails) ? Rails.env.to_s : ENV.fetch('RAILS_ENV', 'development')
      WechatConfig.get_all_configs(environment)
    end

    private_class_method def self.config_from_file
      if defined?(::Rails)
        config_file = ENV.fetch('WECHAT_CONF_FILE') { Rails.root.join('config', 'wechat.yml') }
        resolve_config_file(config_file, Rails.env.to_s)
      else
        require 'erb'
        rails_config_file = ENV.fetch('WECHAT_CONF_FILE') { File.join(Dir.getwd, 'config', 'wechat.yml') }
        application_config_file = File.join(Dir.getwd, 'config', 'application.yml')
        home_config_file = File.join(Dir.home, '.wechat.yml')
        if File.exist?(rails_config_file)
          rails_env = ENV.fetch('RAILS_ENV', 'development')
          if File.exist?(application_config_file) && !defined?(::Figaro)
            require 'figaro'
            Figaro::Application.new(path: application_config_file, environment: rails_env).load
          end
          config = resolve_config_file(rails_config_file, rails_env)
          if config.present? && (default = config[:default]) && (default['appid'] || default['corpid'])
            puts "Using rails project #{ENV.fetch('WECHAT_CONF_FILE', 'config/wechat.yml')} #{rails_env} setting..."
            return config
          end
        end
        resolve_config_file(home_config_file, nil) if File.exist?(home_config_file)
      end
    end

    private_class_method def self.resolve_config_file(config_file, env)
      return unless File.exist?(config_file)

      begin
        raw_data = load_yaml(ERB.new(File.read(config_file)).result)
      rescue NameError
        puts "WARNING: If using 'Rails.application.credentials.wechat_secret!' in wechat.yml, you need run in 'rails c' and access via 'Wechat.api' or gem 'figaro' instead."
      end
      configs = {}
      if env
        # Process multiple accounts when env is given
        raw_data.each do |key, value|
          if key == env
            configs[:default] = value
          else
            m = /(.*?)_#{env}$/.match(key)
            configs[m[1].to_sym] = value if m
          end
        end
      else
        # Treat is as one account when env is omitted
        configs[:default] = raw_data
      end
      configs
    end

    private_class_method def self.config_from_environment
      value = { appid: ENV.fetch('WECHAT_APPID', nil),
                secret: ENV.fetch('WECHAT_SECRET', nil),
                corpid: ENV.fetch('WECHAT_CORPID', nil),
                corpsecret: ENV.fetch('WECHAT_CORPSECRET', nil),
                agentid: ENV.fetch('WECHAT_AGENTID', nil),
                token: ENV.fetch('WECHAT_TOKEN', nil),
                access_token: ENV.fetch('WECHAT_ACCESS_TOKEN', nil),
                encrypt_mode: ENV.fetch('WECHAT_ENCRYPT_MODE', nil),
                timeout: ENV.fetch('WECHAT_TIMEOUT', nil),
                skip_verify_ssl: ENV.fetch('WECHAT_SKIP_VERIFY_SSL', nil),
                proxy_url: ENV.fetch('WECHAT_PROXY_URL', nil),
                proxy_port: ENV.fetch('WECHAT_PROXY_PORT', nil),
                proxy_username: ENV.fetch('WECHAT_PROXY_USERNAME', nil),
                proxy_password: ENV.fetch('WECHAT_PROXY_PASSWORD', nil),
                encoding_aes_key: ENV.fetch('WECHAT_ENCODING_AES_KEY', nil),
                jsapi_ticket: ENV.fetch('WECHAT_JSAPI_TICKET', nil),
                qcloud_env: ENV.fetch('WECHAT_QCLOUD_ENV', nil),
                qcloud_token_file: ENV.fetch('WECHAT_QCLOUD_TOKEN', nil),
                qcloud_token_lifespan: ENV.fetch('WECHAT_QCLOUD_TOKEN_LIFESPAN', nil),
                trusted_domain_fullname: ENV.fetch('WECHAT_TRUSTED_DOMAIN_FULLNAME', nil) }
      { default: value }
    end

    private_class_method def self.class_exists?(class_name)
      klass = Module.const_get(class_name)
      klass.is_a?(Class)
    rescue NameError
      false
    end
  end
end