Class: Pindo::PgyerFeishuOAuthCLI

Inherits:
Object
  • Object
show all
Defined in:
lib/pindo/client/pgyer_feishu_oauth_cli.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client_id) ⇒ PgyerFeishuOAuthCLI

Returns a new instance of PgyerFeishuOAuthCLI.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 20

def initialize(client_id)
  @client_id = client_id
  @feishu_auth_url = 'https://passport.feishu.cn/suite/passport/oauth/authorize?'
  
  # 保持原有的redirect_uri,飞书OAuth流程中仍使用这个URL
  # 前端UI会检测state=terminal_login并自动跳转到localhost:8899
  @redirect_uri = 'https://pgyerapps.com/login'
  @pgyer_api_endpoint = 'https://api.pgyerapps.com/api/user/lark_qr_login'
  
  @state = 'terminal_login'
  
  @larkScopeList = [
    'task:task:write',
    'task:section:write',
    'task:custom_field:write',
    'task:tasklist:write'
  ];

  @access_token = nil
  @username = nil
  @expires_at = nil
end

Instance Attribute Details

#access_tokenObject (readonly)

Returns the value of attribute access_token.



18
19
20
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 18

def access_token
  @access_token
end

#expires_atObject (readonly)

Returns the value of attribute expires_at.



18
19
20
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 18

def expires_at
  @expires_at
end

#usernameObject (readonly)

Returns the value of attribute username.



18
19
20
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 18

def username
  @username
end

Instance Method Details

#authorizeObject

启动授权流程



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 44

def authorize
  # 不再检查已存储的令牌,直接开始授权流程
  authorization_uri = build_authorization_uri
  puts "正在打开浏览器进行飞书OAuth授权..."
  puts "授权URI: #{authorization_uri}"
  puts "注意:授权成功后,网页将自动跳转至本地http://localhost:8899"
  
  # 在浏览器中打开授权URL
  open_browser(authorization_uri)
  
  # 启动本地服务器处理回调
  code = start_callback_server
  
  # 如果自动获取失败,提示用户手动输入
  if code.nil?
    puts "自动获取授权码失败,您可以手动输入:"
    puts "1. 授权码 (直接复制'code='后面的内容)"
    puts "2. 完整回调URL (例如: http://localhost:8899/?code=xxxx...)"
    print "> "
    input = STDIN.gets.chomp
    
    if input.start_with?("http")
      # 尝试从URL中提取code
      begin
        uri = URI(input)
        query_params = URI.decode_www_form(uri.query || '').to_h
        code = query_params['code']
        if code
          puts "从URL中成功提取授权码"
        end
      rescue => e
        puts "无法从URL中提取授权码: #{e.message}"
      end
    else
      # 将输入直接作为code
      code = input unless input.empty?
    end
  end
  
  if code
    puts "成功获取授权码!正在使用飞书身份登录Pgyer..."
    puts "code: #{code}"
    if (code:code)
      puts "Pgyer登录成功!"
      # 登录成功后,返回true,外部程序可以通过访问access_token属性获取token
      return true
    end
    return false
  else
    puts "授权失败"
    return false
  end
end

#login_pgyer_with_feishu(code: nil) ⇒ Object

使用code登录Pgyer, 并且Pgyer返回token



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 125

def (code: nil)
  return false unless code
  
  # 构造请求体
  body_params = {
    code: code,
    redirectUri: @redirect_uri,
    scope: @larkScopeList.join(' ')
  }
  
  puts "请求Pgyer API: #{@pgyer_api_endpoint}"
  puts "请求参数: #{body_params.to_json}"
  
  # 使用HttpClient发送请求
  con = HttpClient.create_instance_with_proxy
  
  begin
    res = con.post do |req|
      req.url @pgyer_api_endpoint
      req.headers['Content-Type'] = 'application/json'
      req.body = body_params.to_json
    end
    
    puts "API响应状态码: #{res.status}"
    
    # 处理响应
    result = nil
    if !res.body.nil?
      begin
        result = JSON.parse(res.body)
        # puts "解析后的响应: #{result.inspect}"
        
        if result['code'] == 200 && !result['data'].nil? && !result['data']['token'].nil?
          @access_token = result['data']['token']
          @username = result['data']['username'] if result['data']['username']
          # 设置token有效期为7天后
          @expires_at = Time.now.to_i + 6 * 24 * 60 * 60  # 7天的秒数
          return true
        else
          error_msg = result['meta'] && result['meta']['message'] ? result['meta']['message'] : '未知错误'
          puts "Pgyer登录失败: #{error_msg}"
          return false
        end
      rescue => e
        puts "解析响应失败: #{e.message}"
        puts "原始响应: #{res.body[0..200]}"
        return false
      end
    else
      puts "请求返回空响应"
      return false
    end
  rescue => e
    puts "请求过程中出错: #{e.class} - #{e.message}"
    return false
  end
end

#validate_pgyer_token(token = nil, expires_at = nil) ⇒ Object

验证Pgyer令牌



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/pindo/client/pgyer_feishu_oauth_cli.rb', line 99

def validate_pgyer_token(token = nil, expires_at = nil)
  token_to_check = token || @access_token
  expiration_time = expires_at || @expires_at
  
  # 首先检查token是否存在
  return false unless token_to_check
  
  # 然后检查token是否过期
  if expiration_time && Time.now.to_i > expiration_time
    puts "令牌已过期,需要重新登录"
    return false
  end
  
  # 最后验证token是否有效
  uri = URI("https://www.pgyer.com/api/user/profile")
  request = Net::HTTP::Get.new(uri)
  request['Authorization'] = "Bearer #{token_to_check}"
  
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
    http.request(request)
  end
  
  return response.code == '200'
end