From 4ab026cbfde57f1d5fa1ed4eb250710a91e96362 Mon Sep 17 00:00:00 2001 From: Nico Mexis Date: Tue, 7 May 2024 09:22:07 +0200 Subject: [PATCH] Migrate Slack screenshots to new API calls --- CHANGELOG.md | 3 + README.md | 4 +- lib/handlers/slack_handler.dart | 134 +++++++++++++++++++++++++------- pubspec.yaml | 3 +- 4 files changed, 112 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6da30..30c2b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.3.0 +* Migrate Slack screenshot API calls to `files.*UploadExternal` (you now need to specify also a `channelId` in the `SlackHandler` for that!) + ## 1.2.6 * Allow `package_info_plus` versions `8.x` * Remove direct dependency on `device_info_plus_platform_interface` (why was this there anyway?) diff --git a/README.md b/README.md index 4b16632..5ec1e6b 100644 --- a/README.md +++ b/README.md @@ -827,7 +827,9 @@ main() { All parameters list: * webhookUrl (required) - url of your webhook -* channel (required) - your channel name (i.e. #catcher2) +* channel (required) - your channel name (e.g. #catcher2) +* apiToken (optional) - your API token, only needed for screenshots (e.g. xxxx-xxxxxxxxx-xxxx) +* channelId (optional) - your screenshot channel ID, only needed for screenshots (e.g. C0NF841BK) * username (optional) - name of the integration bot * iconEmoji (optional) - avatar of the integration bot * enableDeviceParameters (optional) - please look in console handler description diff --git a/lib/handlers/slack_handler.dart b/lib/handlers/slack_handler.dart index 7d12709..ef7ee42 100644 --- a/lib/handlers/slack_handler.dart +++ b/lib/handlers/slack_handler.dart @@ -4,15 +4,16 @@ import 'package:catcher_2/model/platform_type.dart'; import 'package:catcher_2/model/report.dart'; import 'package:catcher_2/model/report_handler.dart'; import 'package:catcher_2/utils/catcher_2_utils.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -/// Slack webhook API doesn't allow file attachments class SlackHandler extends ReportHandler { SlackHandler( this.webhookUrl, this.channel, { this.apiToken, + this.channelId, this.username = 'Catcher 2', this.iconEmoji = ':bangbang:', this.printLogs = false, @@ -28,6 +29,7 @@ class SlackHandler extends ReportHandler { final String webhookUrl; final String? apiToken; final String channel; + final String? channelId; final String username; final String iconEmoji; @@ -62,35 +64,9 @@ class SlackHandler extends ReportHandler { }; _printLog('Sending request to Slack server...'); - if (apiToken != null && screenshot != null) { - final screenshotPath = screenshot.path; - final formData = FormData.fromMap({ - 'token': apiToken, - 'channels': channel, - 'file': await MultipartFile.fromFile(screenshotPath), - }); - final responseFile = await _dio.post( - 'https://slack.com/api/files.upload', - data: formData, - options: Options( - contentType: Headers.multipartFormDataContentType, - ), - ); - if (responseFile.data != null && - responseFile.data['file'] != null && - responseFile.data['file']['url_private'] != null) { - data.addAll({ - 'attachments': [ - { - 'image_url': responseFile.data['file']['url_private'], - 'text': 'Error Screenshot', - }, - ], - }); - } - _printLog( - 'Server responded upload file with code: ${responseFile.statusCode} ' - 'and message upload file: ${responseFile.statusMessage}', + if (screenshot != null) { + data.addAll( + await _tryUploadScreenshot(screenshot: XFile(screenshot.path)), ); } @@ -108,6 +84,104 @@ class SlackHandler extends ReportHandler { } } + Future> _tryUploadScreenshot({ + required XFile screenshot, + }) async { + if (apiToken == null || channelId == null) { + _printLog( + 'Cannot send screenshot to Slack because either ' + 'apiToken or channelId is not set!', + ); + return {}; + } + + try { + final screenshotPath = screenshot.path; + final name = 'catcher_2_${DateTime.now().microsecondsSinceEpoch}.png'; + + final formData = FormData.fromMap({ + 'token': apiToken, + 'filename': name, + 'length': await screenshot.length(), + 'alt_txt': 'Error Screenshot', + }); + final responseFile = await _dio.post( + 'https://slack.com/api/files.getUploadURLExternal', + data: formData, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + if (responseFile.data == null || + responseFile.data['ok'] != true || + responseFile.data['upload_url'] == null || + responseFile.data['file_id'] == null) { + _printLog( + 'Server responded to getUploadURLExternal with code: ' + '${responseFile.statusCode} ' + 'and message upload file: ${responseFile.statusMessage}', + ); + return {}; + } + + final formDataPost = FormData.fromMap({ + 'file': await MultipartFile.fromFile(screenshotPath), + }); + final responseFilePost = await _dio.post( + responseFile.data['upload_url'], + data: formDataPost, + options: Options( + contentType: Headers.multipartFormDataContentType, + ), + ); + if (responseFilePost.statusCode != 200) { + _printLog( + 'Server responded to upload file post with code: ' + '${responseFilePost.statusCode} ' + 'and message upload file: ${responseFilePost.statusMessage}', + ); + return {}; + } + + final formDataComplete = FormData.fromMap({ + 'token': apiToken, + 'files': '[{"id":"${responseFile.data['file_id']}"}]', + 'channel_id': channelId, + }); + final responseFileComplete = await _dio.post( + 'https://slack.com/api/files.completeUploadExternal', + data: formDataComplete, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + + _printLog( + 'Server responded to completeUploadExternal with code: ' + '${responseFileComplete.statusCode} ' + 'and message upload file: ${responseFileComplete.statusMessage}', + ); + + if (responseFileComplete.data == null || + responseFileComplete.data['ok'] != true) { + return {}; + } + _printLog(responseFileComplete.data['files'][0]['url_private']); + + return { + 'attachments': [ + { + 'image_url': responseFileComplete.data['files'][0]['url_private'], + 'text': responseFileComplete.data['files'][0]['permalink'], + }, + ], + }; + } catch (exception) { + _printLog('Failed to send screenshot: $exception'); + return {}; + } + } + String _buildMessage(Report report) { final stringBuffer = StringBuffer() ..write('*Error:* ```${report.error}```\n'); diff --git a/pubspec.yaml b/pubspec.yaml index df27806..65f7f0c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: catcher_2 description: Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer. -version: 1.2.6 +version: 1.3.0 homepage: https://github.com/ThexXTURBOXx/catcher_2 repository: https://github.com/ThexXTURBOXx/catcher_2 issue_tracker: https://github.com/ThexXTURBOXx/catcher_2/issues @@ -27,6 +27,7 @@ environment: flutter: ">=3.0.0" dependencies: + cross_file: ^0.3.0 device_info_plus: '>=9.0.0 <11.0.0' dio: ^5.0.1 flutter: