pindo 5.0.4 → 5.0.6
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/base/githelper.rb +1 -1
- data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +313 -0
- data/lib/pindo/client/pgyeruploadclient.rb +275 -153
- data/lib/pindo/command/android/autobuild.rb +121 -0
- data/lib/pindo/command/android/build.rb +113 -0
- data/lib/pindo/command/android/debug.rb +60 -14
- data/lib/pindo/command/android.rb +5 -2
- data/lib/pindo/command/ios/autobuild.rb +6 -0
- data/lib/pindo/command/ios/build.rb +7 -1
- data/lib/pindo/command/unity/apk.rb +69 -6
- data/lib/pindo/command/utils/renewcert.rb +2 -2
- data/lib/pindo/module/android/apk_helper.rb +91 -0
- data/lib/pindo/module/android/base_helper.rb +293 -0
- data/lib/pindo/module/android/build_helper.rb +112 -0
- data/lib/pindo/module/android/gradle_helper.rb +48 -0
- data/lib/pindo/module/android/so_helper.rb +18 -0
- data/lib/pindo/module/build/buildhelper.rb +50 -37
- data/lib/pindo/module/build/unityhelper.rb +16 -16
- data/lib/pindo/module/pgyer/pgyerhelper.rb +14 -11
- data/lib/pindo/module/xcode/xcodehelper.rb +73 -73
- data/lib/pindo/version.rb +1 -1
- metadata +71 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da7db67cae593054d94da87ec555ccd0d6ed2691ca9d534729a386b4cc55f360
|
4
|
+
data.tar.gz: 25b281165febd17ac56dfcadd64dcf2e55674e326b28575ff3952007b7dbdc2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '089d77424e3f53c88de5d9839b6849a11c868e6bf637b485403cec677a0e320cc2292e09fe8c868ecec3950a28e21b060a0867eadcd9b01dd1cab7aaaf8b878d'
|
7
|
+
data.tar.gz: 34a13c1bf0691285cd4d7c76c284c288c445ec2f3e67650d504ccdf1d3c2baa599c91492943ddee6078245372e5ae42746d2cd2bb881f9da0075305f7bd03d66
|
data/lib/pindo/base/githelper.rb
CHANGED
@@ -545,7 +545,7 @@ module Pindo
|
|
545
545
|
end
|
546
546
|
|
547
547
|
git!(%W(-C #{project_dir} reset --hard))
|
548
|
-
git!(%W(-C #{project_dir} clean -
|
548
|
+
git!(%W(-C #{project_dir} clean -df))
|
549
549
|
git!(%W(-C #{project_dir} branch --set-upstream-to=origin/#{branch} #{branch}))
|
550
550
|
git!(%W(-C #{project_dir} pull))
|
551
551
|
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'webrick'
|
4
|
+
require 'json'
|
5
|
+
require 'net/http'
|
6
|
+
require 'uri'
|
7
|
+
require 'securerandom'
|
8
|
+
require 'digest'
|
9
|
+
require 'base64'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'pindo/base/aeshelper'
|
12
|
+
require 'pindo/config/pindouserlocalconfig'
|
13
|
+
|
14
|
+
module Pindo
|
15
|
+
|
16
|
+
class PgyerFeishuOAuthCLI
|
17
|
+
attr_reader :access_token, :refresh_token, :user_info
|
18
|
+
|
19
|
+
def initialize(client_id)
|
20
|
+
@client_id = client_id
|
21
|
+
@feishu_auth_url = 'https://open.feishu.cn/open-apis/authen/v1/index'
|
22
|
+
@redirect_uri = 'https://jps-new.devtestapp.com/auth/jwt/login'
|
23
|
+
# @state = SecureRandom.hex(16)
|
24
|
+
@state = 'success_login'
|
25
|
+
@larkScopeList = [
|
26
|
+
'offline_access',
|
27
|
+
'task:task:write',
|
28
|
+
'task:section:write',
|
29
|
+
'task:custom_field:write',
|
30
|
+
'task:tasklist:write',
|
31
|
+
];
|
32
|
+
|
33
|
+
@access_token = nil
|
34
|
+
@refresh_token = nil
|
35
|
+
@token_expired_at = nil
|
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'
|
55
|
+
end
|
56
|
+
|
57
|
+
# 启动授权流程
|
58
|
+
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
|
+
|
70
|
+
authorization_uri = build_authorization_uri
|
71
|
+
puts "正在打开浏览器进行飞书OAuth授权..."
|
72
|
+
puts "授权URI: #{authorization_uri}"
|
73
|
+
|
74
|
+
# 在浏览器中打开授权URL
|
75
|
+
open_browser(authorization_uri)
|
76
|
+
|
77
|
+
# 启动本地服务器处理回调
|
78
|
+
code = start_callback_server
|
79
|
+
|
80
|
+
if code
|
81
|
+
puts "正在使用飞书身份登录Pgyer..."
|
82
|
+
if login_pgyer_with_feishu(code:code)
|
83
|
+
store_token
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
return false
|
87
|
+
else
|
88
|
+
puts "授权失败"
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# 验证Pgyer令牌
|
94
|
+
def validate_pgyer_token
|
95
|
+
return false unless @access_token
|
96
|
+
|
97
|
+
# 这里应该调用Pgyer的API验证令牌有效性
|
98
|
+
# 由于没有具体API文档,这里仅做示例
|
99
|
+
uri = URI("https://www.pgyer.com/api/user/profile")
|
100
|
+
request = Net::HTTP::Get.new(uri)
|
101
|
+
request['Authorization'] = "Bearer #{@access_token}"
|
102
|
+
|
103
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
104
|
+
http.request(request)
|
105
|
+
end
|
106
|
+
|
107
|
+
return response.code == '200'
|
108
|
+
end
|
109
|
+
|
110
|
+
# 使用code登录Pgyer, 并且Pgyer返回token
|
111
|
+
def login_pgyer_with_feishu(code: nil)
|
112
|
+
return false unless code
|
113
|
+
|
114
|
+
uri = URI(@pgyer_api_endpoint)
|
115
|
+
request = Net::HTTP::Post.new(uri)
|
116
|
+
request['Content-Type'] = 'application/json'
|
117
|
+
|
118
|
+
# 根据Pgyer的API要求构造请求体
|
119
|
+
request.body = {
|
120
|
+
code: code,
|
121
|
+
redirectUri: @redirect_uri,
|
122
|
+
scope: @larkScopeList.join(' ')
|
123
|
+
}.to_json
|
124
|
+
|
125
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
126
|
+
http.request(request)
|
127
|
+
end
|
128
|
+
|
129
|
+
if response.code == '200'
|
130
|
+
begin
|
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)
|
157
|
+
|
158
|
+
begin
|
159
|
+
encrypted_data = File.read(@pgyer_token_file)
|
160
|
+
json_data = aes_decrypt(encrypted_data, @pgyer_aes_key)
|
161
|
+
token_data = JSON.parse(json_data)
|
162
|
+
|
163
|
+
@access_token = token_data['token']
|
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']
|
166
|
+
|
167
|
+
# 检查令牌是否过期
|
168
|
+
if @token_expired_at && Time.now >= @token_expired_at
|
169
|
+
puts "令牌已过期,需要重新授权"
|
170
|
+
return false
|
171
|
+
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
|
+
rescue => e
|
199
|
+
puts "存储令牌失败: #{e.message}"
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
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
|
+
|
218
|
+
# 构建授权URI
|
219
|
+
def build_authorization_uri
|
220
|
+
uri = URI(@feishu_auth_url)
|
221
|
+
|
222
|
+
|
223
|
+
|
224
|
+
params = {
|
225
|
+
'app_id' => @client_id,
|
226
|
+
'redirect_uri' => @redirect_uri,
|
227
|
+
'response_type' => 'code',
|
228
|
+
'state' => @state,
|
229
|
+
'scope' => @larkScopeList.join(' ')
|
230
|
+
}
|
231
|
+
|
232
|
+
uri.query = URI.encode_www_form(params)
|
233
|
+
uri.to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
# 在浏览器中打开URL
|
237
|
+
def open_browser(url)
|
238
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
239
|
+
system("start", url)
|
240
|
+
elsif RbConfig::CONFIG['host_os'] =~ /darwin/
|
241
|
+
system("open", url)
|
242
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
|
243
|
+
system("xdg-open", url)
|
244
|
+
else
|
245
|
+
puts "无法自动打开浏览器,请手动访问: #{url}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# 启动本地服务器处理回调
|
250
|
+
def start_callback_server
|
251
|
+
code = nil
|
252
|
+
uri = URI(@redirect_uri)
|
253
|
+
server = WEBrick::HTTPServer.new(
|
254
|
+
Port: uri.port,
|
255
|
+
Logger: WEBrick::Log.new("/dev/null"),
|
256
|
+
AccessLog: []
|
257
|
+
)
|
258
|
+
|
259
|
+
path = uri.path.empty? ? '/' : uri.path
|
260
|
+
server.mount_proc path do |req, res|
|
261
|
+
query_params = URI.decode_www_form(req.query_string || '').to_h
|
262
|
+
|
263
|
+
if query_params['state'] != @state
|
264
|
+
res.body = "状态不匹配,可能存在CSRF攻击风险"
|
265
|
+
elsif query_params['error']
|
266
|
+
res.body = "授权错误: #{query_params['error']}"
|
267
|
+
elsif query_params['code']
|
268
|
+
code = query_params['code']
|
269
|
+
res.body = <<-HTML
|
270
|
+
<!DOCTYPE html>
|
271
|
+
<html>
|
272
|
+
<head>
|
273
|
+
<title>飞书授权成功</title>
|
274
|
+
<style>
|
275
|
+
body { font-family: Arial, sans-serif; text-align: center; padding: 40px; }
|
276
|
+
.container { max-width: 600px; margin: 0 auto; }
|
277
|
+
.success { color: #4CAF50; }
|
278
|
+
</style>
|
279
|
+
</head>
|
280
|
+
<body>
|
281
|
+
<div class="container">
|
282
|
+
<h1 class="success">授权成功!</h1>
|
283
|
+
<p>飞书OAuth授权已完成,您现在可以关闭此窗口并返回命令行。</p>
|
284
|
+
</div>
|
285
|
+
</body>
|
286
|
+
</html>
|
287
|
+
HTML
|
288
|
+
else
|
289
|
+
res.body = "未获取到授权码"
|
290
|
+
end
|
291
|
+
|
292
|
+
server.shutdown
|
293
|
+
end
|
294
|
+
|
295
|
+
# 捕获Ctrl+C以允许用户中断
|
296
|
+
trap('INT') { server.shutdown }
|
297
|
+
|
298
|
+
# 在线程中运行服务器,最多等待2分钟
|
299
|
+
thread = Thread.new { server.start }
|
300
|
+
begin
|
301
|
+
thread.join(120) # 最多等待2分钟
|
302
|
+
rescue Timeout::Error
|
303
|
+
puts "授权超时"
|
304
|
+
server.shutdown
|
305
|
+
end
|
306
|
+
|
307
|
+
code
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|