Add Mattermost post and build commands
This commit is contained in:
parent
d3a9abc004
commit
c54161ffa2
11 changed files with 715 additions and 0 deletions
21
assets/templates/mattermost_build_test.yaml
Normal file
21
assets/templates/mattermost_build_test.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
property:
|
||||
workspace: .
|
||||
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: mattermost-build
|
||||
|
||||
commands:
|
||||
- command: MattermostBuild
|
||||
id: mattermost-build
|
||||
param:
|
||||
url: https://mattermost.toki-labs.com/api/v4/posts
|
||||
token: 3zihstia4frodxgea7i91agbye
|
||||
channelId: n78a1wrdwjydzdoat6gci8cphe
|
||||
appName: oto_cli
|
||||
type: ios
|
||||
color: ED1458
|
||||
version: 1.0.0-test
|
||||
downloadURL: https://example.com/download/oto_cli.ipa
|
||||
22
assets/templates/mattermost_test.yaml
Normal file
22
assets/templates/mattermost_test.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
property:
|
||||
workspace: .
|
||||
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: mattermost
|
||||
|
||||
commands:
|
||||
- command: Mattermost
|
||||
id: mattermost
|
||||
param:
|
||||
url: https://mattermost.toki-labs.com/api/v4/posts
|
||||
token: 3zihstia4frodxgea7i91agbye
|
||||
message:
|
||||
channel_id: n78a1wrdwjydzdoat6gci8cphe
|
||||
message: 봇 테스트 메시지입니다.
|
||||
props:
|
||||
attachments:
|
||||
- pretext: Mattermost Bot Test
|
||||
text: 정상적으로 전송되면 이 메시지가 채널에 표시됩니다.
|
||||
28
assets/templates/mattermost_test_json.yaml
Normal file
28
assets/templates/mattermost_test_json.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
property:
|
||||
workspace: .
|
||||
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: mattermost
|
||||
|
||||
commands:
|
||||
- command: Mattermost
|
||||
id: mattermost
|
||||
param:
|
||||
url: https://mattermost.toki-labs.com/api/v4/posts
|
||||
token: 3zihstia4frodxgea7i91agbye
|
||||
messageJson: |
|
||||
{
|
||||
"channel_id": "n78a1wrdwjydzdoat6gci8cphe",
|
||||
"message": "봇 테스트 메시지입니다. (messageJson)",
|
||||
"props": {
|
||||
"attachments": [
|
||||
{
|
||||
"pretext": "Mattermost Bot Test",
|
||||
"text": "정상적으로 전송되면 이 메시지가 채널에 표시됩니다. (messageJson)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
29
assets/templates/mattermost_test_property.yaml
Normal file
29
assets/templates/mattermost_test_property.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
property:
|
||||
workspace: .
|
||||
|
||||
pipeline:
|
||||
id: main
|
||||
workflow:
|
||||
- exe: set-payload
|
||||
- exe: mattermost
|
||||
|
||||
commands:
|
||||
- command: SetValue
|
||||
id: set-payload
|
||||
param:
|
||||
values-object:
|
||||
mattermostPayload:
|
||||
channel_id: n78a1wrdwjydzdoat6gci8cphe
|
||||
message: 봇 테스트 메시지입니다. (property)
|
||||
props:
|
||||
attachments:
|
||||
- pretext: Mattermost Bot Test
|
||||
text: 정상적으로 전송되면 이 메시지가 채널에 표시됩니다. (property)
|
||||
|
||||
- command: Mattermost
|
||||
id: mattermost
|
||||
param:
|
||||
url: https://mattermost.toki-labs.com/api/v4/posts
|
||||
token: 3zihstia4frodxgea7i91agbye
|
||||
property: <!property.mattermostPayload>
|
||||
|
|
@ -29,6 +29,7 @@ import 'package:oto_cli/oto/commands/jira/jira.dart';
|
|||
import 'package:oto_cli/oto/commands/process/process.dart';
|
||||
import 'package:oto_cli/oto/commands/proto/protobuf.dart';
|
||||
import 'package:oto_cli/oto/commands/util/json.dart';
|
||||
import 'package:oto_cli/oto/commands/util/mattermost.dart';
|
||||
import 'package:oto_cli/oto/commands/util/print.dart';
|
||||
import 'package:oto_cli/oto/commands/util/set_value.dart';
|
||||
import 'package:oto_cli/oto/commands/util/smb_authorize.dart';
|
||||
|
|
@ -83,6 +84,8 @@ enum CommandType {
|
|||
Slack,
|
||||
SlackFile,
|
||||
SlackBuild,
|
||||
Mattermost,
|
||||
MattermostBuild,
|
||||
StringSub,
|
||||
StringReplace,
|
||||
StringReplacePattern,
|
||||
|
|
@ -161,6 +164,8 @@ abstract class Command {
|
|||
CommandType.Slack: () => Slack(),
|
||||
CommandType.SlackFile: () => SlackFile(),
|
||||
CommandType.SlackBuild: () => SlackBuild(),
|
||||
CommandType.Mattermost: () => Mattermost(),
|
||||
CommandType.MattermostBuild: () => MattermostBuild(),
|
||||
CommandType.StringSub: () => StringSub(),
|
||||
CommandType.StringReplace: () => StringReplace(),
|
||||
CommandType.StringReplacePattern: () => StringReplacePattern(),
|
||||
|
|
|
|||
97
lib/oto/commands/util/mattermost.dart
Normal file
97
lib/oto/commands/util/mattermost.dart
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// ignore_for_file: avoid_init_to_null
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:color/color.dart';
|
||||
import 'package:dart_framework/utils/system_util.dart';
|
||||
import 'package:oto_cli/oto/commands/command.dart';
|
||||
import 'package:oto_cli/oto/data/command_data.dart';
|
||||
import 'package:oto_cli/oto/utils/mattermost/mattermost_data.dart';
|
||||
import 'package:oto_cli/oto/utils/mattermost/mattermost_sender.dart';
|
||||
|
||||
class Mattermost extends Command {
|
||||
@override
|
||||
Future execute(DataCommand command) async {
|
||||
final data = DataMattermostSender.fromJson(getParam(command));
|
||||
final sender = MattermostSender(data.token, data.url);
|
||||
|
||||
if (data.property != null) {
|
||||
final value = data.property;
|
||||
if (value is DataMattermost) {
|
||||
await sender.sendData(value, defaultChannelId: data.channelId);
|
||||
} else if (value is Map) {
|
||||
await sender.sendMap(
|
||||
DataMattermost.fromJson(Map<String, dynamic>.from(value))
|
||||
.toPostJson(defaultChannelId: data.channelId),
|
||||
);
|
||||
} else {
|
||||
throw Exception('The property must be DataMattermost or Map.');
|
||||
}
|
||||
} else if (data.message != null) {
|
||||
final payload = Map<String, dynamic>.from(data.message!);
|
||||
await sender.sendMap(
|
||||
_replaceMentionsInMap(payload, data.idMap).cast<String, dynamic>());
|
||||
} else if (data.messageJson != null) {
|
||||
await sender.send(getName2ID(data.messageJson!, data.idMap));
|
||||
} else {
|
||||
throw Exception('property or message field should not be null');
|
||||
}
|
||||
|
||||
return simpleFuture;
|
||||
}
|
||||
|
||||
String getName2ID(String message, Map<String, String>? idMap) {
|
||||
if (idMap == null) {
|
||||
return message;
|
||||
}
|
||||
|
||||
for (final entry in idMap.entries) {
|
||||
message = message.replaceAll('@${entry.key}', '@${entry.value}');
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
dynamic _replaceMentionsInMap(dynamic value, Map<String, String>? idMap) {
|
||||
if (idMap == null) {
|
||||
return value;
|
||||
}
|
||||
if (value is String) {
|
||||
var replaced = value;
|
||||
for (final entry in idMap.entries) {
|
||||
replaced = replaced.replaceAll('@${entry.key}', '@${entry.value}');
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
if (value is List) {
|
||||
return value.map((item) => _replaceMentionsInMap(item, idMap)).toList();
|
||||
}
|
||||
if (value is Map) {
|
||||
return value.map(
|
||||
(key, item) => MapEntry(key, _replaceMentionsInMap(item, idMap)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class MattermostBuild extends Command {
|
||||
@override
|
||||
Future execute(DataCommand command) async {
|
||||
final data = DataMattermostBuild.fromJson(getParam(command));
|
||||
final sender = MattermostSender(data.token, data.url);
|
||||
final channelId = data.channelId;
|
||||
if (channelId == null || channelId.isEmpty) {
|
||||
throw Exception('channelId is required for MattermostBuild.');
|
||||
}
|
||||
|
||||
await sender.sendBuild(
|
||||
data.appName,
|
||||
data.type,
|
||||
Color.hex(data.color),
|
||||
data.version,
|
||||
data.downloadURL,
|
||||
channelId: channelId,
|
||||
);
|
||||
|
||||
return complete(0, command);
|
||||
}
|
||||
}
|
||||
|
|
@ -149,6 +149,18 @@ commands:
|
|||
# downloadURL: http://toki-labs.com/build/tplayer/android/tplayer_arm64_<!common.version>.apk
|
||||
# channel: release
|
||||
|
||||
#- command: Mattermost
|
||||
# param:
|
||||
# url: https://mattermost.example.com/api/v4/posts
|
||||
# token: bot-token
|
||||
# channelId: n78a1wrdwjydzdoat6gci8cphe
|
||||
# message:
|
||||
# message: 봇 테스트 메시지입니다.
|
||||
# props:
|
||||
# attachments:
|
||||
# - pretext: Mattermost Bot Test
|
||||
# text: 정상적으로 전송되면 이 메시지가 채널에 표시됩니다.
|
||||
|
||||
''';
|
||||
} else if (Platform.isWindows) {
|
||||
yaml = '''
|
||||
|
|
@ -269,6 +281,18 @@ commands:
|
|||
# downloadURL: http://toki-labs.com/build/tplayer/android/tplayer_arm64_<!common.version>.apk
|
||||
# channel: release
|
||||
|
||||
#- command: Mattermost
|
||||
# param:
|
||||
# url: https://mattermost.example.com/api/v4/posts
|
||||
# token: bot-token
|
||||
# channelId: n78a1wrdwjydzdoat6gci8cphe
|
||||
# message:
|
||||
# message: 봇 테스트 메시지입니다.
|
||||
# props:
|
||||
# attachments:
|
||||
# - pretext: Mattermost Bot Test
|
||||
# text: 정상적으로 전송되면 이 메시지가 채널에 표시됩니다.
|
||||
|
||||
''';
|
||||
} else if (Platform.isLinux) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:oto_cli/oto/commands/build/build_flutter.dart';
|
|||
import 'package:oto_cli/oto/commands/build/build_msbuild.dart';
|
||||
import 'package:oto_cli/oto/commands/command.dart';
|
||||
import 'package:oto_cli/oto/data/pipeline_data.dart';
|
||||
import 'package:oto_cli/oto/utils/mattermost/mattermost_sender.dart';
|
||||
part 'command_data.g.dart';
|
||||
|
||||
/// ***************************** Base Data ****************************///
|
||||
|
|
@ -624,6 +625,42 @@ class DataSlackBuild extends DataSlackSender {
|
|||
Map<String, dynamic> toJson() => _$DataSlackBuildToJson(this);
|
||||
}
|
||||
|
||||
//Mattermost
|
||||
@JsonSerializable()
|
||||
class DataMattermostSender extends DataParam {
|
||||
DataMattermostSender();
|
||||
|
||||
late String token;
|
||||
late String url;
|
||||
late String? channelId;
|
||||
late dynamic property;
|
||||
late Map<String, dynamic>? message;
|
||||
late String? messageJson;
|
||||
late Map<String, String>? idMap;
|
||||
|
||||
factory DataMattermostSender.fromJson(Map<String, dynamic> json) =>
|
||||
_$DataMattermostSenderFromJson(json);
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$DataMattermostSenderToJson(this);
|
||||
}
|
||||
|
||||
//Mattermost Build
|
||||
@JsonSerializable()
|
||||
class DataMattermostBuild extends DataMattermostSender {
|
||||
DataMattermostBuild();
|
||||
|
||||
late String appName;
|
||||
late MattermostBuildType type;
|
||||
late String color;
|
||||
late String version;
|
||||
late String downloadURL;
|
||||
|
||||
factory DataMattermostBuild.fromJson(Map<String, dynamic> json) =>
|
||||
_$DataMattermostBuildFromJson(json);
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$DataMattermostBuildToJson(this);
|
||||
}
|
||||
|
||||
//Jira
|
||||
@JsonSerializable()
|
||||
class DataJira extends DataParam {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ const _$CommandTypeEnumMap = {
|
|||
CommandType.Slack: 'Slack',
|
||||
CommandType.SlackFile: 'SlackFile',
|
||||
CommandType.SlackBuild: 'SlackBuild',
|
||||
CommandType.Mattermost: 'Mattermost',
|
||||
CommandType.MattermostBuild: 'MattermostBuild',
|
||||
CommandType.StringSub: 'StringSub',
|
||||
CommandType.StringReplace: 'StringReplace',
|
||||
CommandType.StringReplacePattern: 'StringReplacePattern',
|
||||
|
|
@ -1117,6 +1119,97 @@ const _$SlackBuildTypeEnumMap = {
|
|||
SlackBuildType.web: 'web',
|
||||
};
|
||||
|
||||
DataMattermostSender _$DataMattermostSenderFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
DataMattermostSender()
|
||||
..workspace = json['workspace'] as String?
|
||||
..passExitCodes = (json['passExitCodes'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList()
|
||||
..setResult = json['setResult'] as String?
|
||||
..setExitCode = json['setExitCode'] as String?
|
||||
..showPrint = json['showPrint'] as bool?
|
||||
..token = json['token'] as String
|
||||
..url = json['url'] as String
|
||||
..channelId = json['channelId'] as String?
|
||||
..property = json['property']
|
||||
..message = json['message'] as Map<String, dynamic>?
|
||||
..messageJson = json['messageJson'] as String?
|
||||
..idMap = (json['idMap'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DataMattermostSenderToJson(
|
||||
DataMattermostSender instance) =>
|
||||
<String, dynamic>{
|
||||
'workspace': instance.workspace,
|
||||
'passExitCodes': instance.passExitCodes,
|
||||
'setResult': instance.setResult,
|
||||
'setExitCode': instance.setExitCode,
|
||||
'showPrint': instance.showPrint,
|
||||
'token': instance.token,
|
||||
'url': instance.url,
|
||||
'channelId': instance.channelId,
|
||||
'property': instance.property,
|
||||
'message': instance.message,
|
||||
'messageJson': instance.messageJson,
|
||||
'idMap': instance.idMap,
|
||||
};
|
||||
|
||||
DataMattermostBuild _$DataMattermostBuildFromJson(Map<String, dynamic> json) =>
|
||||
DataMattermostBuild()
|
||||
..workspace = json['workspace'] as String?
|
||||
..passExitCodes = (json['passExitCodes'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList()
|
||||
..setResult = json['setResult'] as String?
|
||||
..setExitCode = json['setExitCode'] as String?
|
||||
..showPrint = json['showPrint'] as bool?
|
||||
..token = json['token'] as String
|
||||
..url = json['url'] as String
|
||||
..channelId = json['channelId'] as String?
|
||||
..property = json['property']
|
||||
..message = json['message'] as Map<String, dynamic>?
|
||||
..messageJson = json['messageJson'] as String?
|
||||
..idMap = (json['idMap'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
)
|
||||
..appName = json['appName'] as String
|
||||
..type = $enumDecode(_$MattermostBuildTypeEnumMap, json['type'])
|
||||
..color = json['color'] as String
|
||||
..version = json['version'] as String
|
||||
..downloadURL = json['downloadURL'] as String;
|
||||
|
||||
Map<String, dynamic> _$DataMattermostBuildToJson(
|
||||
DataMattermostBuild instance) =>
|
||||
<String, dynamic>{
|
||||
'workspace': instance.workspace,
|
||||
'passExitCodes': instance.passExitCodes,
|
||||
'setResult': instance.setResult,
|
||||
'setExitCode': instance.setExitCode,
|
||||
'showPrint': instance.showPrint,
|
||||
'token': instance.token,
|
||||
'url': instance.url,
|
||||
'channelId': instance.channelId,
|
||||
'property': instance.property,
|
||||
'message': instance.message,
|
||||
'messageJson': instance.messageJson,
|
||||
'idMap': instance.idMap,
|
||||
'appName': instance.appName,
|
||||
'type': _$MattermostBuildTypeEnumMap[instance.type]!,
|
||||
'color': instance.color,
|
||||
'version': instance.version,
|
||||
'downloadURL': instance.downloadURL,
|
||||
};
|
||||
|
||||
const _$MattermostBuildTypeEnumMap = {
|
||||
MattermostBuildType.android: 'android',
|
||||
MattermostBuildType.ios: 'ios',
|
||||
MattermostBuildType.windows: 'windows',
|
||||
MattermostBuildType.mac: 'mac',
|
||||
MattermostBuildType.web: 'web',
|
||||
};
|
||||
|
||||
DataJira _$DataJiraFromJson(Map<String, dynamic> json) => DataJira()
|
||||
..workspace = json['workspace'] as String?
|
||||
..passExitCodes = (json['passExitCodes'] as List<dynamic>?)
|
||||
|
|
|
|||
246
lib/oto/utils/mattermost/mattermost_data.dart
Normal file
246
lib/oto/utils/mattermost/mattermost_data.dart
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
// ignore_for_file: non_constant_identifier_names
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
class DataMattermost {
|
||||
String? channel_id;
|
||||
String? channel;
|
||||
String? message;
|
||||
String? text;
|
||||
String? username;
|
||||
String? icon_url;
|
||||
String? icon_emoji;
|
||||
String? type;
|
||||
Map<String, dynamic>? props;
|
||||
List<MattermostAttachment>? attachments;
|
||||
|
||||
String get json {
|
||||
return jsonEncode(toPostJson());
|
||||
}
|
||||
|
||||
DataMattermost();
|
||||
|
||||
factory DataMattermost.fromJson(Map<String, dynamic> json) {
|
||||
final data = DataMattermost();
|
||||
data.channel_id = json['channel_id'] as String?;
|
||||
data.channel = json['channel'] as String?;
|
||||
data.message = json['message'] as String?;
|
||||
data.text = json['text'] as String?;
|
||||
data.username = json['username'] as String?;
|
||||
data.icon_url = json['icon_url'] as String?;
|
||||
data.icon_emoji = json['icon_emoji'] as String?;
|
||||
data.type = json['type'] as String?;
|
||||
if (json['props'] is Map<String, dynamic>) {
|
||||
data.props =
|
||||
Map<String, dynamic>.from(json['props'] as Map<String, dynamic>);
|
||||
} else if (json['props'] is Map) {
|
||||
data.props = Map<String, dynamic>.from(json['props'] as Map);
|
||||
}
|
||||
if (json['attachments'] is List) {
|
||||
data.attachments = (json['attachments'] as List)
|
||||
.map((item) => MattermostAttachment.fromJson(
|
||||
Map<String, dynamic>.from(item as Map)))
|
||||
.toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return removeEmptyKeys({
|
||||
'channel_id': channel_id,
|
||||
'channel': channel,
|
||||
'message': message,
|
||||
'text': text,
|
||||
'username': username,
|
||||
'icon_url': icon_url,
|
||||
'icon_emoji': icon_emoji,
|
||||
'type': type,
|
||||
'props': props,
|
||||
'attachments': attachments?.map((item) => item.toJson()).toList(),
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, dynamic> toPostJson({String? defaultChannelId}) {
|
||||
return normalizeMattermostPost(toJson(),
|
||||
defaultChannelId: defaultChannelId);
|
||||
}
|
||||
}
|
||||
|
||||
class MattermostAttachment {
|
||||
String? fallback;
|
||||
String? color;
|
||||
String? pretext;
|
||||
String? text;
|
||||
String? author_name;
|
||||
String? author_link;
|
||||
String? author_icon;
|
||||
String? title;
|
||||
String? title_link;
|
||||
String? image_url;
|
||||
String? thumb_url;
|
||||
String? footer;
|
||||
String? footer_icon;
|
||||
List<MattermostField>? fields;
|
||||
List<Map<String, dynamic>>? actions;
|
||||
|
||||
MattermostAttachment();
|
||||
|
||||
factory MattermostAttachment.fromJson(Map<String, dynamic> json) {
|
||||
final data = MattermostAttachment();
|
||||
data.fallback = json['fallback'] as String?;
|
||||
data.color = json['color'] as String?;
|
||||
data.pretext = json['pretext'] as String?;
|
||||
data.text = json['text'] as String?;
|
||||
data.author_name = json['author_name'] as String?;
|
||||
data.author_link = json['author_link'] as String?;
|
||||
data.author_icon = json['author_icon'] as String?;
|
||||
data.title = json['title'] as String?;
|
||||
data.title_link = json['title_link'] as String?;
|
||||
data.image_url = json['image_url'] as String?;
|
||||
data.thumb_url = json['thumb_url'] as String?;
|
||||
data.footer = json['footer'] as String?;
|
||||
data.footer_icon = json['footer_icon'] as String?;
|
||||
if (json['fields'] is List) {
|
||||
data.fields = (json['fields'] as List)
|
||||
.map((item) =>
|
||||
MattermostField.fromJson(Map<String, dynamic>.from(item as Map)))
|
||||
.toList();
|
||||
}
|
||||
if (json['actions'] is List) {
|
||||
data.actions = (json['actions'] as List)
|
||||
.map((item) => Map<String, dynamic>.from(item as Map))
|
||||
.toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return removeEmptyKeys({
|
||||
'fallback': fallback,
|
||||
'color': color,
|
||||
'pretext': pretext,
|
||||
'text': text,
|
||||
'author_name': author_name,
|
||||
'author_link': author_link,
|
||||
'author_icon': author_icon,
|
||||
'title': title,
|
||||
'title_link': title_link,
|
||||
'image_url': image_url,
|
||||
'thumb_url': thumb_url,
|
||||
'footer': footer,
|
||||
'footer_icon': footer_icon,
|
||||
'fields': fields?.map((item) => item.toJson()).toList(),
|
||||
'actions': actions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MattermostField {
|
||||
String? title;
|
||||
String? value;
|
||||
bool short = false;
|
||||
|
||||
MattermostField();
|
||||
MattermostField.withParam(this.title, this.value, {this.short = false});
|
||||
|
||||
factory MattermostField.fromJson(Map<String, dynamic> json) {
|
||||
final data = MattermostField();
|
||||
data.title = json['title'] as String?;
|
||||
data.value = json['value'] as String?;
|
||||
data.short = json['short'] as bool? ?? false;
|
||||
return data;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return removeEmptyKeys({
|
||||
'title': title,
|
||||
'value': value,
|
||||
'short': short,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> normalizeMattermostPost(Map<String, dynamic> payload,
|
||||
{String? defaultChannelId}) {
|
||||
final map = Map<String, dynamic>.from(payload);
|
||||
final props = map['props'] is Map<String, dynamic>
|
||||
? Map<String, dynamic>.from(map['props'] as Map<String, dynamic>)
|
||||
: map['props'] is Map
|
||||
? Map<String, dynamic>.from(map['props'] as Map)
|
||||
: <String, dynamic>{};
|
||||
|
||||
if ((map['channel_id'] == null || '${map['channel_id']}'.isEmpty)) {
|
||||
final channel = map.remove('channel');
|
||||
if (channel != null && '$channel'.isNotEmpty) {
|
||||
map['channel_id'] = channel;
|
||||
} else if (defaultChannelId != null && defaultChannelId.isNotEmpty) {
|
||||
map['channel_id'] = defaultChannelId;
|
||||
}
|
||||
} else {
|
||||
map.remove('channel');
|
||||
}
|
||||
|
||||
if ((map['message'] == null || '${map['message']}'.isEmpty) &&
|
||||
map['text'] != null) {
|
||||
map['message'] = map.remove('text');
|
||||
} else {
|
||||
map.remove('text');
|
||||
}
|
||||
|
||||
_moveValueToProps(map, props, 'attachments', 'attachments');
|
||||
_moveValueToProps(map, props, 'username', 'override_username');
|
||||
_moveValueToProps(map, props, 'icon_url', 'override_icon_url');
|
||||
_moveValueToProps(map, props, 'icon_emoji', 'override_icon_emoji');
|
||||
|
||||
if (props.isNotEmpty) {
|
||||
map['props'] = props;
|
||||
} else {
|
||||
map.remove('props');
|
||||
}
|
||||
|
||||
return removeEmptyKeys(map);
|
||||
}
|
||||
|
||||
void _moveValueToProps(
|
||||
Map<String, dynamic> source,
|
||||
Map<String, dynamic> props,
|
||||
String sourceKey,
|
||||
String targetKey,
|
||||
) {
|
||||
if (props[targetKey] != null) {
|
||||
source.remove(sourceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
final value = source.remove(sourceKey);
|
||||
if (value != null) {
|
||||
props[targetKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> removeEmptyKeys(Map<String, dynamic> map) {
|
||||
final emptyKeys = <String>[];
|
||||
for (final item in map.entries) {
|
||||
final value = item.value;
|
||||
if (value == null) {
|
||||
emptyKeys.add(item.key);
|
||||
continue;
|
||||
}
|
||||
if (value is String && value.isEmpty) {
|
||||
emptyKeys.add(item.key);
|
||||
continue;
|
||||
}
|
||||
if (value is List && value.isEmpty) {
|
||||
emptyKeys.add(item.key);
|
||||
continue;
|
||||
}
|
||||
if (value is Map && value.isEmpty) {
|
||||
emptyKeys.add(item.key);
|
||||
}
|
||||
}
|
||||
|
||||
for (final key in emptyKeys) {
|
||||
map.remove(key);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
113
lib/oto/utils/mattermost/mattermost_sender.dart
Normal file
113
lib/oto/utils/mattermost/mattermost_sender.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:color/color.dart';
|
||||
import 'package:dart_framework/utils/string_util.dart';
|
||||
import 'package:dart_framework/utils/system_util.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:oto_cli/oto/utils/mattermost/mattermost_data.dart';
|
||||
|
||||
enum MattermostBuildType { android, ios, windows, mac, web }
|
||||
|
||||
class MattermostSender {
|
||||
final String token;
|
||||
final String url;
|
||||
|
||||
MattermostSender(this.token, this.url);
|
||||
|
||||
Future sendData(DataMattermost message,
|
||||
{String? defaultChannelId,
|
||||
Function(String)? completeListener,
|
||||
Function(Object)? errorListener}) async {
|
||||
return sendMap(message.toPostJson(defaultChannelId: defaultChannelId),
|
||||
completeListener: completeListener, errorListener: errorListener);
|
||||
}
|
||||
|
||||
Future sendMap(Map<String, dynamic> payload,
|
||||
{Function(String)? completeListener,
|
||||
Function(Object)? errorListener}) async {
|
||||
return send(jsonEncode(payload),
|
||||
completeListener: completeListener, errorListener: errorListener);
|
||||
}
|
||||
|
||||
Future send(String jsonString,
|
||||
{Function(String)? completeListener,
|
||||
Function(Object)? errorListener}) async {
|
||||
final decoded = jsonDecode(jsonString);
|
||||
if (decoded is! Map) {
|
||||
throw Exception('Mattermost payload must be a JSON object.');
|
||||
}
|
||||
|
||||
final payload = normalizeMattermostPost(Map<String, dynamic>.from(decoded));
|
||||
final header = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $token',
|
||||
};
|
||||
final encoded = jsonEncode(payload);
|
||||
print(encoded);
|
||||
final response =
|
||||
await http.post(Uri.parse(url), headers: header, body: encoded);
|
||||
|
||||
print(response.statusCode);
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
print(response.body);
|
||||
if (completeListener != null) {
|
||||
completeListener(response.body);
|
||||
}
|
||||
} else {
|
||||
print(response.body);
|
||||
if (errorListener != null) {
|
||||
errorListener(response);
|
||||
}
|
||||
throw Exception(
|
||||
'Mattermost post failed (${response.statusCode}): ${response.body}');
|
||||
}
|
||||
|
||||
return simpleFuture;
|
||||
}
|
||||
|
||||
Future sendBuild(String appName, MattermostBuildType buildType, Color color,
|
||||
String version, String downloadURL,
|
||||
{required String channelId}) async {
|
||||
final buildURL = 'https://toki-labs.com/cdn/build/';
|
||||
final thumbUrl = buildType == MattermostBuildType.android
|
||||
? '${buildURL}android.png'
|
||||
: '${buildURL}appstore.png';
|
||||
final projectIcon = '$buildURL$appName/icon.png';
|
||||
final buildTypeStr = buildTypeToString(buildType);
|
||||
final osColor =
|
||||
buildType == MattermostBuildType.ios ? '#ED145B' : '#81CA3F';
|
||||
|
||||
final message = DataMattermost()
|
||||
..channel_id = channelId
|
||||
..message = '${toPascal(appName)} $buildTypeStr build is ready.'
|
||||
..props = {
|
||||
'attachments': [
|
||||
MattermostAttachment()
|
||||
..color = color.toHexColor().toCssString()
|
||||
..fields = [
|
||||
MattermostField.withParam(
|
||||
toPascal(appName), 'App: ${toPascal(appName)}')
|
||||
],
|
||||
MattermostAttachment()
|
||||
..color = osColor
|
||||
..author_name = buildTypeStr
|
||||
..author_icon = thumbUrl
|
||||
..thumb_url = projectIcon
|
||||
..title = 'Download'
|
||||
..title_link = downloadURL
|
||||
..text =
|
||||
'Successfully built $buildTypeStr binary file.\nBuild version: **$version**'
|
||||
..fields = [MattermostField.withParam('Download URL', downloadURL)],
|
||||
].map((item) => item.toJson()).toList(),
|
||||
};
|
||||
|
||||
return sendData(message);
|
||||
}
|
||||
|
||||
String buildTypeToString(MattermostBuildType type) {
|
||||
final typeStr = type.name;
|
||||
return typeStr == 'ios' ? 'iOS' : toPascal(typeStr);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue