diff --git a/lib/utils/slack/slack_data.dart b/lib/utils/slack/slack_data.dart index 3a36b35..aae410e 100644 --- a/lib/utils/slack/slack_data.dart +++ b/lib/utils/slack/slack_data.dart @@ -1,113 +1,136 @@ +// ignore_for_file: non_constant_identifier_names, avoid_init_to_null, constant_identifier_names +// ignore: depend_on_referenced_packages, implementation_imports + import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:json_annotation/json_annotation.dart'; import 'package:http/http.dart'; -// ignore: depend_on_referenced_packages, implementation_imports import 'package:http_parser/src/media_type.dart'; part 'slack_data.g.dart'; -enum SlackBlockType { header, section } +enum SlackBlockType { header, section, divider, actions } enum SlackTextType { mrkdwn, plain_text } -enum SlackActionType { button, select } +enum SlackActionType { button, select, image } enum SlackActionStyle { danger, primary } @JsonSerializable() class DataSlack { - String? message; - List blocks = []; - List attachments = []; + String channel; + String? text; + late List? blocks = null; + late List? attachments = null; + bool? link_names; - String getBlocks() { - var list = []; - for (var item in blocks) { - list.add(item.toJson()); - } - return jsonEncode(list); + String get json + { + return jsonEncode(toJson()); } - String getAttachments() { - var list = []; - for (var item in attachments) { - list.add(item.toJson()); - } - return jsonEncode(list); - } - - DataSlack(); + DataSlack(this.channel); factory DataSlack.fromJson(Map json) => _$DataSlackFromJson(json); - Map toJson() => _$DataSlackToJson(this); + Map toJson() { + return removeKeys(_$DataSlackToJson(this)); + } +} + +Map removeKeys(Map map) +{ + var emptyKeys = []; + for(var item in map.entries) { + if(item.value == null) { + emptyKeys.add(item.key); + } + if(item.value is List) { + var list = item.value as List; + if(list.isEmpty) { + emptyKeys.add(item.key); + } + } + } + + for(var key in emptyKeys) { + map.remove(key); + } + return map; } @JsonSerializable() class SlackBlock { late SlackBlockType type; + late String? block_id = null; SlackBlock(); factory SlackBlock.fromJson(Map json) => _$SlackBlockFromJson(json); - Map toJson() => _$SlackBlockToJson(this); + Map toJson() => removeKeys(_$SlackBlockToJson(this)); } @JsonSerializable() class SlackBlockHeader extends SlackBlock { - late SlackBlockText text; + late SlackBlockText? text; SlackBlockHeader() { super.type = SlackBlockType.header; - text = SlackBlockText(); - text.type = SlackTextType.plain_text; } factory SlackBlockHeader.fromJson(Map json) => _$SlackBlockHeaderFromJson(json); @override - Map toJson() => _$SlackBlockHeaderToJson(this); + Map toJson() => removeKeys(_$SlackBlockHeaderToJson(this)); } @JsonSerializable() class SlackBlockSection extends SlackBlock { - List fields = []; + late SlackBlockText? text = null; + late List? fields = null; + late SlackAccessory? accessory = null; - SlackBlockSection() { - super.type = SlackBlockType.section; + SlackBlockSection() + { + type = SlackBlockType.section; } factory SlackBlockSection.fromJson(Map json) => _$SlackBlockSectionFromJson(json); @override - Map toJson() => _$SlackBlockSectionToJson(this); + Map toJson() => removeKeys(_$SlackBlockSectionToJson(this)); } @JsonSerializable() -class SlackBlockSectionField { - SlackTextType type = SlackTextType.mrkdwn; - String? text; +class SlackAccessory { + late SlackActionType type; + late SlackBlockText? text = null; + late String? image_url = null; + late String? alt_text = null; + late String? value = null; + late String? url = null; - SlackBlockSectionField(); + SlackAccessory(); - factory SlackBlockSectionField.fromJson(Map json) => - _$SlackBlockSectionFieldFromJson(json); - Map toJson() => _$SlackBlockSectionFieldToJson(this); + factory SlackAccessory.fromJson(Map json) => + _$SlackAccessoryFromJson(json); + Map toJson() => removeKeys(_$SlackAccessoryToJson(this)); } @JsonSerializable() class SlackBlockText { SlackTextType type = SlackTextType.mrkdwn; - String? text; + late String? text = null; + late bool? emoji = null; SlackBlockText(); factory SlackBlockText.fromJson(Map json) => _$SlackBlockTextFromJson(json); - Map toJson() => _$SlackBlockTextToJson(this); + Map toJson() => removeKeys(_$SlackBlockTextToJson(this)); } @JsonSerializable() @@ -127,27 +150,29 @@ class SlackAttachment { String? footer_icon; String? ts; - List? fields; - List? actions; + late List? blocks = null; + late List? fields = null; + late List? actions = null; SlackAttachment(); factory SlackAttachment.fromJson(Map json) => _$SlackAttachmentFromJson(json); - Map toJson() => _$SlackAttachmentToJson(this); + Map toJson() => removeKeys(_$SlackAttachmentToJson(this)); } @JsonSerializable() class SlackField { String? title; String? value; + bool? short = false; SlackField(); - SlackField.withParam(this.title, this.value); + SlackField.withParam(this.title, this.value, {this.short}); factory SlackField.fromJson(Map json) => _$SlackFieldFromJson(json); - Map toJson() => _$SlackFieldToJson(this); + Map toJson() => removeKeys(_$SlackFieldToJson(this)); } @JsonSerializable() @@ -167,22 +192,19 @@ class SlackAction { factory SlackAction.fromJson(Map json) => _$SlackActionFromJson(json); - Map toJson() => _$SlackActionToJson(this); + Map toJson() => removeKeys(_$SlackActionToJson(this)); } @JsonSerializable() class SlackConfirm { String? title; String? text; - // ignore: non_constant_identifier_names String? ok_text; - // ignore: non_constant_identifier_names String? dismiss_text; } class SlackFile { String? title; - // ignore: non_constant_identifier_names String? initial_comment; String? fileName; String? mimeType; diff --git a/lib/utils/slack/slack_data.g.dart b/lib/utils/slack/slack_data.g.dart index e003b61..ce144da 100644 --- a/lib/utils/slack/slack_data.g.dart +++ b/lib/utils/slack/slack_data.g.dart @@ -6,70 +6,119 @@ part of 'slack_data.dart'; // JsonSerializableGenerator // ************************************************************************** -DataSlack _$DataSlackFromJson(Map json) => DataSlack() - ..message = json['message'] as String? - ..blocks = (json['blocks'] as List) - .map((e) => SlackBlock.fromJson(e as Map)) - .toList() - ..attachments = (json['attachments'] as List) - .map((e) => SlackAttachment.fromJson(e as Map)) - .toList(); +DataSlack _$DataSlackFromJson(Map json) => DataSlack( + json['channel'] as String, + ) + ..text = json['text'] as String? + ..blocks = (json['blocks'] as List?) + ?.map((e) => SlackBlock.fromJson(e as Map)) + .toList() + ..attachments = (json['attachments'] as List?) + ?.map((e) => SlackAttachment.fromJson(e as Map)) + .toList() + ..link_names = json['link_names'] as bool?; Map _$DataSlackToJson(DataSlack instance) => { - 'message': instance.message, + 'channel': instance.channel, + 'text': instance.text, 'blocks': instance.blocks, 'attachments': instance.attachments, + 'link_names': instance.link_names, }; -SlackBlock _$SlackBlockFromJson(Map json) => - SlackBlock()..type = $enumDecode(_$SlackBlockTypeEnumMap, json['type']); +SlackBlock _$SlackBlockFromJson(Map json) => SlackBlock() + ..type = $enumDecode(_$SlackBlockTypeEnumMap, json['type']) + ..block_id = json['block_id'] as String?; Map _$SlackBlockToJson(SlackBlock instance) => { 'type': _$SlackBlockTypeEnumMap[instance.type]!, + 'block_id': instance.block_id, }; const _$SlackBlockTypeEnumMap = { SlackBlockType.header: 'header', SlackBlockType.section: 'section', + SlackBlockType.divider: 'divider', + SlackBlockType.actions: 'actions', }; SlackBlockHeader _$SlackBlockHeaderFromJson(Map json) => SlackBlockHeader() ..type = $enumDecode(_$SlackBlockTypeEnumMap, json['type']) - ..text = SlackBlockText.fromJson(json['text'] as Map); + ..block_id = json['block_id'] as String? + ..text = json['text'] == null + ? null + : SlackBlockText.fromJson(json['text'] as Map); Map _$SlackBlockHeaderToJson(SlackBlockHeader instance) => { 'type': _$SlackBlockTypeEnumMap[instance.type]!, + 'block_id': instance.block_id, 'text': instance.text, }; SlackBlockSection _$SlackBlockSectionFromJson(Map json) => SlackBlockSection() ..type = $enumDecode(_$SlackBlockTypeEnumMap, json['type']) - ..fields = (json['fields'] as List) - .map( - (e) => SlackBlockSectionField.fromJson(e as Map)) - .toList(); + ..block_id = json['block_id'] as String? + ..text = json['text'] == null + ? null + : SlackBlockText.fromJson(json['text'] as Map) + ..fields = (json['fields'] as List?) + ?.map((e) => SlackBlockText.fromJson(e as Map)) + .toList() + ..accessory = json['accessory'] == null + ? null + : SlackAccessory.fromJson(json['accessory'] as Map); Map _$SlackBlockSectionToJson(SlackBlockSection instance) => { 'type': _$SlackBlockTypeEnumMap[instance.type]!, + 'block_id': instance.block_id, + 'text': instance.text, 'fields': instance.fields, + 'accessory': instance.accessory, }; -SlackBlockSectionField _$SlackBlockSectionFieldFromJson( - Map json) => - SlackBlockSectionField() - ..type = $enumDecode(_$SlackTextTypeEnumMap, json['type']) - ..text = json['text'] as String?; +SlackAccessory _$SlackAccessoryFromJson(Map json) => + SlackAccessory() + ..type = $enumDecode(_$SlackActionTypeEnumMap, json['type']) + ..text = json['text'] == null + ? null + : SlackBlockText.fromJson(json['text'] as Map) + ..image_url = json['image_url'] as String? + ..alt_text = json['alt_text'] as String? + ..value = json['value'] as String? + ..url = json['url'] as String?; -Map _$SlackBlockSectionFieldToJson( - SlackBlockSectionField instance) => +Map _$SlackAccessoryToJson(SlackAccessory instance) => + { + 'type': _$SlackActionTypeEnumMap[instance.type]!, + 'text': instance.text, + 'image_url': instance.image_url, + 'alt_text': instance.alt_text, + 'value': instance.value, + 'url': instance.url, + }; + +const _$SlackActionTypeEnumMap = { + SlackActionType.button: 'button', + SlackActionType.select: 'select', + SlackActionType.image: 'image', +}; + +SlackBlockText _$SlackBlockTextFromJson(Map json) => + SlackBlockText() + ..type = $enumDecode(_$SlackTextTypeEnumMap, json['type']) + ..text = json['text'] as String? + ..emoji = json['emoji'] as bool?; + +Map _$SlackBlockTextToJson(SlackBlockText instance) => { 'type': _$SlackTextTypeEnumMap[instance.type]!, 'text': instance.text, + 'emoji': instance.emoji, }; const _$SlackTextTypeEnumMap = { @@ -77,17 +126,6 @@ const _$SlackTextTypeEnumMap = { SlackTextType.plain_text: 'plain_text', }; -SlackBlockText _$SlackBlockTextFromJson(Map json) => - SlackBlockText() - ..type = $enumDecode(_$SlackTextTypeEnumMap, json['type']) - ..text = json['text'] as String?; - -Map _$SlackBlockTextToJson(SlackBlockText instance) => - { - 'type': _$SlackTextTypeEnumMap[instance.type]!, - 'text': instance.text, - }; - SlackAttachment _$SlackAttachmentFromJson(Map json) => SlackAttachment() ..fallback = json['fallback'] as String? @@ -104,6 +142,9 @@ SlackAttachment _$SlackAttachmentFromJson(Map json) => ..footer = json['footer'] as String? ..footer_icon = json['footer_icon'] as String? ..ts = json['ts'] as String? + ..blocks = (json['blocks'] as List?) + ?.map((e) => SlackBlock.fromJson(e as Map)) + .toList() ..fields = (json['fields'] as List?) ?.map((e) => SlackField.fromJson(e as Map)) .toList() @@ -127,18 +168,21 @@ Map _$SlackAttachmentToJson(SlackAttachment instance) => 'footer': instance.footer, 'footer_icon': instance.footer_icon, 'ts': instance.ts, + 'blocks': instance.blocks, 'fields': instance.fields, 'actions': instance.actions, }; SlackField _$SlackFieldFromJson(Map json) => SlackField() ..title = json['title'] as String? - ..value = json['value'] as String?; + ..value = json['value'] as String? + ..short = json['short'] as bool?; Map _$SlackFieldToJson(SlackField instance) => { 'title': instance.title, 'value': instance.value, + 'short': instance.short, }; SlackAction _$SlackActionFromJson(Map json) => SlackAction() @@ -159,11 +203,6 @@ Map _$SlackActionToJson(SlackAction instance) => 'style': _$SlackActionStyleEnumMap[instance.style], }; -const _$SlackActionTypeEnumMap = { - SlackActionType.button: 'button', - SlackActionType.select: 'select', -}; - const _$SlackActionStyleEnumMap = { SlackActionStyle.danger: 'danger', SlackActionStyle.primary: 'primary', diff --git a/lib/utils/slack/slack_sender.dart b/lib/utils/slack/slack_sender.dart index 03fef5b..308d38d 100644 --- a/lib/utils/slack/slack_sender.dart +++ b/lib/utils/slack/slack_sender.dart @@ -1,6 +1,9 @@ +// ignore_for_file: unused_local_variable + import 'dart:async'; import 'package:color/color.dart'; +import 'package:dart_framework/utils/system_util.dart'; import 'package:http/http.dart' as http; import 'slack_data.dart'; @@ -10,80 +13,56 @@ enum SlackBuildType { android, ios, windows, mac, web } class SlackSender { //Bot TOKEN - final token = "xoxb-291006091297-QT2K1PCpwyX4b5wKk3AQew2G"; + String token = "xoxb-291006091297-QT2K1PCpwyX4b5wKk3AQew2G"; //API - https://api.slack.com/methods/chat.postMessage final urlMessagePost = "https://slack.com/api/chat.postMessage"; //API - https://api.slack.com/methods/files.upload final urlFileUplaod = "https://slack.com/api/files.upload"; + SlackSender(String? token) { + if(token != null) { + this.token = token; + } + } + String getValidatedUser(String? targetUser) { - targetUser ??= "@channel"; + targetUser ??= "@here"; if (targetUser.indexOf("@") != 0) targetUser = "@$targetUser"; return targetUser; } - String getURL(String channel, DataSlack message, String? targetUser, - {bool includeURL = true}) { - targetUser = getValidatedUser(targetUser); - var buffer = StringBuffer(); - if (includeURL) { - buffer.write(urlMessagePost); - buffer.write('?'); - } - buffer.write('token=$token'); - buffer.write('&channel=$channel'); - var sendMessage = message.message == null - ? '' - : Uri.encodeComponent(' ${message.message!}'); - buffer.write('&text=$targetUser$sendMessage'); - if (message.blocks.isNotEmpty) { - var json = message.getBlocks(); - print(json); - buffer.write('&blocks=${Uri.encodeComponent(message.getBlocks())}'); - } - if (message.attachments.isNotEmpty) { - buffer.write( - '&attachments=${Uri.encodeComponent(message.getAttachments())}'); - } - buffer.write('&link_names=true'); - print(buffer.toString()); - return buffer.toString(); - } - - //단일 Attachment로 보낼시 - Future send(String channel, SlackAttachment attachment, - {String? targetUser, - Function(String)? completeListener, - Function(Exception)? errorListener}) async { - var data = DataSlack(); - data.attachments = [attachment]; - return await sends(channel, data, targetUser: targetUser); - } - - //복수의 Attachment로 보낼시 - Future sends(String channel, DataSlack message, - {String? targetUser, - Function(String)? completeListener, + Future send(DataSlack message, + {Function(String)? completeListener, Function(Object)? errorListener}) async { - var c = Completer(); - String url = getURL(channel, message, targetUser); + String url = urlMessagePost; - var response = await http.get(Uri.parse(url)); + var header = + { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token' + }; + var body = message.json; + print(body); + var response = await http.post(Uri.parse(url), headers: header, body: message.json); + + print(response.statusCode); if (response.statusCode < 300 && response.statusCode >= 200) { print(response.body); - if (completeListener != null) completeListener(response.body); + if (completeListener != null) + { + completeListener(response.body); + print(response.body); + } } else { - if (errorListener != null) errorListener(response); + if (errorListener != null) + { + errorListener(response); + print(response.body); + } } - c.complete(); - return c.future; - } - - String getParameters(String channel, DataSlack message, - {String? targetUser}) { - return getURL(channel, message, targetUser, includeURL: false); + return simpleFuture; } /// ****************** File Send ******************* @@ -100,7 +79,6 @@ class SlackSender { {String? targetUser, Function(String)? completeListener, Function(Object)? errorListener}) async { - var c = Completer(); var form = getPostForm(urlFileUplaod, channel, targetUser); form = await file.setFileData(form); var response = await form.send(); @@ -112,36 +90,10 @@ class SlackSender { if (errorListener != null) errorListener(result); } - c.complete(); - return c.future; - } - - /// ****************** Send template ******************* - void sendTest() async { - DataSlack message = DataSlack(); - message.attachments = []; - var attachment = SlackAttachment(); - attachment.author_name = 'toki'; - attachment.text = 'This is Test!!'; - attachment.color = '#ff2f00'; - attachment.fields = [ - SlackField.withParam('Test1', 'Value1'), - SlackField.withParam('Test2', 'Value2'), - ]; - message.attachments.add(attachment); - - attachment = SlackAttachment(); - attachment.author_name = 'toki'; - attachment.text = 'This is Test!!2'; - attachment.color = '#0084ff'; - attachment.fields = [ - SlackField.withParam('Test3', 'Value3'), - SlackField.withParam('Test4', 'Value4'), - ]; - message.attachments.add(attachment); - await sends('app_notifier', message, targetUser: 'toki'); + return simpleFuture; } + /// ****************** Build Send ******************* Future sendBuild(String appName, SlackBuildType buildType, Color color, String version, String downloadURL, {String channel = 'build'}) async { @@ -150,17 +102,17 @@ class SlackSender { ? '${buildURL}android.png' : '${buildURL}appstore.png'; String projectIcon = "$buildURL$appName/icon.png"; - // ignore: unused_local_variable String emoji = buildType == SlackBuildType.android ? ":android1: " : ":ios0: "; String osColor = buildType == SlackBuildType.ios ? "#ED145B" : "#81CA3F"; - var message = DataSlack(); + var message = DataSlack(channel); + message.attachments = []; var attachments = message.attachments; var attachment = SlackAttachment(); attachment.color = color.toHexColor().toCssString(); attachment.fields = [SlackField.withParam(toPascal(appName), null)]; - attachments.add(attachment); + attachments?.add(attachment); var buildTypeStr = buildTypeToString(buildType); attachment = SlackAttachment(); @@ -176,9 +128,9 @@ class SlackSender { SlackAction.withParam( 'build', 'Download', downloadURL, null, SlackActionType.button, null) ]; - attachments.add(attachment); + attachments?.add(attachment); - return await sends(channel, message); + return await send(message); } String buildTypeToString(SlackBuildType type) { diff --git a/lib/utils/slack/slack_template.dart b/lib/utils/slack/slack_template.dart new file mode 100644 index 0000000..1d05cd2 --- /dev/null +++ b/lib/utils/slack/slack_template.dart @@ -0,0 +1,97 @@ + +import 'package:dart_framework/utils/slack/slack_data.dart'; + +//Format info +//https://api.slack.com/reference/surfaces/formatting + +class SlackTemplate { + static DataSlack getAttachments(String channel) { + DataSlack data = DataSlack(channel); + + data.attachments = []; + var attachment = SlackAttachment(); + attachment.fallback = 'Plain-text summary of the attachment.'; + attachment.color = '#2eb886'; + attachment.pretext = 'Optional text that appears above the attachment block'; + attachment.author_name = 'Bobby Tables'; + attachment.author_link = 'http://flickr.com/bobby/'; + attachment.author_icon = 'http://flickr.com/icons/bobby.jpg'; + attachment.title = 'Slack API Documentation'; + attachment.title_link = 'https://api.slack.com/'; + attachment.text = 'Optional text that appears within the attachment'; + attachment.fields = [ + SlackField.withParam('Priority', 'High', short: false), + ]; + attachment.image_url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Flat_tick_icon.svg/1024px-Flat_tick_icon.svg.png'; + attachment.thumb_url = 'https://cdn-icons-png.flaticon.com/512/3771/3771140.png'; + attachment.footer = 'Slack API'; + attachment.footer_icon = 'https://platform.slack-edge.com/img/default_application_icon.png'; + attachment.ts = '123456789'; + data.attachments!.add(attachment); + return data; + } + + static DataSlack getBlocks(String channel) { + DataSlack data = DataSlack(channel); + + data.attachments = []; + var attachment = SlackAttachment(); + data.blocks = []; + var blockSection = SlackBlockSection(); + blockSection.text = SlackBlockText(); + blockSection.text!.text = 'Danny Torrence left the following review for your property:'; + data.blocks!.add(blockSection); + + blockSection = SlackBlockSection(); + blockSection.text = SlackBlockText(); + blockSection.text!.text = ' \n :star: \n Doors had too many axe holes, guest in room 237 was far too rowdy, whole place felt stuck in the 1920s.'; + blockSection.accessory = SlackAccessory(); + blockSection.accessory!.type = SlackActionType.image; + blockSection.accessory!.image_url = 'https://is5-ssl.mzstatic.com/image/thumb/Purple3/v4/d3/72/5c/d3725c8f-c642-5d69-1904-aa36e4297885/source/256x256bb.jpg'; + blockSection.accessory!.alt_text = 'Haunted hotel image'; + data.blocks!.add(blockSection); + + blockSection = SlackBlockSection(); + blockSection.block_id = 'section789'; + blockSection.fields = []; + var field = SlackBlockText(); + field.text = '*Average Rating*\n1.0'; + blockSection.fields!.add(field); + data.blocks!.add(blockSection); + + attachment.blocks = []; + blockSection = SlackBlockSection(); + blockSection.text = SlackBlockText(); + blockSection.text!.text = '*Alternative hotel options*'; + attachment.blocks!.add(blockSection); + + blockSection = SlackBlockSection(); + blockSection.text = SlackBlockText(); + blockSection.text!.text = ' :star::star:'; + blockSection.accessory = SlackAccessory(); + blockSection.accessory!.type = SlackActionType.button; + blockSection.accessory!.url = 'https://www.google.com'; + blockSection.accessory!.text = SlackBlockText(); + blockSection.accessory!.text!.type = SlackTextType.plain_text; + blockSection.accessory!.text!.text = 'View'; + blockSection.accessory!.text!.emoji = true; + blockSection.accessory!.value = 'view_alternate_1'; + attachment.blocks!.add(blockSection); + + blockSection = SlackBlockSection(); + blockSection.text = SlackBlockText(); + blockSection.text!.text = ' :star::star::star::star:'; + blockSection.accessory = SlackAccessory(); + blockSection.accessory!.type = SlackActionType.button; + blockSection.accessory!.url = 'https://www.naver.com'; + blockSection.accessory!.text = SlackBlockText(); + blockSection.accessory!.text!.type = SlackTextType.plain_text; + blockSection.accessory!.text!.text = 'View'; + blockSection.accessory!.text!.emoji = true; + blockSection.accessory!.value = 'view_alternate_2'; + attachment.blocks!.add(blockSection); + + data.attachments!.add(attachment); + return data; + } +} \ No newline at end of file