From f1559c7b0715f5b963ebc7e851cb59d2c7e3bca1 Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 19 Feb 2023 12:46:55 +0900 Subject: [PATCH] =?UTF-8?q?slack=20=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/slack/slack_data.dart | 132 +++++++++++++++++++++ lib/utils/slack/slack_data.g.dart | 128 ++++++++++++++++++++ lib/utils/slack/slack_sender.dart | 188 ++++++++++++++++++++++++++++++ lib/utils/system_util.dart | 17 +++ pubspec.yaml | 2 + 5 files changed, 467 insertions(+) create mode 100644 lib/utils/slack/slack_data.dart create mode 100644 lib/utils/slack/slack_data.g.dart create mode 100644 lib/utils/slack/slack_sender.dart diff --git a/lib/utils/slack/slack_data.dart b/lib/utils/slack/slack_data.dart new file mode 100644 index 0000000..4adba93 --- /dev/null +++ b/lib/utils/slack/slack_data.dart @@ -0,0 +1,132 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:json_annotation/json_annotation.dart'; +import 'package:dart_framework/utils/system_util.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 SlackActionType { button, select } + +enum SlackActionStyle { danger, primary } + +@JsonSerializable() +class DataSlack { + String? message; + late List attachments; + + String getAttachments() { + var list = []; + for (var item in attachments) { + list.add(item.toJson()); + } + return jsonEncode(list); + } + + DataSlack(); + + factory DataSlack.fromJson(Map json) => + _$DataSlackFromJson(json); + Map toJson() => _$DataSlackToJson(this); +} + +@JsonSerializable() +class SlackAttachment { + String? fallback; + String? color; + String? author_name; + String? author_link; + String? author_icon; + String? title; + String? title_link; + String? pretext; + String? text; + String? image_url; + String? thumb_url; + String? footer; + String? footer_icon; + String? ts; + + List? fields; + List? actions; + + SlackAttachment(); + + factory SlackAttachment.fromJson(Map json) => + _$SlackAttachmentFromJson(json); + Map toJson() => _$SlackAttachmentToJson(this); +} + +@JsonSerializable() +class SlackField { + String? title; + String? value; + + SlackField(); + SlackField.withParam(this.title, this.value); + + factory SlackField.fromJson(Map json) => + _$SlackFieldFromJson(json); + Map toJson() => _$SlackFieldToJson(this); +} + +@JsonSerializable() +class SlackAction { + SlackActionType? type; + String? name; + String? text; + String? url; + String? value; + SlackActionStyle? style; + /*SlackField options; + SlackConfirm confirm;*/ + + SlackAction(); + SlackAction.withParam( + this.name, this.text, this.url, this.value, this.type, this.style); + + factory SlackAction.fromJson(Map json) => + _$SlackActionFromJson(json); + Map toJson() => _$SlackActionToJson(this); +} + +@JsonSerializable() +class SlackConfirm { + String? title; + String? text; + String? ok_text; + String? dismiss_text; +} + +// To-do: File upload test +@JsonSerializable() +class SlackFile { + String? title; + String? initial_comment; + String? filePath; + String? fileName; + String? mimeType; + + Future setFileData(MultipartRequest form) async { + // var bytes = await readFileByte(filePath!); + if (title != null) { + form.fields['title'] = title!; + } + if (initial_comment != null) { + form.fields['initial_comment'] = initial_comment!; + } + form.fields['filetype'] = + "auto"; //참조 - https://api.slack.com/types/file#file_types + if (fileName != null && mimeType != null) { + form.files.add(MultipartFile.fromBytes( + 'file', await File.fromUri(Uri.parse(filePath!)).readAsBytes(), + contentType: MediaType(HttpHeaders.contentTypeHeader, mimeType!))); + } + var c = Completer(); + c.complete(form); + return c.future; + } +} diff --git a/lib/utils/slack/slack_data.g.dart b/lib/utils/slack/slack_data.g.dart new file mode 100644 index 0000000..62d2f2e --- /dev/null +++ b/lib/utils/slack/slack_data.g.dart @@ -0,0 +1,128 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'slack_data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DataSlack _$DataSlackFromJson(Map json) => DataSlack() + ..message = json['message'] as String? + ..attachments = (json['attachments'] as List) + .map((e) => SlackAttachment.fromJson(e as Map)) + .toList(); + +Map _$DataSlackToJson(DataSlack instance) => { + 'message': instance.message, + 'attachments': instance.attachments, + }; + +SlackAttachment _$SlackAttachmentFromJson(Map json) => + SlackAttachment() + ..fallback = json['fallback'] as String? + ..color = json['color'] as String? + ..author_name = json['author_name'] as String? + ..author_link = json['author_link'] as String? + ..author_icon = json['author_icon'] as String? + ..title = json['title'] as String? + ..title_link = json['title_link'] as String? + ..pretext = json['pretext'] as String? + ..text = json['text'] as String? + ..image_url = json['image_url'] as String? + ..thumb_url = json['thumb_url'] as String? + ..footer = json['footer'] as String? + ..footer_icon = json['footer_icon'] as String? + ..ts = json['ts'] as String? + ..fields = (json['fields'] as List?) + ?.map((e) => SlackField.fromJson(e as Map)) + .toList() + ..actions = (json['actions'] as List?) + ?.map((e) => SlackAction.fromJson(e as Map)) + .toList(); + +Map _$SlackAttachmentToJson(SlackAttachment instance) => + { + 'fallback': instance.fallback, + 'color': instance.color, + 'author_name': instance.author_name, + 'author_link': instance.author_link, + 'author_icon': instance.author_icon, + 'title': instance.title, + 'title_link': instance.title_link, + 'pretext': instance.pretext, + 'text': instance.text, + 'image_url': instance.image_url, + 'thumb_url': instance.thumb_url, + 'footer': instance.footer, + 'footer_icon': instance.footer_icon, + 'ts': instance.ts, + 'fields': instance.fields, + 'actions': instance.actions, + }; + +SlackField _$SlackFieldFromJson(Map json) => SlackField() + ..title = json['title'] as String? + ..value = json['value'] as String?; + +Map _$SlackFieldToJson(SlackField instance) => + { + 'title': instance.title, + 'value': instance.value, + }; + +SlackAction _$SlackActionFromJson(Map json) => SlackAction() + ..type = $enumDecodeNullable(_$SlackActionTypeEnumMap, json['type']) + ..name = json['name'] as String? + ..text = json['text'] as String? + ..url = json['url'] as String? + ..value = json['value'] as String? + ..style = $enumDecodeNullable(_$SlackActionStyleEnumMap, json['style']); + +Map _$SlackActionToJson(SlackAction instance) => + { + 'type': _$SlackActionTypeEnumMap[instance.type], + 'name': instance.name, + 'text': instance.text, + 'url': instance.url, + 'value': instance.value, + 'style': _$SlackActionStyleEnumMap[instance.style], + }; + +const _$SlackActionTypeEnumMap = { + SlackActionType.button: 'button', + SlackActionType.select: 'select', +}; + +const _$SlackActionStyleEnumMap = { + SlackActionStyle.danger: 'danger', + SlackActionStyle.primary: 'primary', +}; + +SlackConfirm _$SlackConfirmFromJson(Map json) => SlackConfirm() + ..title = json['title'] as String? + ..text = json['text'] as String? + ..ok_text = json['ok_text'] as String? + ..dismiss_text = json['dismiss_text'] as String?; + +Map _$SlackConfirmToJson(SlackConfirm instance) => + { + 'title': instance.title, + 'text': instance.text, + 'ok_text': instance.ok_text, + 'dismiss_text': instance.dismiss_text, + }; + +SlackFile _$SlackFileFromJson(Map json) => SlackFile() + ..title = json['title'] as String? + ..initial_comment = json['initial_comment'] as String? + ..filePath = json['filePath'] as String? + ..fileName = json['fileName'] as String? + ..mimeType = json['mimeType'] as String?; + +Map _$SlackFileToJson(SlackFile instance) => { + 'title': instance.title, + 'initial_comment': instance.initial_comment, + 'filePath': instance.filePath, + 'fileName': instance.fileName, + 'mimeType': instance.mimeType, + }; diff --git a/lib/utils/slack/slack_sender.dart b/lib/utils/slack/slack_sender.dart new file mode 100644 index 0000000..4608923 --- /dev/null +++ b/lib/utils/slack/slack_sender.dart @@ -0,0 +1,188 @@ +import 'dart:async'; + +import 'package:color/color.dart'; +import 'package:http/http.dart' as http; + +import 'slack_data.dart'; + +enum SlackBuildType { android, ios, windows, mac, web } + +class SlackSender { + //Bot TOKEN + final 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"; + + Function(String)? _completeListener; + Function(Exception)? _errorListener; + + String getValidatedUser(String? targetUser) { + targetUser ??= "@channel"; + 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'); + buffer + .write('&attachments=${Uri.encodeComponent(message.getAttachments())}'); + buffer.write('&link_names=true'); + print(buffer.toString()); + return buffer.toString(); + } + + void onComplete(String result) { + print(result); + if (_completeListener != null) _completeListener!(result); + } + + void onError(Exception exception) { + if (_errorListener != null) _errorListener!(exception); + } + + void initializeListener() { + _completeListener = null; + _errorListener = null; + } + + //단일 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, + Function(Exception)? errorListener}) async { + var c = Completer(); + String url = getURL(channel, message, targetUser); + + var response = await http.get(Uri.parse(url)); + + print(response.body); + if (_completeListener != null) _completeListener!(response.body); + + c.complete(); + return c.future; + } + + String getParameters(String channel, DataSlack message, + {String? targetUser}) { + return getURL(channel, message, targetUser, includeURL: false); + } + + /// ****************** File Send ******************* + http.MultipartRequest getPostForm( + String url, String channel, String? targetUser) { + var form = http.MultipartRequest('POST', Uri.parse(url)); + targetUser = getValidatedUser(targetUser); + form.fields['token'] = token; + form.fields['channels'] = channel; + return form; + } + + void sendFile(String channel, SlackFile file, + {String? targetUser, + Function(String)? completeListener, + Function(Exception)? errorListener}) async { + _completeListener = completeListener; + _errorListener = errorListener; + var form = getPostForm(urlFileUplaod, channel, targetUser); + form = await file.setFileData(form); + //To-do: send http + } + + /// ****************** 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); + sends('app_notifier', message, targetUser: 'toki'); + } + + Future sendBuild(String appName, SlackBuildType buildType, Color color, + String version, String downloadURL, + {String channel = 'build'}) async { + final buildURL = 'http://toki-labs.com/build/'; + String thumUrl = buildType == SlackBuildType.android + ? '${buildURL}android.png' + : '${buildURL}appstore.png'; + String projectIcon = "$buildURL$appName/icon.png"; + String emoji = + buildType == SlackBuildType.android ? ":android1: " : ":ios0: "; + String osColor = buildType == SlackBuildType.ios ? "#ED145B" : "#81CA3F"; + var message = DataSlack(); + message.attachments = []; + var attachments = message.attachments; + + var attachment = SlackAttachment(); + attachment.color = color.toHexColor().toCssString(); + attachment.fields = [SlackField.withParam(toPascal(appName), null)]; + attachments.add(attachment); + + var buildTypeStr = buildTypeToString(buildType); + attachment = SlackAttachment(); + attachment.color = osColor; + attachment.author_name = /*emoji +*/ buildTypeStr; + attachment.author_icon = thumUrl; + attachment.thumb_url = projectIcon; + attachment.fields = [ + SlackField.withParam(null, + "Successfully build $buildTypeStr binary file.\nBuild version: *$version*") + ]; + attachment.actions = [ + SlackAction.withParam( + 'build', 'Download', downloadURL, null, SlackActionType.button, null) + ]; + attachments.add(attachment); + + return await sends(channel, message); + } + + String buildTypeToString(SlackBuildType type) { + var typeStr = type.toString().replaceAll('SlackBuildType.', ''); + return toPascal(typeStr); + } + + String toPascal(String raw) { + return '${raw[0].toUpperCase()}${raw.substring(1).toLowerCase()}'; + } +} diff --git a/lib/utils/system_util.dart b/lib/utils/system_util.dart index bd95b39..178819a 100644 --- a/lib/utils/system_util.dart +++ b/lib/utils/system_util.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:yaml/yaml.dart'; @@ -87,3 +88,19 @@ Future get simpleFuture { c.complete(); return c.future; } + +Future readFileByte(String filePath) async { + var c = Completer(); + Uri myUri = Uri.parse(filePath); + File audioFile = File.fromUri(myUri); + Uint8List? bytes; + await audioFile.readAsBytes().then((value) { + bytes = Uint8List.fromList(value); + print('reading of bytes is completed'); + }).catchError((onError) { + print( + 'Exception Error while reading audio from path:' + onError.toString()); + }); + c.complete(bytes); + return c.future; +} diff --git a/pubspec.yaml b/pubspec.yaml index fb47cf4..ec54299 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: archive: ^3.3.5 ftpconnect: ^2.0.5 yaml: ^3.1.1 + http: ^0.13.5 + color: ^3.0.0 dev_dependencies: lints: ^2.0.0