pindo 5.0.6 → 5.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/pindo/client/bossconfigclient.rb +3 -4
- data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +271 -172
- data/lib/pindo/client/pgyerclient.rb +36 -111
- data/lib/pindo/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd1d664dc77bc75c43a1816a8316fafb10351c9133a4e9dabf403f010bd1882a
|
4
|
+
data.tar.gz: 7e4387a7ad42c6830898bd9466a7ea8131ab92429b78ed5ed61479b07eebe44f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 305e2b32210bf046c6d56a82cff9f799e9bf73672e812aa28e3c78e378b32bc8c4700f5f4f45cfe39e3301427632f6074c2f6b419778c51a21de15dc19d8a676
|
7
|
+
data.tar.gz: 991b426167216c8c2f95fbd99400314fe807f892a6975300d145ff1e07684611547f6a89df7fbabd634d5f0f076839928fb89a1787977b313e4b1e332029a997
|
@@ -49,10 +49,9 @@ module Pindo
|
|
49
49
|
|
50
50
|
def do_login()
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
password = "HIsdfUHfwdh"
|
52
|
+
username = ask('请输入Boss网站的用户名:')
|
53
|
+
password = ask('请输入Boss网站的密码:')
|
54
|
+
|
56
55
|
|
57
56
|
do_login_req(username:username, password:password)
|
58
57
|
end
|
@@ -10,66 +10,43 @@ require 'base64'
|
|
10
10
|
require 'fileutils'
|
11
11
|
require 'pindo/base/aeshelper'
|
12
12
|
require 'pindo/config/pindouserlocalconfig'
|
13
|
+
require 'pindo/client/httpclient'
|
13
14
|
|
14
15
|
module Pindo
|
15
16
|
|
16
17
|
class PgyerFeishuOAuthCLI
|
17
|
-
attr_reader :access_token, :
|
18
|
+
attr_reader :access_token, :username, :expires_at
|
18
19
|
|
19
20
|
def initialize(client_id)
|
20
21
|
@client_id = client_id
|
21
|
-
@feishu_auth_url = 'https://
|
22
|
-
|
23
|
-
#
|
24
|
-
|
22
|
+
@feishu_auth_url = 'https://passport.feishu.cn/suite/passport/oauth/authorize?'
|
23
|
+
|
24
|
+
# 保持原有的redirect_uri,飞书OAuth流程中仍使用这个URL
|
25
|
+
# 前端UI会检测state=terminal_login并自动跳转到localhost:8899
|
26
|
+
@redirect_uri = 'https://pgyerapps.com/login'
|
27
|
+
@pgyer_api_endpoint = 'https://api.pgyerapps.com/api/user/lark_qr_login'
|
28
|
+
|
29
|
+
@state = 'terminal_login'
|
30
|
+
|
25
31
|
@larkScopeList = [
|
26
|
-
'offline_access',
|
27
32
|
'task:task:write',
|
28
33
|
'task:section:write',
|
29
34
|
'task:custom_field:write',
|
30
|
-
'task:tasklist:write'
|
35
|
+
'task:tasklist:write'
|
31
36
|
];
|
32
37
|
|
33
38
|
@access_token = nil
|
34
|
-
@
|
35
|
-
@
|
36
|
-
|
37
|
-
@pgyer_token_file = File.join(File::expand_path(Pindoconfig.instance.pindo_dir), ".pgyer_token")
|
38
|
-
|
39
|
-
# 尝试读取配置文件中的AES密钥
|
40
|
-
begin
|
41
|
-
config_file = File.join(File::expand_path(Pindoconfig.instance.pindo_common_configdir), "pgyer_client_config.json")
|
42
|
-
if File.exist?(config_file)
|
43
|
-
config_json = JSON.parse(File.read(config_file))
|
44
|
-
@pgyer_aes_key = config_json["pgyerapps_aes_key"] if config_json["pgyerapps_aes_key"]
|
45
|
-
end
|
46
|
-
rescue => e
|
47
|
-
puts "读取配置文件失败: #{e.message}"
|
48
|
-
end
|
49
|
-
|
50
|
-
# 如果配置文件中没有找到密钥,则使用默认密钥
|
51
|
-
@pgyer_aes_key ||= ENV['PGYER_AES_KEY'] || "pgyerOauthToken2024"
|
52
|
-
|
53
|
-
# Pgyer API endpoints
|
54
|
-
@pgyer_api_endpoint = 'https://jps-api.devtestapp.com/api/lark_login'
|
39
|
+
@username = nil
|
40
|
+
@expires_at = nil
|
55
41
|
end
|
56
42
|
|
57
43
|
# 启动授权流程
|
58
44
|
def authorize
|
59
|
-
#
|
60
|
-
if load_token
|
61
|
-
puts "找到已保存的Pgyer令牌,尝试验证..."
|
62
|
-
if validate_pgyer_token
|
63
|
-
puts "已有令牌验证成功!"
|
64
|
-
return true
|
65
|
-
else
|
66
|
-
puts "已有令牌已失效,需要重新授权。"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
45
|
+
# 不再检查已存储的令牌,直接开始授权流程
|
70
46
|
authorization_uri = build_authorization_uri
|
71
47
|
puts "正在打开浏览器进行飞书OAuth授权..."
|
72
48
|
puts "授权URI: #{authorization_uri}"
|
49
|
+
puts "注意:授权成功后,网页将自动跳转至本地http://localhost:8899"
|
73
50
|
|
74
51
|
# 在浏览器中打开授权URL
|
75
52
|
open_browser(authorization_uri)
|
@@ -77,10 +54,38 @@ module Pindo
|
|
77
54
|
# 启动本地服务器处理回调
|
78
55
|
code = start_callback_server
|
79
56
|
|
57
|
+
# 如果自动获取失败,提示用户手动输入
|
58
|
+
if code.nil?
|
59
|
+
puts "自动获取授权码失败,您可以手动输入:"
|
60
|
+
puts "1. 授权码 (直接复制'code='后面的内容)"
|
61
|
+
puts "2. 完整回调URL (例如: http://localhost:8899/?code=xxxx...)"
|
62
|
+
print "> "
|
63
|
+
input = STDIN.gets.chomp
|
64
|
+
|
65
|
+
if input.start_with?("http")
|
66
|
+
# 尝试从URL中提取code
|
67
|
+
begin
|
68
|
+
uri = URI(input)
|
69
|
+
query_params = URI.decode_www_form(uri.query || '').to_h
|
70
|
+
code = query_params['code']
|
71
|
+
if code
|
72
|
+
puts "从URL中成功提取授权码"
|
73
|
+
end
|
74
|
+
rescue => e
|
75
|
+
puts "无法从URL中提取授权码: #{e.message}"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# 将输入直接作为code
|
79
|
+
code = input unless input.empty?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
80
83
|
if code
|
81
|
-
puts "
|
84
|
+
puts "成功获取授权码!正在使用飞书身份登录Pgyer..."
|
85
|
+
puts "code: #{code}"
|
82
86
|
if login_pgyer_with_feishu(code:code)
|
83
|
-
|
87
|
+
puts "Pgyer登录成功!"
|
88
|
+
# 登录成功后,返回true,外部程序可以通过访问access_token属性获取token
|
84
89
|
return true
|
85
90
|
end
|
86
91
|
return false
|
@@ -91,14 +96,23 @@ module Pindo
|
|
91
96
|
end
|
92
97
|
|
93
98
|
# 验证Pgyer令牌
|
94
|
-
def validate_pgyer_token
|
95
|
-
|
99
|
+
def validate_pgyer_token(token = nil, expires_at = nil)
|
100
|
+
token_to_check = token || @access_token
|
101
|
+
expiration_time = expires_at || @expires_at
|
96
102
|
|
97
|
-
#
|
98
|
-
|
103
|
+
# 首先检查token是否存在
|
104
|
+
return false unless token_to_check
|
105
|
+
|
106
|
+
# 然后检查token是否过期
|
107
|
+
if expiration_time && Time.now.to_i > expiration_time
|
108
|
+
puts "令牌已过期,需要重新登录"
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
|
112
|
+
# 最后验证token是否有效
|
99
113
|
uri = URI("https://www.pgyer.com/api/user/profile")
|
100
114
|
request = Net::HTTP::Get.new(uri)
|
101
|
-
request['Authorization'] = "Bearer #{
|
115
|
+
request['Authorization'] = "Bearer #{token_to_check}"
|
102
116
|
|
103
117
|
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
104
118
|
http.request(request)
|
@@ -111,118 +125,70 @@ module Pindo
|
|
111
125
|
def login_pgyer_with_feishu(code: nil)
|
112
126
|
return false unless code
|
113
127
|
|
114
|
-
|
115
|
-
|
116
|
-
request['Content-Type'] = 'application/json'
|
117
|
-
|
118
|
-
# 根据Pgyer的API要求构造请求体
|
119
|
-
request.body = {
|
128
|
+
# 构造请求体
|
129
|
+
body_params = {
|
120
130
|
code: code,
|
121
131
|
redirectUri: @redirect_uri,
|
122
132
|
scope: @larkScopeList.join(' ')
|
123
|
-
}
|
133
|
+
}
|
124
134
|
|
125
|
-
|
126
|
-
|
127
|
-
end
|
135
|
+
puts "请求Pgyer API: #{@pgyer_api_endpoint}"
|
136
|
+
puts "请求参数: #{body_params.to_json}"
|
128
137
|
|
129
|
-
|
130
|
-
|
131
|
-
result = JSON.parse(response.body)
|
132
|
-
if !result['meta'].nil? && result['meta']['code'] == 200 && !result['data'].nil? && !result['data']['token'].nil?
|
133
|
-
@access_token = result['data']['token']
|
134
|
-
@username = result['data']['token']
|
135
|
-
@user_id = result['data']['user_id']
|
136
|
-
|
137
|
-
return true
|
138
|
-
else
|
139
|
-
puts "Pgyer登录失败: #{result['message'] || '未知错误'}"
|
140
|
-
return false
|
141
|
-
end
|
142
|
-
rescue JSON::ParserError
|
143
|
-
puts "解析Pgyer响应失败"
|
144
|
-
return false
|
145
|
-
end
|
146
|
-
else
|
147
|
-
puts "Pgyer登录请求失败: HTTP #{response.code}"
|
148
|
-
return false
|
149
|
-
end
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
# 加载存储的令牌
|
155
|
-
def load_token
|
156
|
-
return false unless File.exist?(@pgyer_token_file)
|
138
|
+
# 使用HttpClient发送请求
|
139
|
+
con = HttpClient.create_instance_with_proxy
|
157
140
|
|
158
141
|
begin
|
159
|
-
|
160
|
-
|
161
|
-
|
142
|
+
res = con.post do |req|
|
143
|
+
req.url @pgyer_api_endpoint
|
144
|
+
req.headers['Content-Type'] = 'application/json'
|
145
|
+
req.body = body_params.to_json
|
146
|
+
end
|
162
147
|
|
163
|
-
|
164
|
-
@user_info = token_data['user_info'] if token_data['user_info']
|
165
|
-
@token_expired_at = Time.at(token_data['expires_at']) if token_data['expires_at']
|
148
|
+
puts "API响应状态码: #{res.status}"
|
166
149
|
|
167
|
-
#
|
168
|
-
|
169
|
-
|
150
|
+
# 处理响应
|
151
|
+
result = nil
|
152
|
+
if !res.body.nil?
|
153
|
+
begin
|
154
|
+
result = JSON.parse(res.body)
|
155
|
+
# puts "解析后的响应: #{result.inspect}"
|
156
|
+
|
157
|
+
if result['code'] == 200 && !result['data'].nil? && !result['data']['token'].nil?
|
158
|
+
@access_token = result['data']['token']
|
159
|
+
@username = result['data']['username'] if result['data']['username']
|
160
|
+
# 设置token有效期为7天后
|
161
|
+
@expires_at = Time.now.to_i + 6 * 24 * 60 * 60 # 7天的秒数
|
162
|
+
return true
|
163
|
+
else
|
164
|
+
error_msg = result['meta'] && result['meta']['message'] ? result['meta']['message'] : '未知错误'
|
165
|
+
puts "Pgyer登录失败: #{error_msg}"
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
rescue => e
|
169
|
+
puts "解析响应失败: #{e.message}"
|
170
|
+
puts "原始响应: #{res.body[0..200]}"
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
else
|
174
|
+
puts "请求返回空响应"
|
170
175
|
return false
|
171
176
|
end
|
172
|
-
|
173
|
-
return true
|
174
|
-
rescue => e
|
175
|
-
puts "加载令牌失败: #{e.message}"
|
176
|
-
return false
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
# 存储令牌
|
181
|
-
def store_token
|
182
|
-
return false unless @access_token
|
183
|
-
|
184
|
-
token_data = {
|
185
|
-
'token' => @access_token,
|
186
|
-
'user_info' => @user_info,
|
187
|
-
'expires_at' => Time.now.to_i + 7*24*60*60 # 假设令牌有效期为7天
|
188
|
-
}
|
189
|
-
|
190
|
-
encrypted_data = aes_encrypt(token_data.to_json, @pgyer_aes_key)
|
191
|
-
|
192
|
-
begin
|
193
|
-
File.open(@pgyer_token_file, 'w') do |f|
|
194
|
-
f.write(encrypted_data)
|
195
|
-
end
|
196
|
-
puts "令牌已安全存储"
|
197
|
-
return true
|
198
177
|
rescue => e
|
199
|
-
puts "
|
178
|
+
puts "请求过程中出错: #{e.class} - #{e.message}"
|
200
179
|
return false
|
201
180
|
end
|
202
181
|
end
|
203
182
|
|
204
183
|
private
|
205
184
|
|
206
|
-
# AES加密
|
207
|
-
def aes_encrypt(data, key)
|
208
|
-
# 使用Pindo的AESHelper类进行真正的AES加密
|
209
|
-
AESHelper::aes_128_ecb_encrypt(key, data)
|
210
|
-
end
|
211
|
-
|
212
|
-
# AES解密
|
213
|
-
def aes_decrypt(encrypted_data, key)
|
214
|
-
# 使用Pindo的AESHelper类进行真正的AES解密
|
215
|
-
AESHelper::aes_128_ecb_decrypt(key, encrypted_data)
|
216
|
-
end
|
217
185
|
|
218
186
|
# 构建授权URI
|
219
187
|
def build_authorization_uri
|
220
188
|
uri = URI(@feishu_auth_url)
|
221
189
|
|
222
|
-
|
223
|
-
|
224
190
|
params = {
|
225
|
-
'
|
191
|
+
'client_id' => @client_id,
|
226
192
|
'redirect_uri' => @redirect_uri,
|
227
193
|
'response_type' => 'code',
|
228
194
|
'state' => @state,
|
@@ -249,61 +215,194 @@ module Pindo
|
|
249
215
|
# 启动本地服务器处理回调
|
250
216
|
def start_callback_server
|
251
217
|
code = nil
|
252
|
-
|
218
|
+
|
219
|
+
puts "启动本地服务器,监听端口8899..."
|
220
|
+
|
221
|
+
# 使用本地8899端口处理回调,不管redirect_uri配置如何
|
253
222
|
server = WEBrick::HTTPServer.new(
|
254
|
-
Port:
|
223
|
+
Port: 8899,
|
255
224
|
Logger: WEBrick::Log.new("/dev/null"),
|
256
225
|
AccessLog: []
|
257
226
|
)
|
258
227
|
|
259
|
-
|
260
|
-
server.mount_proc
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
228
|
+
# 处理根路径的请求
|
229
|
+
server.mount_proc '/' do |req, res|
|
230
|
+
begin
|
231
|
+
# puts "接收到请求: #{req.request_line}"
|
232
|
+
# puts "请求参数: #{req.query_string}"
|
233
|
+
|
234
|
+
# 安全解析请求参数
|
235
|
+
begin
|
236
|
+
query_params = URI.decode_www_form(req.query_string || '').to_h
|
237
|
+
puts "解析的参数: #{query_params.inspect}"
|
238
|
+
rescue => e
|
239
|
+
puts "解析请求参数失败: #{e.message}"
|
240
|
+
query_params = {}
|
241
|
+
end
|
242
|
+
|
243
|
+
# if query_params['state'] != @state
|
244
|
+
# puts "状态不匹配: 接收到 #{query_params['state']},期望 #{@state}"
|
245
|
+
# res.content_type = "text/html; charset=UTF-8"
|
246
|
+
# res.body = "状态不匹配,可能存在CSRF攻击风险"
|
247
|
+
# elsif query_params['error']
|
248
|
+
if query_params['error']
|
249
|
+
puts "授权错误: #{query_params['error']}"
|
250
|
+
res.content_type = "text/html; charset=UTF-8"
|
251
|
+
res.body = "授权错误: #{query_params['error']}"
|
252
|
+
elsif query_params['code']
|
253
|
+
code = query_params['code']
|
254
|
+
puts "成功获取授权码: #{code}"
|
255
|
+
res.content_type = "text/html; charset=UTF-8"
|
256
|
+
res.body = <<-HTML
|
257
|
+
<!DOCTYPE html>
|
258
|
+
<html>
|
259
|
+
<head>
|
260
|
+
<meta charset="UTF-8">
|
261
|
+
<title>飞书授权成功</title>
|
262
|
+
<style>
|
263
|
+
body { font-family: Arial, sans-serif; text-align: center; padding: 40px; }
|
264
|
+
.container { max-width: 600px; margin: 0 auto; }
|
265
|
+
.success { color: #4CAF50; }
|
266
|
+
.code { font-family: monospace; background: #f5f5f5; padding: 10px; border-radius: 4px; word-break: break-all; }
|
267
|
+
.countdown { font-weight: bold; color: #FF5722; }
|
268
|
+
.autoclose-banner {
|
269
|
+
background-color: #333; color: white; padding: 10px;
|
270
|
+
position: fixed; top: 0; left: 0; right: 0;
|
271
|
+
display: flex; justify-content: space-between; align-items: center;
|
272
|
+
}
|
273
|
+
.close-btn {
|
274
|
+
background: #f44336; color: white; border: none;
|
275
|
+
padding: 5px 10px; cursor: pointer; border-radius: 3px;
|
276
|
+
}
|
277
|
+
</style>
|
278
|
+
<script>
|
279
|
+
// 尝试多种方法关闭窗口
|
280
|
+
function attemptClose() {
|
281
|
+
try {
|
282
|
+
// 方法1: 最基本的关闭尝试
|
283
|
+
window.close();
|
284
|
+
|
285
|
+
// 方法2: 对于某些浏览器需要历史记录操作
|
286
|
+
window.history.back();
|
287
|
+
|
288
|
+
// 方法3: 空白页替换
|
289
|
+
window.location.href = "about:blank";
|
290
|
+
|
291
|
+
// 方法4: 尝试使用opener关系
|
292
|
+
if (window.opener) {
|
293
|
+
window.opener.focus();
|
294
|
+
window.close();
|
295
|
+
}
|
296
|
+
|
297
|
+
// 如果以上方法都失败了,显示手动关闭提示
|
298
|
+
setTimeout(function() {
|
299
|
+
document.getElementById('close-message').style.display = 'block';
|
300
|
+
document.getElementById('countdown-container').style.display = 'none';
|
301
|
+
}, 1000);
|
302
|
+
} catch (e) {
|
303
|
+
console.error("关闭窗口失败:", e);
|
304
|
+
document.getElementById('close-message').style.display = 'block';
|
305
|
+
document.getElementById('countdown-container').style.display = 'none';
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
// 倒计时函数
|
310
|
+
var secondsLeft = 3;
|
311
|
+
function updateCountdown() {
|
312
|
+
document.getElementById('countdown').innerText = secondsLeft;
|
313
|
+
if (secondsLeft <= 0) {
|
314
|
+
attemptClose();
|
315
|
+
} else {
|
316
|
+
secondsLeft -= 1;
|
317
|
+
setTimeout(updateCountdown, 1000);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
// 初始化
|
322
|
+
window.onload = function() {
|
323
|
+
updateCountdown();
|
324
|
+
}
|
325
|
+
</script>
|
326
|
+
</head>
|
327
|
+
<body>
|
328
|
+
<div class="autoclose-banner">
|
329
|
+
<span>授权成功! 此窗口将自动关闭 (<span id="countdown" class="countdown">3</span>)</span>
|
330
|
+
<button class="close-btn" onclick="attemptClose()">立即关闭</button>
|
331
|
+
</div>
|
332
|
+
|
333
|
+
<div class="container">
|
334
|
+
<h1 class="success">飞书授权成功!</h1>
|
335
|
+
<p>已获取授权码,正在返回命令行...</p>
|
336
|
+
|
337
|
+
<div id="countdown-container">
|
338
|
+
<p>此窗口将在 <span id="countdown-text" class="countdown">3</span> 秒后自动关闭</p>
|
339
|
+
</div>
|
340
|
+
|
341
|
+
<div id="close-message" style="display: none;">
|
342
|
+
<p>自动关闭失败,请手动关闭此窗口</p>
|
343
|
+
<button class="close-btn" onclick="attemptClose()">尝试再次关闭</button>
|
344
|
+
</div>
|
345
|
+
|
346
|
+
<p>授权码:</p>
|
347
|
+
<div class="code">#{code}</div>
|
348
|
+
</div>
|
349
|
+
|
350
|
+
<script>
|
351
|
+
// 同步倒计时显示
|
352
|
+
document.getElementById('countdown-text').innerText = secondsLeft;
|
353
|
+
</script>
|
354
|
+
</body>
|
355
|
+
</html>
|
356
|
+
HTML
|
357
|
+
else
|
358
|
+
puts "未获取到授权码"
|
359
|
+
res.content_type = "text/html; charset=UTF-8"
|
360
|
+
res.body = "未获取到授权码"
|
361
|
+
end
|
291
362
|
|
292
|
-
|
363
|
+
# 给用户更多时间看到页面内容,并让JavaScript倒计时完成
|
364
|
+
puts "4秒后关闭服务器..."
|
365
|
+
Thread.new do
|
366
|
+
sleep 4 # 设置为4秒,比JavaScript的3秒稍长一点,确保客户端有足够时间执行
|
367
|
+
server.shutdown
|
368
|
+
end
|
369
|
+
rescue => e
|
370
|
+
puts "处理请求时出错: #{e.class} - #{e.message}"
|
371
|
+
puts e.backtrace.join("\n")
|
372
|
+
|
373
|
+
# 确保即使出错也返回有效的响应
|
374
|
+
res.content_type = "text/html; charset=UTF-8"
|
375
|
+
res.body = "处理请求时出错"
|
376
|
+
|
377
|
+
# 出错时也关闭服务器
|
378
|
+
Thread.new do
|
379
|
+
sleep 4 # 与正常情况保持一致
|
380
|
+
server.shutdown
|
381
|
+
end
|
382
|
+
end
|
293
383
|
end
|
294
|
-
|
384
|
+
|
295
385
|
# 捕获Ctrl+C以允许用户中断
|
296
386
|
trap('INT') { server.shutdown }
|
297
387
|
|
298
|
-
#
|
388
|
+
# puts "正在监听http://localhost:8899等待飞书重定向..."
|
389
|
+
# puts "您也可以手动访问: http://localhost:8899/?code=YOUR_CODE&state=terminal_login"
|
390
|
+
|
391
|
+
# 在线程中运行服务器,最多等待3分钟
|
299
392
|
thread = Thread.new { server.start }
|
300
393
|
begin
|
301
|
-
thread.join(
|
394
|
+
thread.join(180) # 最多等待3分钟
|
302
395
|
rescue Timeout::Error
|
303
396
|
puts "授权超时"
|
304
397
|
server.shutdown
|
305
398
|
end
|
306
399
|
|
400
|
+
if code
|
401
|
+
puts "成功获取授权码: #{code}"
|
402
|
+
else
|
403
|
+
puts "未获取到授权码"
|
404
|
+
end
|
405
|
+
|
307
406
|
code
|
308
407
|
end
|
309
408
|
|
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
1
|
require 'uri'
|
3
2
|
require 'pindo/base/aeshelper'
|
4
3
|
require 'pindo/config/pindouserlocalconfig'
|
5
4
|
require 'pindo/client/httpclient'
|
6
|
-
|
5
|
+
require 'pindo/client/pgyer_feishu_oauth_cli'
|
7
6
|
|
8
7
|
module Pindo
|
9
8
|
|
@@ -35,124 +34,44 @@ module Pindo
|
|
35
34
|
|
36
35
|
end
|
37
36
|
|
38
|
-
def get_faraday_instance
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
37
|
def do_login(force_login:false)
|
44
38
|
|
39
|
+
login_success = false
|
40
|
+
need_req_login = true
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
@token = load_token()
|
49
|
-
if !@token.nil? && !@token["token"].nil? && !force_login
|
50
|
-
login_success = true
|
51
|
-
|
52
|
-
# puts "用户#{@token["username"]}登录pgyer成功!!!"
|
53
|
-
# puts
|
54
|
-
|
55
|
-
else
|
56
|
-
username = nil
|
57
|
-
if !@token.nil? && !@token["username"].nil?
|
58
|
-
username = @token["username"]
|
59
|
-
checksum_password = nil
|
60
|
-
if !@token["password"].nil?
|
61
|
-
checksum_password = @token["password"]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
token = do_login_req(username:username, checksum_password:checksum_password)
|
65
|
-
if !token.nil? && !token["token"].nil?
|
66
|
-
@token = token
|
67
|
-
login_success = true
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
return login_success
|
72
|
-
end
|
73
|
-
|
74
|
-
def do_login_req(username:nil, checksum_password:nil)
|
75
|
-
|
76
|
-
|
77
|
-
login_name = username
|
78
|
-
if login_name.nil? || login_name.empty?
|
79
|
-
login_name = ask('请输入pgger网站的usernmae:') || nil
|
80
|
-
end
|
81
|
-
|
82
|
-
checksum_pass = checksum_password
|
83
|
-
if checksum_pass.nil? || checksum_pass.empty?
|
84
|
-
login_passwork = ask('请输入pgger网站的密码:') || nil
|
85
|
-
checksum_pass = Digest::MD5.hexdigest(login_passwork)
|
86
|
-
end
|
87
|
-
|
88
|
-
if !login_name.nil? && !login_name.empty? && !checksum_pass.nil? && !checksum_pass.empty?
|
89
|
-
|
42
|
+
if force_login
|
43
|
+
need_req_login = true
|
90
44
|
else
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
username:login_name,
|
104
|
-
phoneCode:result_data["msg"],
|
105
|
-
password:checksum_pass
|
106
|
-
}
|
107
|
-
|
108
|
-
# puts JSON.pretty_generate(body_params)
|
109
|
-
|
110
|
-
login_response_data = nil
|
111
|
-
|
112
|
-
begin
|
113
|
-
|
114
|
-
con = HttpClient.create_instance_with_proxy
|
115
|
-
res = con.post do |req|
|
116
|
-
req.url boss_url
|
117
|
-
req.headers['Content-Type'] = 'application/json'
|
118
|
-
req.body = body_params.to_json
|
45
|
+
@token = load_token()
|
46
|
+
if !@token.nil? && !@token["token"].nil? && !@token["username"].nil?
|
47
|
+
# 检查token是否已过期(超过7天)
|
48
|
+
if @token["expires_at"] && Time.now.to_i < @token["expires_at"]
|
49
|
+
need_req_login = false
|
50
|
+
else
|
51
|
+
Funlog.instance.fancyinfo_error("令牌已过期,需要重新登录...")
|
52
|
+
login_success = true
|
53
|
+
end
|
54
|
+
else
|
55
|
+
need_req_login = true
|
56
|
+
end
|
119
57
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
58
|
+
if need_req_login
|
59
|
+
login_handle = PgyerFeishuOAuthCLI.new("cli_a7db8213883ed00d")
|
60
|
+
result = login_handle.authorize
|
61
|
+
@token = {}
|
62
|
+
@token["token"] = login_handle.access_token
|
63
|
+
@token["username"] = login_handle.username
|
64
|
+
@token["expires_at"] = login_handle.expires_at
|
65
|
+
store_token(token:@token)
|
66
|
+
login_success = true
|
67
|
+
else
|
68
|
+
login_success = true
|
124
69
|
end
|
125
70
|
|
126
|
-
|
127
|
-
Funlog.instance.fancyinfo_error("pgyer登录失败!")
|
128
|
-
puts "登录失败,请重试!!!"
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# puts JSON.pretty_generate(login_response_data)
|
134
|
-
|
135
|
-
if !login_response_data.nil? && !login_response_data["code"].nil? && login_response_data["code"].to_s.eql?("200")
|
136
|
-
|
137
|
-
token = {}
|
138
|
-
token= login_response_data["data"]
|
139
|
-
token["password"] = checksum_pass
|
140
|
-
# puts JSON.pretty_generate(token)
|
141
|
-
store_token(token:token)
|
142
|
-
|
143
|
-
Funlog.instance.fancyinfo_success("用户:#{@token["username"]}登录pgyer成功!")
|
144
|
-
|
145
|
-
else
|
146
|
-
if File.exist?(@pgyer_token_file)
|
147
|
-
FileUtils.rm_rf(@pgyer_token_file)
|
148
|
-
end
|
149
|
-
Funlog.instance.fancyinfo_error("pgyer登录失败!")
|
150
|
-
end
|
151
|
-
|
152
|
-
return token
|
153
|
-
|
71
|
+
return login_success
|
154
72
|
end
|
155
73
|
|
74
|
+
|
156
75
|
def load_token()
|
157
76
|
|
158
77
|
@token = nil
|
@@ -165,6 +84,12 @@ module Pindo
|
|
165
84
|
temp_token = data_string
|
166
85
|
temp_token = JSON.parse(data_string)
|
167
86
|
if !temp_token.nil? && !temp_token["token"].nil? && !temp_token["username"].nil?
|
87
|
+
# 检查token是否已过期
|
88
|
+
if temp_token["expires_at"] && Time.now.to_i > temp_token["expires_at"]
|
89
|
+
Funlog.instance.fancyinfo_error("令牌已过期,需要重新登录...")
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
|
168
93
|
@token = temp_token
|
169
94
|
Funlog.instance.fancyinfo_success("读取pgyer token成功!")
|
170
95
|
end
|
data/lib/pindo/version.rb
CHANGED