pindo 4.9.6 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce1731c2ab77bdee7cfa10865db928c8b97344eee053ab5e636b6976875b7081
4
- data.tar.gz: 5ec006fbfe270a055da4d716d823cd13820c96975758027773a233ca06d47b18
3
+ metadata.gz: 48f45a274d7bf7e99a3c76e791805e2138b556bf8953bb0bc57c65eeb7dbda06
4
+ data.tar.gz: b4a8afc25cc290bb596fa3c0061fe2ea75a604b7934bd7521f43e7ce9e003b41
5
5
  SHA512:
6
- metadata.gz: 6f50bd7f5cbca0ab7ffc57e6b4ec6277b8c9a7ab6a10eb2f7fba50e79dacb392fcdba2c96fb2c5b46786a3a0058cb94d2c4c6dde7b2504ef59c8ad9742c209c4
7
- data.tar.gz: f94c5d8264b77884aa15f6112cc8546fb2ba23de5eea03a5142bd312aadb46e60ec32de33ebfa6e00d65f6249a724c96fcc0c0be35105b08f2d83004ce210938
6
+ metadata.gz: 73bd11a9717893c06c35f731d36522c9e8a90936dbbc5cc86b679f31008e8f0046a211014fda1b24a90167b4c52884a386ef97c94626c80c688d91fa154c5a4d
7
+ data.tar.gz: b99da9f081dda86a6ae7184b4528bead3e4895f7d890d89783ff1b0dd4d523918dc728fd022f6b3f73aa444e2cd278650ef6bbe66fe9998e4e7c6f0929494210
@@ -0,0 +1,254 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'json'
7
+ require 'openssl'
8
+ require 'base64'
9
+
10
+ module Pindo
11
+ class FeishuClient
12
+
13
+ def initialize(webhook_url, secret = nil)
14
+ @webhook_url = webhook_url
15
+ @secret = secret
16
+ end
17
+
18
+ private
19
+
20
+ def generate_sign(timestamp)
21
+ return nil unless @secret
22
+
23
+ # 构造密钥字符串:timestamp + "\n" + secret
24
+ string_to_sign = "#{timestamp}\n#{@secret}"
25
+
26
+ # 添加调试信息
27
+ # puts "密钥字符串: #{string_to_sign.inspect}"
28
+
29
+ # 使用string_to_sign作为密钥,对空字节数组进行签名
30
+ hmac_code = OpenSSL::HMAC.digest(
31
+ OpenSSL::Digest.new('sha256'),
32
+ string_to_sign, # 使用 timestamp\n + secret 作为密钥
33
+ "" # 使用空字符串作为待签名数据,对应Go代码中的空data
34
+ )
35
+
36
+ sign = Base64.strict_encode64(hmac_code)
37
+
38
+ # 添加调试信息
39
+ # puts "生成的签名: #{sign}"
40
+
41
+ sign
42
+ end
43
+
44
+ def send_request(payload)
45
+ uri = URI.parse(@webhook_url)
46
+ http = Net::HTTP.new(uri.host, uri.port)
47
+ http.use_ssl = true
48
+
49
+ request = Net::HTTP::Post.new(uri.request_uri)
50
+ request['Content-Type'] = 'application/json'
51
+
52
+ # 如果设置了密钥,添加签名
53
+ if @secret
54
+ # 使用秒级时间戳(飞书要求)
55
+ timestamp = Time.now.to_i
56
+
57
+ # 添加更多调试信息
58
+ # puts "\n签名信息:"
59
+ # puts "Secret: #{@secret}"
60
+ # puts "当前UTC时间: #{Time.now.utc}"
61
+ # puts "当前本地时间: #{Time.now}"
62
+ # puts "使用的时间戳(秒): #{timestamp}"
63
+
64
+ # 生成签名
65
+ sign = generate_sign(timestamp)
66
+
67
+ # 添加时间戳和签名
68
+ payload = payload.merge({
69
+ timestamp: timestamp,
70
+ sign: sign
71
+ })
72
+ end
73
+
74
+ # 打印完整的请求信息
75
+ # puts "\n发送请求详情:"
76
+ # puts "URL: #{@webhook_url}"
77
+ # puts "Headers: #{request.to_hash}"
78
+ # puts "Payload: #{JSON.pretty_generate(payload)}\n"
79
+
80
+ request.body = payload.to_json
81
+ response = http.request(request)
82
+
83
+ result = JSON.parse(response.body)
84
+
85
+ result
86
+ rescue => e
87
+ puts "请求发送失败: #{e.message}"
88
+ puts e.backtrace
89
+ raise
90
+ end
91
+
92
+ public
93
+
94
+ def send_message(message)
95
+ payload = {
96
+ msg_type: "text",
97
+ content: {
98
+ text: message
99
+ }
100
+ }
101
+
102
+ response = send_request(payload)
103
+ puts "发送结果: #{response['msg']}" if response['msg']
104
+ response
105
+ end
106
+
107
+ def send_rich_text(title, content)
108
+ payload = {
109
+ msg_type: "post",
110
+ content: {
111
+ post: {
112
+ zh_cn: {
113
+ title: title,
114
+ content: [
115
+ [
116
+ {
117
+ tag: "text",
118
+ text: content
119
+ }
120
+ ]
121
+ ]
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ response = send_request(payload)
128
+ # puts "发送结果: #{response['msg']}" if response['msg']
129
+ response
130
+ end
131
+
132
+ def send_card_message(title, content)
133
+ payload = {
134
+ msg_type: "interactive",
135
+ card: {
136
+ config: {
137
+ wide_screen_mode: true
138
+ },
139
+ header: {
140
+ title: {
141
+ tag: "plain_text",
142
+ content: title
143
+ }
144
+ },
145
+ elements: [
146
+ {
147
+ tag: "div",
148
+ text: {
149
+ tag: "plain_text",
150
+ content: content
151
+ }
152
+ }
153
+ ]
154
+ }
155
+ }
156
+
157
+ response = send_request(payload)
158
+ puts "发送结果: #{response['msg']}" if response['msg']
159
+ response
160
+ end
161
+
162
+ def send_markdown(title, markdown_content)
163
+ # 提取一级标题作为消息标题
164
+ lines = markdown_content.split("\n")
165
+ title_match = lines.find { |line| line.strip =~ /^# (.+)/ }
166
+ message_title = title_match ? $1.strip : title
167
+
168
+ # 移除原始的一级标题
169
+ content_without_title = lines.reject { |line| line.strip =~ /^# / }.join("\n")
170
+
171
+ # 格式化剩余内容
172
+ formatted_content = format_markdown_content(content_without_title)
173
+
174
+ payload = {
175
+ msg_type: "interactive",
176
+ card: {
177
+ config: {
178
+ wide_screen_mode: true
179
+ },
180
+ header: {
181
+ title: {
182
+ tag: "plain_text",
183
+ content: message_title,
184
+ style: {
185
+ bold: true,
186
+ size: 32 # 增大标题字体到32
187
+ }
188
+ }
189
+ },
190
+ elements: [
191
+ {
192
+ tag: "div",
193
+ text: {
194
+ tag: "lark_md",
195
+ content: formatted_content
196
+ }
197
+ }
198
+ ]
199
+ }
200
+ }
201
+
202
+ response = send_request(payload)
203
+ puts "发送结果: #{response['msg']}" if response['msg']
204
+ response
205
+ end
206
+
207
+ private
208
+
209
+ def format_markdown_content(markdown)
210
+ lines = markdown.split("\n")
211
+ formatted_lines = []
212
+
213
+ lines.each_with_index do |line, index|
214
+ original_line = line # 保存原始行(包含缩进)
215
+ line = line.strip
216
+ next if line.empty? && (index == 0 || lines[index-1].strip.empty?) # 跳过连续的空行
217
+
218
+ # 检查是否需要跳过这个空行
219
+ next if line.empty? && index > 0 && (
220
+ lines[index-1].strip =~ /^### / || # 跳过三级标题后的空行
221
+ lines[index-1].strip =~ /^## \[.+?\]/ || # 跳过二级标题后的空行
222
+ lines[index-1].strip =~ /^- / # 跳过列表项后的空行
223
+ )
224
+
225
+ # 获取缩进级别
226
+ indent_level = original_line[/^\s*/].length / 4 # 假设每级缩进是4个空格
227
+ indent = " " * indent_level # 使用4个空格作为一级缩进
228
+
229
+ formatted_line = case line
230
+ when /^## \[(.+?)\] - (.+)/ # 版本标题
231
+ "\n**【#{$1}】**#{$2}" # 版本号加粗,前面加空行
232
+ when /^### (.)(.+)/ # 分类标题
233
+ "#{$1}**#{$2}**" # emoji 不加粗,文字加粗
234
+ when /^- (.+?) \((.+?)\)/ # 列表项带commit
235
+ "#{indent}• #{$1} (#{$2})" # 添加缩进
236
+ when /^- (.+)/ # 普通列表项
237
+ "#{indent}• #{$1}" # 添加缩进
238
+ when '' # 空行
239
+ line
240
+ else
241
+ "#{indent}#{line}" # 其他内容也保持缩进
242
+ end
243
+
244
+ formatted_lines << formatted_line if formatted_line
245
+ end
246
+
247
+ # 移除开头和结尾的空行,并确保段落之间只有一个空行
248
+ content = formatted_lines.join("\n").strip
249
+ content.gsub!(/\n{3,}/, "\n\n") # 将连续的3个或更多换行替换为2个
250
+ content
251
+ end
252
+ end
253
+ end
254
+
@@ -0,0 +1,110 @@
1
+ require 'highline/import'
2
+ require 'fileutils'
3
+ require 'pindo/client/feishuclient'
4
+ require 'pindo/module/build/buildhelper'
5
+ require 'pindo/module/pgyer/pgyerhelper'
6
+
7
+ module Pindo
8
+ class Command
9
+ class Dev < Command
10
+ class Feishu < Dev
11
+ self.summary = '发送飞书消息'
12
+
13
+ # 命令的详细说明,包含用法示例
14
+ self.description = <<-DESC
15
+ 发送飞书消息到指定的飞书群。
16
+
17
+ 使用示例:
18
+ $ pindo dev feishu "要发送的消息"
19
+ $ pindo dev feishu -w WEBHOOK_URL -s SECRET "要发送的消息"
20
+ DESC
21
+
22
+ # 命令的参数列表
23
+ self.arguments = []
24
+
25
+ # 命令的选项列表
26
+ def self.options
27
+ [
28
+ ['--webhook', '使用feishu 机器人的webhook url'],
29
+ ].concat(super)
30
+ end
31
+
32
+ def initialize(argv)
33
+ super
34
+ @webhook_url = argv.option('webhook')
35
+ # @webhook_url = 'https://open.feishu.cn/open-apis/bot/v2/hook/dbffda6e-a809-4e79-9652-c427c93ffe62'
36
+ # @secret = 'VoC4vVmhwWs676zK62NK8d'
37
+ @secret = nil
38
+ @message = argv.shift_argument || "测试消息"
39
+ end
40
+
41
+ def validate!
42
+ super
43
+ help! '请提供要发送的消息内容' unless @message
44
+ end
45
+
46
+ def run
47
+
48
+ pindo_project_dir = Dir.pwd
49
+ if @webhook_url.nil? || @webhook_url.empty?
50
+ if is_git_directory?(local_repo_dir: pindo_project_dir)
51
+ current_git_root_path = git_root_directory(local_repo_dir: pindo_project_dir)
52
+ if File.exist?(File.join(current_git_root_path, 'feishu.json'))
53
+ feishu_config = JSON.parse(File.read(File.join(current_git_root_path, 'feishu.json')))
54
+ @webhook_url = feishu_config['webhook_url']
55
+ @secret = feishu_config['secret']
56
+ end
57
+ end
58
+
59
+ if @webhook_url.nil? || @webhook_url.empty?
60
+ @webhook_url = ask("请输入飞书webhook url:")
61
+ if is_git_directory?(local_repo_dir: pindo_project_dir)
62
+ current_git_root_path = git_root_directory(local_repo_dir: pindo_project_dir)
63
+ feishu_config = {webhook_url: @webhook_url, secret: @secret}
64
+ File.write(File.join(current_git_root_path, 'feishu.json'), JSON.pretty_generate(feishu_config))
65
+ Dir.chdir(current_git_root_path)
66
+ current_branch = git!(%W(-C #{current_git_root_path} rev-parse --abbrev-ref HEAD)).strip
67
+ git!(%W(-C #{current_git_root_path} add feishu.json))
68
+ commit_message = "docs: 添加飞书webhook url".encode('UTF-8')
69
+ git!(%W(-C #{current_git_root_path} commit -m #{commit_message}))
70
+ git!(%W(-C #{current_git_root_path} push origin #{current_branch}))
71
+ puts "添加飞书webhook url成功"
72
+ end
73
+ end
74
+
75
+ end
76
+ puts "webhook_url: #{@webhook_url}"
77
+ puts "secret: #{@secret}"
78
+ puts
79
+ puts "正在发送飞书消息..."
80
+
81
+
82
+ build_helper = Pindo::BuildHelper.share_instance
83
+ build_helper.check_check_and_install_cliff(pindo_project_dir)
84
+ is_need_add_tag,tag_action_parms = build_helper.check_is_need_add_tag?(pindo_project_dir)
85
+ if is_need_add_tag
86
+ Pindo::Command::Dev::Tag::run(tag_action_parms)
87
+ end
88
+
89
+ unless @webhook_url.nil? || @webhook_url.empty?
90
+ @client = Pindo::FeishuClient.new(@webhook_url, @secret)
91
+ end
92
+
93
+ markdown_content = PgyerHelper.share_instace.get_description_from_git(current_project_dir:pindo_project_dir)
94
+ puts
95
+ puts "#{markdown_content}"
96
+ puts
97
+ if !markdown_content.nil? && !markdown_content.empty?
98
+ @client.send_markdown(nil, markdown_content)
99
+ # @client.send_rich_text("1111", markdown_content)
100
+ end
101
+
102
+ end
103
+
104
+
105
+
106
+
107
+ end
108
+ end
109
+ end
110
+ end
@@ -6,7 +6,7 @@ require 'pindo/command/dev/debug'
6
6
  require 'pindo/command/dev/autobuild'
7
7
  require 'pindo/command/dev/build'
8
8
  require 'pindo/command/dev/repoinit'
9
-
9
+ require 'pindo/command/dev/feishu'
10
10
 
11
11
  module Pindo
12
12
  class Command
@@ -211,7 +211,7 @@ module Pindo
211
211
  end
212
212
  end
213
213
 
214
- system "open #{project_dir}"
214
+ system "open #{pindo_project_dir}"
215
215
 
216
216
  end
217
217
 
@@ -126,6 +126,8 @@ module Pindo
126
126
  end
127
127
  end
128
128
  end
129
+
130
+ system "open #{pindo_project_dir}"
129
131
  end
130
132
 
131
133
  end
@@ -37,6 +37,7 @@ module Pindo
37
37
  gitignore_content = <<~GITIGNORE
38
38
  # Build logs
39
39
  build_ios.log
40
+ feishu.json
40
41
  CHANGELOG.md
41
42
 
42
43
  # Platform specific directories
data/lib/pindo/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Pindo
2
2
 
3
- VERSION = "4.9.6"
3
+ VERSION = "5.0.0"
4
4
 
5
5
  class VersionCheck
6
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.9.6
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
@@ -235,6 +235,7 @@ files:
235
235
  - lib/pindo/client/aws3sclient.rb
236
236
  - lib/pindo/client/bossclient.rb
237
237
  - lib/pindo/client/bossconfigclient.rb
238
+ - lib/pindo/client/feishuclient.rb
238
239
  - lib/pindo/client/giteeclient.rb
239
240
  - lib/pindo/client/httpclient.rb
240
241
  - lib/pindo/client/pgyerclient.rb
@@ -277,6 +278,7 @@ files:
277
278
  - lib/pindo/command/dev/autobuild.rb
278
279
  - lib/pindo/command/dev/build.rb
279
280
  - lib/pindo/command/dev/debug.rb
281
+ - lib/pindo/command/dev/feishu.rb
280
282
  - lib/pindo/command/dev/repoinit.rb
281
283
  - lib/pindo/command/dev/tag.rb
282
284
  - lib/pindo/command/env.rb