Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
huhao98 committed Mar 28, 2014
0 parents commit be0e257
Show file tree
Hide file tree
Showing 50 changed files with 1,584 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .gitignore
@@ -0,0 +1,20 @@
*.gem
*.rbc
.bundle
.config
.rspec
.ruby-version
.ruby-gemset
Gemfile.lock
log/*.log
pkg/
tmp/
spec/dummy/db/*.sqlite3
spec/dummy/log/*.log
spec/dummy/tmp/
spec/dummy/.sass-cache
.DS_Store
Thumbs.db
coverage
pkg

6 changes: 6 additions & 0 deletions .travis.yml
@@ -0,0 +1,6 @@
before_install:
- gem update --system 2.1.11
language: ruby
rvm:
- "2.0.0"
- "2.1.0"
14 changes: 14 additions & 0 deletions Gemfile
@@ -0,0 +1,14 @@
source "https://rubygems.org"

gemspec

# jquery-rails is used by the dummy application
gem "jquery-rails"
gem "thor"

group :test do
gem 'rspec-rails', '~> 2.14.0'
end

gem "rake", "~> 0.9.6"

21 changes: 21 additions & 0 deletions LICENSE
@@ -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.
3 changes: 3 additions & 0 deletions README.md
@@ -0,0 +1,3 @@
= WechatRails

This project rocks and uses MIT-LICENSE.
31 changes: 31 additions & 0 deletions Rakefile
@@ -0,0 +1,31 @@
#!/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 = 'WechatRails'
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)


Bundler::GemHelper.install_tasks

task :default => :spec
1 change: 1 addition & 0 deletions VERSION
@@ -0,0 +1 @@
0.1.0
84 changes: 84 additions & 0 deletions bin/wechat
@@ -0,0 +1,84 @@
#!/usr/bin/env ruby

lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)

require 'thor'
require "wechat-rails"
require 'json'
require "active_resource/railtie"
require 'fileutils'



class App < Thor
class Helper
def self.with(options)
appid = ENV["WECHAT_APPID"]
secret = ENV["WECHAT_SECRET"]
token_file = options[:toke_file] || ENV["WECHAT_ACCESS_TOKEN"]

if (appid.nil? || secret.nil? || token_file.nil?)
puts <<-HELP
You need set wechat appid and secret in environment variables.
export WECHAT_APPID=<appid>
export WECHAT_SECRET=<secret>
export WECHAT_ACCESS_TOKEN=<file location for storing access token>
HELP
exit 1
end
WechatRails::Api.new(appid, secret, token_file)
end
end

package_name "WechatRails"
option :toke_file, :aliases=>"-t", :desc => "File to store access token"

desc "users", "关注者列表"
def users
puts Helper.with(options).users
end

desc "user [OPEN_ID]", "查找关注者"
def user(open_id)
puts Helper.with(options).user(open_id)
end

desc "menu", "当前菜单"
def menu
puts Helper.with(options).menu
end

desc "menu_delete", "删除菜单"
def menu_delete
puts "Menu deleted" if Helper.with(options).menu_delete
end

desc "menu_create [MENU_YAML]", "删除菜单"
def menu_create(menu_yaml)
menu = YAML.load(File.new(menu_yaml).read)
puts "Menu created" if Helper.with(options).menu_create(menu)
end

desc "media [MEDIA_ID, PATH]", "媒体下载"
def media(media_id, path)
tmp_file = Helper.with(options).media(media_id)
FileUtils.mv(tmp_file.path, path)
puts "File downloaded"
end

desc "media_create [MEDIA_ID, PATH]", "媒体上传"
def media_create(type, path)
file = File.new(path)
puts Helper.with(options).media_create(type, file)
end

desc "custom_text_message [OPENID, TEXT_MESSAGE]", "发送文字客服消息"
def custom_text_message(openid, text_message)
puts Helper.with(options).custom_text_message(openid, text_message)
end
end

App.start
36 changes: 36 additions & 0 deletions lib/wechat-rails.rb
@@ -0,0 +1,36 @@
require "wechat-rails/api"

module WechatRails
autoload :Handler, "wechat-rails/handler"
autoload :Response, "wechat-rails/response"

class AccessTokenExpiredError < StandardError; end
class ResponseError < StandardError
attr_reader :error_code
def initialize(errcode, errmsg)
error_code = errcode
super "#{errmsg}(#{error_code})"
end
end

attr_reader :config

def self.config
@config ||= begin
yaml = ERB.new(File.new(Rails.root.join("config/wechat.yml")).read).result
OpenStruct.new YAML.load(yaml)[Rails.env]
end
end

def self.api
@api ||= WechatRails::Api.new(self.config.app_id, self.config.secret)
end
end

if defined? ActionController::Base
class ActionController::Base
def self.wechat_rails
self.send(:include, WechatRails::Handler)
end
end
end
35 changes: 35 additions & 0 deletions lib/wechat-rails/access_token.rb
@@ -0,0 +1,35 @@
module WechatRails
class AccessToken
attr_reader :client, :appid, :secret, :token_file, :token_data

def initialize(client, appid, secret, token_file)
@appid = appid
@secret = secret
@client = client
@token_file = token_file
end

def token
begin
@token_data ||= JSON.parse(File.read(token_file))
rescue
self.refresh
end
return valid_token(@token_data)
end

def refresh
data = client.get("token", params:{grant_type: "client_credential", appid: appid, secret: secret})
File.open(token_file, 'w'){|f| f.write(data.to_s)} if valid_token(data)
return @token_data = data
end

private
def valid_token token_data
access_token = token_data["access_token"]
raise "Response didn't have access_token" if access_token.blank?
return access_token
end

end
end
80 changes: 80 additions & 0 deletions lib/wechat-rails/api.rb
@@ -0,0 +1,80 @@
require 'wechat-rails/client'
require 'wechat-rails/access_token'

class WechatRails::Api
attr_reader :app_id, :secret, :access_token, :client

API_BASE = "https://api.weixin.qq.com/cgi-bin/"
FILE_BASE = "http://file.api.weixin.qq.com/cgi-bin/"

def initialize app_id, secret, token_file
@client = WechatRails::Client.new(API_BASE)
@access_token = WechatRails::AccessToken.new(@client, app_id, secret, token_file)
end

def users
get("user/get")
end

def user openid
get("user/info", params:{openid: openid})
end

def menu
get("menu/get")
end

def menu_delete
get("menu/delete")
end

def menu_create menu
# 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码
escaped_utf_json = menu.to_json.gsub(/\\u([0-9a-z]{4})/) {|s| [$1.to_i(16)].pack("U")}
post("menu/create", escaped_utf_json)
end

def media media_id
response = get "media/get", params:{media_id: media_id}, base: FILE_BASE, as: :file
end

def media_create type, file
post "media/upload", {upload:{media: file}}, params:{type: type}, base: FILE_BASE
end

def custom_text_message openid, text
custom_message openid, :text, content: text
end

def custom_message openid, message_type, options={}
data = {
:touser => openid,
:msgtype => message_type,
message_type => options
}

post "message/custom/send", data.to_json, :content_type => :json
end


protected

def get path, headers={}
with_access_token(headers[:params]){|params| client.get path, headers.merge(params: params)}
end

def post path, payload, headers = {}
with_access_token(headers[:params]){|params| client.post path, payload, headers.merge(params: params)}
end

def with_access_token params={}, tries=2
begin
params ||= {}
yield(params.merge(access_token: access_token.token))
rescue WechatRails::AccessTokenExpiredError => ex
access_token.refresh
retry unless (tries -= 1).zero?
end
end

end

0 comments on commit be0e257

Please sign in to comment.