parent
							
								
									5a60702412
								
							
						
					
					
						commit
						cc9825e6a2
					
				@ -0,0 +1,6 @@
 | 
				
			||||
# Save as .codeclimate.yml (note leading .) in project root directory
 | 
				
			||||
languages:
 | 
				
			||||
  Ruby: true
 | 
				
			||||
 | 
				
			||||
exclude_paths:
 | 
				
			||||
- "lib/generators/wechat/templates/app/controllers/wechats_controller.rb"
 | 
				
			||||
@ -0,0 +1,17 @@
 | 
				
			||||
Documentation:
 | 
				
			||||
  Enabled: false
 | 
				
			||||
 | 
				
			||||
Metrics/LineLength:
 | 
				
			||||
  Max: 150
 | 
				
			||||
 | 
				
			||||
Metrics/AbcSize:
 | 
				
			||||
  Max: 37
 | 
				
			||||
 | 
				
			||||
Metrics/ClassLength:
 | 
				
			||||
  Max: 150
 | 
				
			||||
 | 
				
			||||
Metrics/MethodLength:
 | 
				
			||||
  Max: 15
 | 
				
			||||
 | 
				
			||||
Style/NumericLiterals:
 | 
				
			||||
  MinDigits: 7
 | 
				
			||||
@ -0,0 +1,22 @@
 | 
				
			||||
language: ruby
 | 
				
			||||
 | 
				
			||||
sudo: false
 | 
				
			||||
 | 
				
			||||
rvm:
 | 
				
			||||
  - 2.0.0
 | 
				
			||||
  - 2.1
 | 
				
			||||
  - 2.2
 | 
				
			||||
  - 2.3
 | 
				
			||||
 | 
				
			||||
matrix:
 | 
				
			||||
  allow_failures:
 | 
				
			||||
    - rvm: 2.3
 | 
				
			||||
 | 
				
			||||
install:
 | 
				
			||||
  - "travis_retry bundle config build.nokogiri --use-system-libraries"
 | 
				
			||||
  - "travis_retry bundle install --retry 3"
 | 
				
			||||
 | 
				
			||||
script: bundle exec rake
 | 
				
			||||
 | 
				
			||||
git:
 | 
				
			||||
  depth: 10
 | 
				
			||||
@ -0,0 +1,121 @@
 | 
				
			||||
# Changelog
 | 
				
			||||
 | 
				
			||||
## v0.7.1 (released at 1/11/2016)
 | 
				
			||||
 | 
				
			||||
* Fix after using http, upload file function break. #78
 | 
				
			||||
* Add callback function after_wechat_response support. by @zfben #79
 | 
				
			||||
* Should using department_id instead of departmentid at enterprise api: user_simplelist/user_list.
 | 
				
			||||
 | 
				
			||||
## v0.7.0 (released at 1/1/2016)
 | 
				
			||||
 | 
				
			||||
* Using [http](https://github.com/httprb/http) instead of rest-client for performance reason. (not support upload file yet)
 | 
				
			||||
 | 
				
			||||
## v0.6.9 (released at 1/6/2016)
 | 
				
			||||
 | 
				
			||||
* Fix token refresh bug on multi worker. #76
 | 
				
			||||
* Rewrite the token relative code to add more storage support in future.
 | 
				
			||||
 | 
				
			||||
## v0.6.8 (released at 12/25/2015)
 | 
				
			||||
 | 
				
			||||
* Support Rails 5.0.0.beta1.
 | 
				
			||||
* English README available
 | 
				
			||||
* Fix oauth2_url calling error, fix #75
 | 
				
			||||
 | 
				
			||||
## v0.6.7 (released at 12/18/2015)
 | 
				
			||||
 | 
				
			||||
* Add timeout configuration option, close #74
 | 
				
			||||
* New getuserinfo and oauth2_url to support getting FromUserName from web page.
 | 
				
			||||
 | 
				
			||||
## v0.6.6 (released at 12/15/2015)
 | 
				
			||||
 | 
				
			||||
* Add jsapi_ticket support for Enterprise Account
 | 
				
			||||
* Default generated WechatsController < ActionController::Base, as many Rails application may having #authenticate_user or #set_current_user in ApplicationController, so easily affect the first time using experience.
 | 
				
			||||
* New syntax `on :view, with: 'VIEW_URL'` support.
 | 
				
			||||
* New command `upload_replaceparty` which combine three sub command to make uploading department easier.
 | 
				
			||||
* New command `upload_replaceuser` which combine three sub command to make uploading user easier.
 | 
				
			||||
 | 
				
			||||
## v0.6.5 (released at 11/24/2015)
 | 
				
			||||
 | 
				
			||||
* Handle 48001 error if token is expire/not valid, close #71
 | 
				
			||||
* ApiLoader will do config reading and initialize the api instead of spreading the logic.
 | 
				
			||||
 | 
				
			||||
## v0.6.4 (released at 11/16/2015)
 | 
				
			||||
 | 
				
			||||
* Command mode now display different command set based on enterprise/public account setting
 | 
				
			||||
* Move config logic in command/wechat to ApiLoader class
 | 
				
			||||
* Unsubscribe can only reply plain text 'success' #68
 | 
				
			||||
* Fix 404 qrcode download problem, by @huangxiangdan #69
 | 
				
			||||
 | 
				
			||||
## v0.6.3 (released at 11/14/2015)
 | 
				
			||||
 | 
				
			||||
* Official testing and support public encrypt mode, also fix one cipher bug, many thanks to @hlltc #67
 | 
				
			||||
* hlltc report public account FILE_BASE no longer needs, clean code #67
 | 
				
			||||
* Media command line reflect recent Tecent json schema change. #67
 | 
				
			||||
 | 
				
			||||
## v0.6.2 (released at 11/05/2015)
 | 
				
			||||
 | 
				
			||||
* Tecent report location API changed, so change wechat gems also. #64
 | 
				
			||||
 | 
				
			||||
## v0.6.1 (released at 10/20/2015)
 | 
				
			||||
 | 
				
			||||
* Handle 40001, invalid credential, access_token is invalid or not latest hint # 57
 | 
				
			||||
* Support at Rails 4.2.1 wechat can not run #58
 | 
				
			||||
 | 
				
			||||
## v0.6.0 (released at 10/08/2015)
 | 
				
			||||
 | 
				
			||||
### Scan and Batch job are BREAK CHANGE!
 | 
				
			||||
 | 
				
			||||
* Scan 2D barcode using new syntax `on :scan, with: 'BINDING_QR_CODE' ` instead of `on :event, with: 'BINDING_QR_CODE' ` in previous version #55
 | 
				
			||||
  Which will fix can not using `on :event, with: "scan" ` problem
 | 
				
			||||
* Batch job using new syntax `on :batch_job, with: 'replace_user' `
 | 
				
			||||
instead of previous `on :event, with: 'replace_user' `. 
 | 
				
			||||
* Click menu support new syntax `on :click, with: 'BOOK_LUNCH' `, but `on :event, with: 'BOOK_LUNCH' ` still supported. perfer `on :click` because it running faster and more nature expression.
 | 
				
			||||
* Wechat::Responder using Hash for new :client and :batch_job event, avoid time consuming Array match responder
 | 
				
			||||
* Fix refresh token not working problem under ruby 2.0.0 #54
 | 
				
			||||
* New department_update, user_batchdelete, convert_to_openid API
 | 
				
			||||
 | 
				
			||||
## v0.5.0 (released at 9/25/2015)
 | 
				
			||||
 | 
				
			||||
* Only relay on activesupport on run time, so will greatly improve wechat cli startup time
 | 
				
			||||
* Add rails generator support `rails g wechat:install`
 | 
				
			||||
* Add batch job support for enterprise account like batch create user/department, both API, callback responder and CLI
 | 
				
			||||
* Add material management API and CLI
 | 
				
			||||
* Add tag API and CLI for enterprise account
 | 
				
			||||
* Add QR code scene function for public account
 | 
				
			||||
 | 
				
			||||
## v0.4.2 (released at 9/7/2015)
 | 
				
			||||
 | 
				
			||||
* Fix wrong number of arguments at Wechat::Responder.on by using arity #47
 | 
				
			||||
* Fix can not access wechat method after using instance level context.
 | 
				
			||||
* Fix skip_verify_ssl parameter error. 
 | 
				
			||||
 | 
				
			||||
## v0.4.1 (released at 9/6/2015)
 | 
				
			||||
 | 
				
			||||
* Limit news articles collection to 10, close #5
 | 
				
			||||
* Resolve the conflict with gem "responders" by @seamon #45
 | 
				
			||||
 | 
				
			||||
## v0.4.0 (released at 9/5/2015)
 | 
				
			||||
 | 
				
			||||
* Enable the verify SSL for enterprise mode by default, as security is more importent than speed, but still can switch off by configure
 | 
				
			||||
* Support scancode_push/scancode_waitmsg event.
 | 
				
			||||
* New API method can get wechat server IP list
 | 
				
			||||
* New API to query/create department/media/material
 | 
				
			||||
* Fix can not read token_file in mingw bug, which introduce at #43
 | 
				
			||||
 | 
				
			||||
## v0.3.0 (released at 8/30/2015)
 | 
				
			||||
 | 
				
			||||
* New user group management API
 | 
				
			||||
* Allow transfer to customer service on fallback. #42
 | 
				
			||||
* Read and write access_token properly using file locking, #43
 | 
				
			||||
 | 
				
			||||
## v0.2.0 (released at 8/27/2015)
 | 
				
			||||
 | 
				
			||||
* Add wechat enterprise account support
 | 
				
			||||
* Make responder execute in action context, by @lazing #15
 | 
				
			||||
* jsapi_ticket support, by @feitian124 #27
 | 
				
			||||
* Rename gems to wechat and ambitious to being #1 gems about development wechat. thanks Xiaoning transfer this gem name.
 | 
				
			||||
* Original gem `wechat-rails` author skinnyworm trasfer to Eric-Guo as maintainer
 | 
				
			||||
 | 
				
			||||
## v0.1.1
 | 
				
			||||
 | 
				
			||||
* Initial release from [wechat-rails](https://github.com/skinnyworm/wechat-rails).
 | 
				
			||||
@ -0,0 +1,11 @@
 | 
				
			||||
source 'https://rubygems.org'
 | 
				
			||||
 | 
				
			||||
gemspec
 | 
				
			||||
 | 
				
			||||
# jquery-rails is used by the dummy application
 | 
				
			||||
gem 'jquery-rails'
 | 
				
			||||
gem 'rake', '~> 10.4.2'
 | 
				
			||||
gem 'codeclimate-test-reporter', group: :test, require: nil
 | 
				
			||||
 | 
				
			||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
 | 
				
			||||
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
 | 
				
			||||
@ -0,0 +1,21 @@
 | 
				
			||||
The MIT License (MIT)
 | 
				
			||||
 | 
				
			||||
Copyright (c) 2014 skinnyworm
 | 
				
			||||
 | 
				
			||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||
of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||
in the Software without restriction, including without limitation the rights
 | 
				
			||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||
copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||
furnished to do so, subject to the following conditions:
 | 
				
			||||
 | 
				
			||||
The above copyright notice and this permission notice shall be included in all
 | 
				
			||||
copies or substantial portions of the Software.
 | 
				
			||||
 | 
				
			||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||
SOFTWARE.
 | 
				
			||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,27 @@
 | 
				
			||||
#!/usr/bin/env rake
 | 
				
			||||
begin
 | 
				
			||||
  require 'bundler/setup'
 | 
				
			||||
rescue LoadError
 | 
				
			||||
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
 | 
				
			||||
end
 | 
				
			||||
begin
 | 
				
			||||
  require 'rdoc/task'
 | 
				
			||||
rescue LoadError
 | 
				
			||||
  require 'rdoc/rdoc'
 | 
				
			||||
  require 'rake/rdoctask'
 | 
				
			||||
  RDoc::Task = Rake::RDocTask
 | 
				
			||||
end
 | 
				
			||||
 | 
				
			||||
RDoc::Task.new(:rdoc) do |rdoc|
 | 
				
			||||
  rdoc.rdoc_dir = 'rdoc'
 | 
				
			||||
  rdoc.title    = 'Wechat'
 | 
				
			||||
  rdoc.options << '--line-numbers'
 | 
				
			||||
  rdoc.rdoc_files.include('README.rdoc')
 | 
				
			||||
  rdoc.rdoc_files.include('lib/**/*.rb')
 | 
				
			||||
end
 | 
				
			||||
 | 
				
			||||
require File.join('bundler', 'gem_tasks')
 | 
				
			||||
require File.join('rspec', 'core', 'rake_task')
 | 
				
			||||
RSpec::Core::RakeTask.new(:spec)
 | 
				
			||||
 | 
				
			||||
task default: :spec
 | 
				
			||||
@ -0,0 +1 @@
 | 
				
			||||
0.7.1
 | 
				
			||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,37 @@
 | 
				
			||||
module ActionController
 | 
				
			||||
  module WechatResponder
 | 
				
			||||
    def wechat_responder(opts = {})
 | 
				
			||||
      include Wechat::Responder
 | 
				
			||||
 | 
				
			||||
      self.corpid = opts[:corpid] || Wechat.config.corpid
 | 
				
			||||
      self.agentid = opts[:agentid] || Wechat.config.agentid
 | 
				
			||||
      self.encrypt_mode = opts[:encrypt_mode] || Wechat.config.encrypt_mode || corpid.present?
 | 
				
			||||
      self.timeout = opts[:timeout] || 20
 | 
				
			||||
      self.skip_verify_ssl = opts[:skip_verify_ssl]
 | 
				
			||||
      self.token = opts[:token] || Wechat.config.token
 | 
				
			||||
      self.encoding_aes_key = opts[:encoding_aes_key] || Wechat.config.encoding_aes_key
 | 
				
			||||
 | 
				
			||||
      if opts.empty?
 | 
				
			||||
        self.wechat = Wechat.api
 | 
				
			||||
      else
 | 
				
			||||
        if corpid.present?
 | 
				
			||||
          self.wechat = Wechat::CorpApi.new(corpid, opts[:corpsecret], opts[:access_token], agentid, timeout, skip_verify_ssl, opts[:jsapi_ticket])
 | 
				
			||||
        else
 | 
				
			||||
          self.wechat = Wechat::Api.new(opts[:appid], opts[:secret], opts[:access_token], timeout, skip_verify_ssl, opts[:jsapi_ticket])
 | 
				
			||||
        end
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  if defined? Base
 | 
				
			||||
    class << Base
 | 
				
			||||
      include WechatResponder
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  if defined? API
 | 
				
			||||
    class << API
 | 
				
			||||
      include WechatResponder
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,32 @@
 | 
				
			||||
require 'rails/generators/active_record'
 | 
				
			||||
 | 
				
			||||
module Wechat
 | 
				
			||||
  module Generators
 | 
				
			||||
    class InstallGenerator < Rails::Generators::Base
 | 
				
			||||
      include ::Rails::Generators::Migration
 | 
				
			||||
 | 
				
			||||
      desc 'Install Wechat support files'
 | 
				
			||||
      source_root File.expand_path('../templates', __FILE__)
 | 
				
			||||
 | 
				
			||||
      def copy_config
 | 
				
			||||
        template 'config/wechat.yml'
 | 
				
			||||
      end
 | 
				
			||||
 | 
				
			||||
      def add_wechat_route
 | 
				
			||||
        route 'resource :wechat, only: [:show, :create]'
 | 
				
			||||
      end
 | 
				
			||||
 | 
				
			||||
      def copy_wechat_controller
 | 
				
			||||
        template 'app/controllers/wechats_controller.rb'
 | 
				
			||||
      end
 | 
				
			||||
 | 
				
			||||
      def copy_model_migration
 | 
				
			||||
        migration_template 'db/migration.rb', 'db/migrate/create_wechat_logs.rb'
 | 
				
			||||
      end
 | 
				
			||||
 | 
				
			||||
      def self.next_migration_number(dirname)
 | 
				
			||||
        ::ActiveRecord::Generators::Base.next_migration_number(dirname)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,123 @@
 | 
				
			||||
<% if  defined? ActionController::API -%>
 | 
				
			||||
class WechatsController < ApplicationController
 | 
				
			||||
<% else  -%>
 | 
				
			||||
class WechatsController < ActionController::Base
 | 
				
			||||
<% end  -%>
 | 
				
			||||
  wechat_responder
 | 
				
			||||
 | 
				
			||||
  # default text responder when no other match
 | 
				
			||||
  on :text do |request, content|
 | 
				
			||||
    request.reply.text "echo: #{content}" # Just echo
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When receive 'help', will trigger this responder
 | 
				
			||||
  on :text, with: 'help' do |request|
 | 
				
			||||
    request.reply.text 'help content'
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When receive '<n>news', will match and will got count as <n> as parameter
 | 
				
			||||
  on :text, with: /^(\d+) news$/ do |request, count|
 | 
				
			||||
    # Wechat article can only contain max 10 items, large than 10 will dropped.
 | 
				
			||||
    news = (1..count.to_i).each_with_object([]) { |n, memo| memo << { title: 'News title', content: "No. #{n} news content" } }
 | 
				
			||||
    request.reply.news(news) do |article, n, index| # article is return object
 | 
				
			||||
      article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: 'http://www.baidu.com/img/bdlogo.gif', url: 'http://www.baidu.com/'
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  on :event, with: 'subscribe' do |request|
 | 
				
			||||
    request.reply.text "#{request[:FromUserName]} subscribe now"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When unsubscribe user scan qrcode qrscene_xxxxxx to subscribe in public account
 | 
				
			||||
  # notice user will subscribe public account at same time, so wechat won't trigger subscribe event any more
 | 
				
			||||
  on :scan, with: 'qrscene_xxxxxx' do |request, ticket|
 | 
				
			||||
    request.reply.text "Unsubscribe user #{request[:FromUserName]} Ticket #{ticket}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When subscribe user scan scene_id in public account
 | 
				
			||||
  on :scan, with: 'scene_id' do |request, ticket|
 | 
				
			||||
    request.reply.text "Subscribe user #{request[:FromUserName]} Ticket #{ticket}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When no any on :scan responder can match subscribe user scaned scene_id
 | 
				
			||||
  on :event, with: 'scan' do |request|
 | 
				
			||||
    if request[:EventKey].present?
 | 
				
			||||
      request.reply.text "event scan got EventKey #{request[:EventKey]} Ticket #{request[:Ticket]}"
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When enterprise user press menu BINDING_QR_CODE and success to scan bar code
 | 
				
			||||
  on :scan, with: 'BINDING_QR_CODE' do |request, scan_result, scan_type|
 | 
				
			||||
    request.reply.text "User #{request[:FromUserName]} ScanResult #{scan_result} ScanType #{scan_type}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # Except QR code, wechat can also scan CODE_39 bar code in enterprise account
 | 
				
			||||
  on :scan, with: 'BINDING_BARCODE' do |message, scan_result|
 | 
				
			||||
    if scan_result.start_with? 'CODE_39,'
 | 
				
			||||
      message.reply.text "User: #{message[:FromUserName]} scan barcode, result is #{scan_result.split(',')[1]}"
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user click the menu button
 | 
				
			||||
  on :click, with: 'BOOK_LUNCH' do |request, key|
 | 
				
			||||
    request.reply.text "User: #{request[:FromUserName]} click #{key}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user view URL in the menu button
 | 
				
			||||
  on :view, with: 'http://wechat.somewhere.com/view_url' do |request, view|
 | 
				
			||||
    request.reply.text "#{request[:FromUserName]} view #{view}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user sent the imsage
 | 
				
			||||
  on :image do |request|
 | 
				
			||||
    request.reply.image(request[:MediaId]) # Echo the sent image to user
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user sent the voice
 | 
				
			||||
  on :voice do |request|
 | 
				
			||||
    request.reply.voice(request[:MediaId]) # Echo the sent voice to user
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user sent the video
 | 
				
			||||
  on :video do |request|
 | 
				
			||||
    nickname = wechat.user(request[:FromUserName])['nickname'] # Call wechat api to get sender nickname
 | 
				
			||||
    request.reply.video(request[:MediaId], title: 'Echo', description: "Got #{nickname} sent video") # Echo the sent video to user
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user sent location
 | 
				
			||||
  on :location do |request|
 | 
				
			||||
    request.reply.text("Latitude: #{message[:Latitude]} Longitude: #{message[:Longitude]} Precision: #{message[:Precision]}")
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  on :event, with: 'unsubscribe' do |request|
 | 
				
			||||
    request.reply.success # user can not receive this message
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When user enter the app / agent app
 | 
				
			||||
  on :event, with: 'enter_agent' do |request|
 | 
				
			||||
    request.reply.text "#{request[:FromUserName]} enter agent app now"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When batch job create/update user (incremental) finished.
 | 
				
			||||
  on :batch_job, with: 'sync_user' do |request, batch_job|
 | 
				
			||||
    request.reply.text "sync_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When batch job replace user (full sync) finished.
 | 
				
			||||
  on :batch_job, with: 'replace_user' do |request, batch_job|
 | 
				
			||||
    request.reply.text "replace_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When batch job invent user finished.
 | 
				
			||||
  on :batch_job, with: 'invite_user' do |request, batch_job|
 | 
				
			||||
    request.reply.text "invite_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # When batch job replace department (full sync) finished.
 | 
				
			||||
  on :batch_job, with: 'replace_party' do |request, batch_job|
 | 
				
			||||
    request.reply.text "replace_party job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  # Any not match above will fail to below
 | 
				
			||||
  on :fallback, respond: 'fallback message'
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,33 @@
 | 
				
			||||
default: &default
 | 
				
			||||
  corpid: "corpid"
 | 
				
			||||
  corpsecret: "corpsecret"
 | 
				
			||||
  agentid:  1
 | 
				
			||||
# Or if using public account, only need above two line
 | 
				
			||||
#  appid: "my_appid"
 | 
				
			||||
#  secret: "my_secret"
 | 
				
			||||
  token:    "my_token"
 | 
				
			||||
  access_token: "C:/Users/[username]/wechat_access_token"
 | 
				
			||||
  encrypt_mode: false # if true must fill encoding_aes_key
 | 
				
			||||
  encoding_aes_key:  "my_encoding_aes_key"
 | 
				
			||||
  jsapi_ticket: "C:/Users/[user_name]/wechat_jsapi_ticket"
 | 
				
			||||
 | 
				
			||||
production:
 | 
				
			||||
  corpid:     <%%= ENV['WECHAT_CORPID'] %>
 | 
				
			||||
  corpsecret: <%%= ENV['WECHAT_CORPSECRET'] %>
 | 
				
			||||
  agentid:    <%%= ENV['WECHAT_AGENTID'] %>
 | 
				
			||||
# Or if using public account, only need above two line
 | 
				
			||||
#  appid:      <%= ENV['WECHAT_APPID'] %>
 | 
				
			||||
#  secret:     <%= ENV['WECHAT_APP_SECRET'] %>
 | 
				
			||||
  token:      <%%= ENV['WECHAT_TOKEN'] %>
 | 
				
			||||
  timeout:    30,
 | 
				
			||||
  skip_verify_ssl: true
 | 
				
			||||
  access_token:  <%%= ENV['WECHAT_ACCESS_TOKEN'] %>
 | 
				
			||||
  encrypt_mode: false # if true must fill encoding_aes_key
 | 
				
			||||
  encoding_aes_key:  <%%= ENV['WECHAT_ENCODING_AES_KEY'] %>
 | 
				
			||||
  jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
 | 
				
			||||
 | 
				
			||||
development:
 | 
				
			||||
  <<: *default
 | 
				
			||||
 | 
				
			||||
test:
 | 
				
			||||
  <<: *default
 | 
				
			||||
@ -0,0 +1,11 @@
 | 
				
			||||
class CreateWechatLogs < ActiveRecord::Migration
 | 
				
			||||
  def change
 | 
				
			||||
    create_table :wechat_logs do |t|
 | 
				
			||||
      t.string :openid, null: false, index: true
 | 
				
			||||
      t.text :request_raw
 | 
				
			||||
      t.text :response_raw
 | 
				
			||||
      t.text :session_raw
 | 
				
			||||
      t.datetime :created_at, null: false
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,28 @@
 | 
				
			||||
require 'wechat/api_loader'
 | 
				
			||||
require 'wechat/api'
 | 
				
			||||
require 'wechat/corp_api'
 | 
				
			||||
require 'action_controller/wechat_responder'
 | 
				
			||||
 | 
				
			||||
module Wechat
 | 
				
			||||
  autoload :Message, 'wechat/message'
 | 
				
			||||
  autoload :Responder, 'wechat/responder'
 | 
				
			||||
  autoload :Cipher, 'wechat/cipher'
 | 
				
			||||
  autoload :WechatLog, 'wechat/wechat_log'
 | 
				
			||||
 | 
				
			||||
  class AccessTokenExpiredError < StandardError; end
 | 
				
			||||
  class ResponseError < StandardError
 | 
				
			||||
    attr_reader :error_code
 | 
				
			||||
    def initialize(errcode, errmsg)
 | 
				
			||||
      @error_code = errcode
 | 
				
			||||
      super "#{errmsg}(#{error_code})"
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  def self.config
 | 
				
			||||
    ApiLoader.config
 | 
				
			||||
  end
 | 
				
			||||
 | 
				
			||||
  def self.api
 | 
				
			||||
    @wechat_api ||= ApiLoader.with({})
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,51 @@
 | 
				
			||||
module Wechat
 | 
				
			||||
  class ApiBase
 | 
				
			||||
    attr_reader :access_token, :client, :jsapi_ticket
 | 
				
			||||
 | 
				
			||||
    MP_BASE = 'https://mp.weixin.qq.com/cgi-bin/'
 | 
				
			||||
 | 
				
			||||
    def callbackip
 | 
				
			||||
      get 'getcallbackip'
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def qrcode(ticket)
 | 
				
			||||
      client.get 'showqrcode', params: { ticket: ticket }, base: MP_BASE, as: :file
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def media(media_id)
 | 
				
			||||
      get 'media/get', params: { media_id: media_id }, as: :file
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def media_create(type, file)
 | 
				
			||||
      post_file 'media/upload', file, params: { type: type }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    protected
 | 
				
			||||
 | 
				
			||||
    def get(path, headers = {})
 | 
				
			||||
      with_access_token(headers[:params]) do |params|
 | 
				
			||||
        client.get path, headers.merge(params: params)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def post(path, payload, headers = {})
 | 
				
			||||
      with_access_token(headers[:params]) do |params|
 | 
				
			||||
        client.post path, payload, headers.merge(params: params)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def post_file(path, file, headers = {})
 | 
				
			||||
      with_access_token(headers[:params]) do |params|
 | 
				
			||||
        client.post_file path, file, headers.merge(params: params)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def with_access_token(params = {}, tries = 2)
 | 
				
			||||
      params ||= {}
 | 
				
			||||
      yield(params.merge(access_token: access_token.token))
 | 
				
			||||
    rescue AccessTokenExpiredError
 | 
				
			||||
      access_token.refresh
 | 
				
			||||
      retry unless (tries -= 1).zero?
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,79 @@
 | 
				
			||||
module Wechat
 | 
				
			||||
  module ApiLoader
 | 
				
			||||
    def self.with(options)
 | 
				
			||||
      c = ApiLoader.config
 | 
				
			||||
 | 
				
			||||
      token_file = options[:token_file] || c.access_token || '/var/tmp/wechat_access_token'
 | 
				
			||||
      js_token_file = options[:js_token_file] || c.jsapi_ticket || '/var/tmp/wechat_jsapi_ticket'
 | 
				
			||||
 | 
				
			||||
      if c.appid && c.secret && token_file.present?
 | 
				
			||||
        Wechat::Api.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file)
 | 
				
			||||
      elsif c.corpid && c.corpsecret && token_file.present?
 | 
				
			||||
        Wechat::CorpApi.new(c.corpid, c.corpsecret, token_file, c.agentid, c.timeout, c.skip_verify_ssl, js_token_file)
 | 
				
			||||
      else
 | 
				
			||||
        puts <<-HELP
 | 
				
			||||
Need create ~/.wechat.yml with wechat appid and secret
 | 
				
			||||
or running at rails root folder so wechat can read config/wechat.yml
 | 
				
			||||
HELP
 | 
				
			||||
        exit 1
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    @config = nil
 | 
				
			||||
 | 
				
			||||
    def self.config
 | 
				
			||||
      return @config unless @config.nil?
 | 
				
			||||
      @config ||= loading_config!
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    private
 | 
				
			||||
 | 
				
			||||
    def self.loading_config!
 | 
				
			||||
      config ||= config_from_file || config_from_environment
 | 
				
			||||
 | 
				
			||||
      if defined?(::Rails)
 | 
				
			||||
        config[:access_token] ||= Rails.root.join('tmp/access_token').to_s
 | 
				
			||||
        config[:jsapi_ticket] ||= Rails.root.join('tmp/jsapi_ticket').to_s
 | 
				
			||||
      end
 | 
				
			||||
      config[:timeout] ||= 20
 | 
				
			||||
      config.symbolize_keys!
 | 
				
			||||
      @config = OpenStruct.new(config)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def self.config_from_file
 | 
				
			||||
      if defined?(::Rails)
 | 
				
			||||
        config_file = Rails.root.join('config/wechat.yml')
 | 
				
			||||
        return YAML.load(ERB.new(File.read(config_file)).result)[Rails.env] if File.exist?(config_file)
 | 
				
			||||
      else
 | 
				
			||||
        rails_config_file = File.join(Dir.getwd, 'config/wechat.yml')
 | 
				
			||||
        home_config_file = File.join(Dir.home, '.wechat.yml')
 | 
				
			||||
        if File.exist?(rails_config_file)
 | 
				
			||||
          rails_env = ENV['RAILS_ENV'] || 'default'
 | 
				
			||||
          config = YAML.load(ERB.new(File.read(rails_config_file)).result)[rails_env]
 | 
				
			||||
          if config.present? && (config['appid'] || config['corpid'])
 | 
				
			||||
            puts "Using rails project config/wechat.yml #{rails_env} setting..."
 | 
				
			||||
            return config
 | 
				
			||||
          end
 | 
				
			||||
        end
 | 
				
			||||
        if File.exist?(home_config_file)
 | 
				
			||||
          return YAML.load ERB.new(File.read(home_config_file)).result
 | 
				
			||||
        end
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def self.config_from_environment
 | 
				
			||||
      { appid: ENV['WECHAT_APPID'],
 | 
				
			||||
        secret: ENV['WECHAT_SECRET'],
 | 
				
			||||
        corpid: ENV['WECHAT_CORPID'],
 | 
				
			||||
        corpsecret: ENV['WECHAT_CORPSECRET'],
 | 
				
			||||
        agentid: ENV['WECHAT_AGENTID'],
 | 
				
			||||
        token: ENV['WECHAT_TOKEN'],
 | 
				
			||||
        access_token: ENV['WECHAT_ACCESS_TOKEN'],
 | 
				
			||||
        encrypt_mode: ENV['WECHAT_ENCRYPT_MODE'],
 | 
				
			||||
        timeout: ENV['WECHAT_TIMEOUT'],
 | 
				
			||||
        skip_verify_ssl: ENV['WECHAT_SKIP_VERIFY_SSL'],
 | 
				
			||||
        encoding_aes_key: ENV['WECHAT_ENCODING_AES_KEY'],
 | 
				
			||||
        jsapi_ticket: ENV['WECHAT_JSAPI_TICKET'] }
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,72 @@
 | 
				
			||||
require 'openssl/cipher'
 | 
				
			||||
require 'securerandom'
 | 
				
			||||
require 'base64'
 | 
				
			||||
 | 
				
			||||
module Wechat
 | 
				
			||||
  module Cipher
 | 
				
			||||
    extend ActiveSupport::Concern
 | 
				
			||||
 | 
				
			||||
    BLOCK_SIZE = 32
 | 
				
			||||
    CIPHER = 'AES-256-CBC'
 | 
				
			||||
 | 
				
			||||
    def encrypt(plain, encoding_aes_key)
 | 
				
			||||
      cipher = OpenSSL::Cipher.new(CIPHER)
 | 
				
			||||
      cipher.encrypt
 | 
				
			||||
 | 
				
			||||
      cipher.padding = 0
 | 
				
			||||
      key_data = Base64.decode64(encoding_aes_key + '=')
 | 
				
			||||
      cipher.key = key_data
 | 
				
			||||
      cipher.iv = key_data[0..16]
 | 
				
			||||
 | 
				
			||||
      cipher.update(plain) + cipher.final
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def decrypt(msg, encoding_aes_key)
 | 
				
			||||
      cipher = OpenSSL::Cipher.new(CIPHER)
 | 
				
			||||
      cipher.decrypt
 | 
				
			||||
 | 
				
			||||
      cipher.padding = 0
 | 
				
			||||
      key_data = Base64.decode64(encoding_aes_key + '=')
 | 
				
			||||
      cipher.key = key_data
 | 
				
			||||
      cipher.iv = key_data[0..16]
 | 
				
			||||
 | 
				
			||||
      plain = cipher.update(msg) + cipher.final
 | 
				
			||||
      decode_padding(plain)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    # app_id or corp_id
 | 
				
			||||
    def pack(content, app_id)
 | 
				
			||||
      random = SecureRandom.hex(8)
 | 
				
			||||
      text = content.force_encoding('ASCII-8BIT')
 | 
				
			||||
      msg_len = [text.length].pack('N')
 | 
				
			||||
 | 
				
			||||
      encode_padding("#{random}#{msg_len}#{text}#{app_id}")
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def unpack(msg)
 | 
				
			||||
      msg = decode_padding(msg)
 | 
				
			||||
      msg_len = msg[16, 4].reverse.unpack('V')[0]
 | 
				
			||||
      content = msg[20, msg_len]
 | 
				
			||||
      app_id = msg[(20 + msg_len)..-1]
 | 
				
			||||
 | 
				
			||||
      [content, app_id]
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    private
 | 
				
			||||
 | 
				
			||||
    def encode_padding(data)
 | 
				
			||||
      length = data.bytes.length
 | 
				
			||||
      amount_to_pad = BLOCK_SIZE - (length % BLOCK_SIZE)
 | 
				
			||||
      amount_to_pad = BLOCK_SIZE if amount_to_pad == 0
 | 
				
			||||
      padding = ([amount_to_pad].pack('c') * amount_to_pad)
 | 
				
			||||
      data + padding
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def decode_padding(plain)
 | 
				
			||||
      pad = plain.bytes[-1]
 | 
				
			||||
      # no padding
 | 
				
			||||
      pad = 0 if pad < 1 || pad > BLOCK_SIZE
 | 
				
			||||
      plain[0...(plain.length - pad)]
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,90 @@
 | 
				
			||||
require 'http'
 | 
				
			||||
 | 
				
			||||
module Wechat
 | 
				
			||||
  class Client
 | 
				
			||||
    attr_reader :base, :ssl_context
 | 
				
			||||
 | 
				
			||||
    def initialize(base, timeout, skip_verify_ssl)
 | 
				
			||||
      @base = base
 | 
				
			||||
      HTTP.timeout(:global, write: timeout, connect: timeout, read: timeout)
 | 
				
			||||
      @ssl_context = OpenSSL::SSL::SSLContext.new
 | 
				
			||||
      @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE if skip_verify_ssl
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def get(path, get_header = {})
 | 
				
			||||
      request(path, get_header) do |url, header|
 | 
				
			||||
        params = header.delete(:params)
 | 
				
			||||
        HTTP.headers(header).get(url, params: params, ssl_context: ssl_context)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def post(path, payload, post_header = {})
 | 
				
			||||
      request(path, post_header) do |url, header|
 | 
				
			||||
        params = header.delete(:params)
 | 
				
			||||
        HTTP.headers(header).post(url, params: params, body: payload, ssl_context: ssl_context)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def post_file(path, file, post_header = {})
 | 
				
			||||
      request(path, post_header) do |url, header|
 | 
				
			||||
        params = header.delete(:params)
 | 
				
			||||
        HTTP.headers(header)
 | 
				
			||||
          .post(url, params: params,
 | 
				
			||||
                     form: { media: HTTP::FormData::File.new(file),
 | 
				
			||||
                             hack: 'X' }, # Existing here for http-form_data 1.0.1 handle single param improperly
 | 
				
			||||
                     ssl_context: ssl_context)
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    private
 | 
				
			||||
 | 
				
			||||
    def request(path, header = {}, &_block)
 | 
				
			||||
      url = "#{header.delete(:base) || base}#{path}"
 | 
				
			||||
      as = header.delete(:as)
 | 
				
			||||
      header.merge!('Accept' => 'application/json')
 | 
				
			||||
      response = yield(url, header)
 | 
				
			||||
 | 
				
			||||
      fail "Request not OK, response status #{response.status}" if response.status != 200
 | 
				
			||||
      parse_response(response, as || :json) do |parse_as, data|
 | 
				
			||||
        break data unless parse_as == :json && data['errcode'].present?
 | 
				
			||||
 | 
				
			||||
        case data['errcode']
 | 
				
			||||
        when 0 # for request didn't expect results
 | 
				
			||||
          data
 | 
				
			||||
        # 42001: access_token timeout
 | 
				
			||||
        # 40014: invalid access_token
 | 
				
			||||
        # 40001, invalid credential, access_token is invalid or not latest hint
 | 
				
			||||
        # 48001, api unauthorized hint, for qrcode creation # 71
 | 
				
			||||
        when 42001, 40014, 40001, 48001
 | 
				
			||||
          fail AccessTokenExpiredError
 | 
				
			||||
        else
 | 
				
			||||
          fail ResponseError.new(data['errcode'], data['errmsg'])
 | 
				
			||||
        end
 | 
				
			||||
      end
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def parse_response(response, as)
 | 
				
			||||
      content_type = response.headers[:content_type]
 | 
				
			||||
      parse_as = {
 | 
				
			||||
        %r{^application\/json} => :json,
 | 
				
			||||
        %r{^image\/.*} => :file
 | 
				
			||||
      }.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || as || :text
 | 
				
			||||
 | 
				
			||||
      case parse_as
 | 
				
			||||
      when :file
 | 
				
			||||
        file = Tempfile.new('tmp')
 | 
				
			||||
        file.binmode
 | 
				
			||||
        file.write(response.body)
 | 
				
			||||
        file.close
 | 
				
			||||
        data = file
 | 
				
			||||
 | 
				
			||||
      when :json
 | 
				
			||||
        data = JSON.parse response.body.to_s.gsub(/[\u0000-\u001f]+/, '')
 | 
				
			||||
      else
 | 
				
			||||
        data = response.body
 | 
				
			||||
      end
 | 
				
			||||
 | 
				
			||||
      yield(parse_as, data)
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,166 @@
 | 
				
			||||
require 'wechat/api_base'
 | 
				
			||||
require 'wechat/client'
 | 
				
			||||
require 'wechat/token/corp_access_token'
 | 
				
			||||
require 'wechat/ticket/corp_jsapi_ticket'
 | 
				
			||||
require 'cgi'
 | 
				
			||||
 | 
				
			||||
module Wechat
 | 
				
			||||
  class CorpApi < ApiBase
 | 
				
			||||
    attr_reader :agentid
 | 
				
			||||
 | 
				
			||||
    API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin/'
 | 
				
			||||
 | 
				
			||||
    def initialize(appid, secret, token_file, agentid, timeout, skip_verify_ssl, jsapi_ticket_file)
 | 
				
			||||
      @client = Client.new(API_BASE, timeout, skip_verify_ssl)
 | 
				
			||||
      @access_token = Token::CorpAccessToken.new(@client, appid, secret, token_file)
 | 
				
			||||
      @agentid = agentid
 | 
				
			||||
      @jsapi_ticket = Ticket::CorpJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def agent_list
 | 
				
			||||
      get 'agent/list'
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def agent(agentid)
 | 
				
			||||
      get 'agent/get', params: { agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def user(userid)
 | 
				
			||||
      get 'user/get', params: { userid: userid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def getuserinfo(code)
 | 
				
			||||
      get 'user/getuserinfo', params: { code: code }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def oauth2_url(redirect_uri, appid)
 | 
				
			||||
      redirect_uri = CGI.escape(redirect_uri)
 | 
				
			||||
      "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{appid}&redirect_uri=#{redirect_uri}&response_type=code&scope=snsapi_base#wechat_redirect"
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def convert_to_openid(userid)
 | 
				
			||||
      post 'user/convert_to_openid', JSON.generate(userid: userid, agentid: agentid)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def invite_user(userid)
 | 
				
			||||
      post 'invite/send', JSON.generate(userid: userid)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def user_auth_success(userid)
 | 
				
			||||
      get 'user/authsucc', params: { userid: userid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def user_delete(userid)
 | 
				
			||||
      get 'user/delete', params: { userid: userid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def user_batchdelete(useridlist)
 | 
				
			||||
      post 'user/batchdelete', JSON.generate(useridlist: useridlist)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def batch_job_result(jobid)
 | 
				
			||||
      get 'batch/getresult', params: { jobid: jobid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def batch_replaceparty(media_id)
 | 
				
			||||
      post 'batch/replaceparty', JSON.generate(media_id: media_id)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def batch_syncuser(media_id)
 | 
				
			||||
      post 'batch/syncuser', JSON.generate(media_id: media_id)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def batch_replaceuser(media_id)
 | 
				
			||||
      post 'batch/replaceuser', JSON.generate(media_id: media_id)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def department_create(name, parentid)
 | 
				
			||||
      post 'department/create', JSON.generate(name: name, parentid: parentid)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def department_delete(departmentid)
 | 
				
			||||
      get 'department/delete', params: { id: departmentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def department_update(departmentid, name = nil, parentid = nil, order = nil)
 | 
				
			||||
      post 'department/update', JSON.generate({ id: departmentid, name: name, parentid: parentid, order: order }.reject { |_k, v| v.nil? })
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def department(departmentid = 1)
 | 
				
			||||
      get 'department/list', params: { id: departmentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def user_simplelist(department_id, fetch_child = 0, status = 0)
 | 
				
			||||
      get 'user/simplelist', params: { department_id: department_id, fetch_child: fetch_child, status: status }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def user_list(department_id, fetch_child = 0, status = 0)
 | 
				
			||||
      get 'user/list', params: { department_id: department_id, fetch_child: fetch_child, status: status }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tag_create(tagname, tagid = nil)
 | 
				
			||||
      post 'tag/create', JSON.generate(tagname: tagname, tagid: tagid)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tag_update(tagid, tagname)
 | 
				
			||||
      post 'tag/update', JSON.generate(tagid: tagid, tagname: tagname)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tag_delete(tagid)
 | 
				
			||||
      get 'tag/delete', params: { tagid: tagid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tags
 | 
				
			||||
      get 'tag/list'
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tag(tagid)
 | 
				
			||||
      get 'tag/get', params: { tagid: tagid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tag_add_user(tagid, userids = nil, departmentids = nil)
 | 
				
			||||
      post 'tag/addtagusers', JSON.generate(tagid: tagid, userlist: userids, partylist: departmentids)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def tag_del_user(tagid, userids = nil, departmentids = nil)
 | 
				
			||||
      post 'tag/deltagusers', JSON.generate(tagid: tagid, userlist: userids, partylist: departmentids)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def menu
 | 
				
			||||
      get 'menu/get', params: { agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def menu_delete
 | 
				
			||||
      get 'menu/delete', params: { agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def menu_create(menu)
 | 
				
			||||
      # 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
 | 
				
			||||
      post 'menu/create', JSON.generate(menu), params: { agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def material_count
 | 
				
			||||
      get 'material/get_count', params: { agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def material_list(type, offset, count)
 | 
				
			||||
      post 'material/batchget', JSON.generate(type: type, agentid: agentid, offset: offset, count: count)
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def material(media_id)
 | 
				
			||||
      get 'material/get', params: { media_id: media_id, agentid: agentid }, as: :file
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def material_add(type, file)
 | 
				
			||||
      post_file 'material/add_material', file, params: { type: type, agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def material_delete(media_id)
 | 
				
			||||
      get 'material/del', params: { media_id: media_id, agentid: agentid }
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    def message_send(openid, message)
 | 
				
			||||
      post 'message/send', Message.to(openid).text(message).agent_id(agentid).to_json, content_type: :json
 | 
				
			||||
    end
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue