Skip to content

Commit e2e4441

Browse files
authored
fix: new file upload api (#8)
* fix: clean up after the robot * Add comments * Add image from urls * Fix robot review comments * Move sandbox to unit tests * Update readme
1 parent 7f604b6 commit e2e4441

File tree

10 files changed

+282
-429
lines changed

10 files changed

+282
-429
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ DerivedData/
77
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
88
.netrc
99
.idea
10+
.swiftpm/xcode/xcshareddata/xcschemes/Slackito-Package.xcscheme
11+
.swiftpm/xcode/xcshareddata/xcschemes/Slackito.xcscheme
12+
.swiftpm/xcode/xcshareddata/xcschemes/slack-cli.xcscheme

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ let package = Package(
99
.library(
1010
name: "Slackito",
1111
targets: ["Slackito"]
12-
),
12+
)
1313
],
1414
dependencies: [
1515
.package(url: "https://github.com/platacard/cronista.git", from: "1.0.3"),
@@ -25,6 +25,6 @@ let package = Package(
2525
name: "SlackitoTests",
2626
dependencies: ["Slackito"],
2727
path: "Tests/Slackito"
28-
),
28+
)
2929
]
3030
)

README.md

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,23 @@ try await message.send(as: "slack_token")
5151

5252
### Attachments
5353

54-
Slackito also supports sending attachments including images, CSV files, and other file types:
54+
Slackito also supports sending attachments including images, CSV files, and other file types. Use raw data to attach files to a message. For images use `ImageAccessory` in MarkdownSection/PlainSection or `Image` block for full-sized images.
5555

5656
```swift
5757
let message = SlackMessage(
5858
channel: "reports",
59-
attachments: [imageAttachment, csvAttachment, csvDataAttachment]
59+
attachments: [csvAttachment]
6060
) {
6161
MarkdownSection("📊 Here's the monthly report with data!")
62+
63+
Image(url: "https://example.com/image.jpg", text: "Jack's avatar")
64+
MarkdownSection(
65+
"Author's avatar",
66+
accessory: ImageAccessory(
67+
url: "https://example.com/image.jpg",
68+
text: "Avatar image"
69+
)
70+
)
6271
Context {
6372
"*Generated on*: \(Date())"
6473
"*Data source*: Internal systems"
@@ -68,18 +77,4 @@ let message = SlackMessage(
6877
try await message.send(as: "slack_token")
6978
```
7079

71-
#### Supported File Types
72-
73-
- **Images**: JPEG, PNG, GIF
74-
- **Documents**: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX
75-
- **Data**: CSV, JSON, XML, TXT
76-
- **Archives**: ZIP
77-
- **Media**: MP4, MOV, MP3, WAV
78-
79-
#### Attachment Types
80-
81-
- **URL-based**: Attach files from web URLs
82-
- **Data-based**: Attach files from local data (automatically uploaded to Slack)
83-
- **Images**: Special handling for image attachments with alt text support
84-
8580
> Author: [@havebeenfitz](https://github.com/havebeenfitz)

Sources/Slackito/Models/MessageBlocks.swift

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,46 @@ public struct Header: BlockConvertible {
4242
/// Markdown text section. Used both inside `FieldsSection` and without it
4343
public struct MarkdownSection: MarkdownSectionConvertible, BlockConvertible {
4444
public var json: String {
45-
"""
46-
{ "type": "section", "text": { "type": "mrkdwn", "text": "\(markdown)" } }
47-
"""
45+
if let accessory {
46+
"""
47+
{ "type": "section", "text": { "type": "mrkdwn", "text": "\(markdown)" } \(accessory.json) }
48+
"""
49+
} else {
50+
"""
51+
{ "type": "section", "text": { "type": "mrkdwn", "text": "\(markdown)" } }
52+
"""
53+
}
4854
}
4955

5056
public let markdown: String
51-
52-
public init(_ markdown: String) {
57+
public let accessory: ImageAccessory?
58+
59+
public init(_ markdown: String, accessory: ImageAccessory? = nil) {
5360
self.markdown = markdown
61+
self.accessory = accessory
5462
}
5563
}
5664

5765
/// Plain text section, used in the message body to send a simple text
5866
public struct PlainSection: PlainSectionConvertible & BlockConvertible {
5967
public var json: String {
60-
"""
61-
{ "type": "section", "text": { "type": "plain_text", "text": "\(plainText)" } }
62-
"""
68+
if let accessory {
69+
"""
70+
{ "type": "section", "text": { "type": "plain_text", "text": "\(plainText)" } \(accessory.json) }
71+
"""
72+
} else {
73+
"""
74+
{ "type": "section", "text": { "type": "plain_text", "text": "\(plainText)" } }
75+
"""
76+
}
6377
}
6478

6579
public let plainText: String
66-
67-
public init(_ plainText: String) {
80+
public let accessory: ImageAccessory?
81+
82+
public init(_ plainText: String, accessory: ImageAccessory? = nil) {
6883
self.plainText = plainText
84+
self.accessory = accessory
6985
}
7086
}
7187

@@ -91,6 +107,51 @@ public struct FieldsSection: BlockConvertible {
91107
}
92108
}
93109

110+
public struct Image: BlockConvertible {
111+
public var json: String {
112+
"""
113+
{
114+
"type": "image",
115+
"title": {
116+
"type": "plain_text",
117+
"text": "\(text)",
118+
"emoji": true
119+
},
120+
"image_url": "\(url)",
121+
"alt_text": "\(text)"
122+
}
123+
"""
124+
}
125+
126+
public let url: String
127+
public let text: String
128+
129+
public init(url: String, text: String) {
130+
self.url = url
131+
self.text = text
132+
}
133+
}
134+
135+
public struct ImageAccessory {
136+
public var json: String {
137+
"""
138+
, "accessory": {
139+
"type": "image",
140+
"image_url": "\(url)",
141+
"alt_text": "\(text)"
142+
}
143+
"""
144+
}
145+
146+
public let url: String
147+
public let text: String
148+
149+
public init(url: String, text: String) {
150+
self.url = url
151+
self.text = text
152+
}
153+
}
154+
94155
/// Usually used at the bottom of the message to provide some kind of context, e.g. app version or branch
95156
public struct Context: BlockConvertible {
96157
public var json: String {

Sources/Slackito/Models/SlackAttachment.swift

Lines changed: 1 addition & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -5,131 +5,19 @@ import UniformTypeIdentifiers
55
public struct SlackAttachment: Sendable {
66
/// The type of attachment
77
public let type: AttachmentType
8-
/// Optional title for the attachment
9-
public let title: String?
10-
/// Optional fallback text for the attachment
11-
public let fallback: String?
12-
/// Optional color for the attachment (hex color code)
13-
public let color: String?
14-
/// Optional text content for the attachment
158
public let text: String?
169

1710
public init(
1811
type: AttachmentType,
19-
title: String? = nil,
20-
fallback: String? = nil,
21-
color: String? = nil,
2212
text: String? = nil
2313
) {
2414
self.type = type
25-
self.title = title
26-
self.fallback = fallback
27-
self.color = color
2815
self.text = text
2916
}
30-
31-
/// JSON representation of the attachment for Slack API
32-
public var json: String {
33-
var components: [String] = []
34-
35-
// Add title if present
36-
if let title = title {
37-
components.append("\"title\": \"\(title)\"")
38-
}
39-
40-
// Add fallback if present
41-
if let fallback = fallback {
42-
components.append("\"fallback\": \"\(fallback)\"")
43-
}
44-
45-
// Add color if present
46-
if let color = color {
47-
components.append("\"color\": \"\(color)\"")
48-
}
49-
50-
// Add text if present
51-
if let text = text {
52-
components.append("\"text\": \"\(text)\"")
53-
}
54-
55-
// Add type-specific fields
56-
switch type {
57-
case .image(let url, let altText):
58-
components.append("\"image_url\": \"\(url)\"")
59-
if let altText = altText {
60-
components.append("\"alt_text\": \"\(altText)\"")
61-
}
62-
case .file(let url, let filename, let fileType):
63-
components.append("\"file_url\": \"\(url)\"")
64-
components.append("\"filename\": \"\(filename)\"")
65-
components.append("\"filetype\": \"\(fileType.rawValue)\"")
66-
case .fileData(_, let filename, let fileType):
67-
components.append("\"filename\": \"\(filename)\"")
68-
components.append("\"filetype\": \"\(fileType.rawValue)\"")
69-
}
70-
71-
return "{ \(components.joined(separator: ", ")) }"
72-
}
7317
}
7418

7519
/// Types of attachments supported
7620
public enum AttachmentType: Sendable {
77-
/// Image attachment from URL
78-
case image(url: String, altText: String? = nil)
79-
/// File attachment from URL
80-
case file(url: String, filename: String, fileType: FileType)
8121
/// File attachment from local data
82-
case fileData(data: Data, filename: String, fileType: FileType)
83-
}
84-
85-
/// Supported file types for attachments
86-
public enum FileType: String, CaseIterable, Sendable {
87-
case csv, pdf, txt, json, xml, zip, jpeg, png, gif, mp4, mov, mp3
88-
89-
/// MIME type for the file type
90-
public var mimeType: String? {
91-
switch self {
92-
case .csv: UTType.commaSeparatedText.preferredMIMEType
93-
case .pdf: UTType.pdf.preferredMIMEType
94-
case .txt: UTType.plainText.preferredMIMEType
95-
case .json: UTType.json.preferredMIMEType
96-
case .xml: UTType.xml.preferredMIMEType
97-
case .zip: UTType.zip.preferredMIMEType
98-
case .jpeg: UTType.jpeg.preferredMIMEType
99-
case .png: UTType.png.preferredMIMEType
100-
case .gif: UTType.gif.preferredMIMEType
101-
case .mp4: UTType.mpeg4Movie.preferredMIMEType
102-
case .mov: UTType.quickTimeMovie.preferredMIMEType
103-
case .mp3: UTType.mp3.preferredMIMEType
104-
}
105-
}
106-
}
107-
108-
// MARK: - Convenience Initializers
109-
110-
public extension SlackAttachment {
111-
112-
static func image(url: String, altText: String? = nil, title: String? = nil, fallback: String? = nil) -> SlackAttachment {
113-
SlackAttachment(
114-
type: .image(url: url, altText: altText),
115-
title: title,
116-
fallback: fallback
117-
)
118-
}
119-
120-
static func file(url: String, filename: String, fileType: FileType, title: String? = nil, fallback: String? = nil) -> SlackAttachment {
121-
SlackAttachment(
122-
type: .file(url: url, filename: filename, fileType: fileType),
123-
title: title,
124-
fallback: fallback
125-
)
126-
}
127-
128-
static func file(data: Data, filename: String, fileType: FileType, title: String? = nil, fallback: String? = nil) -> SlackAttachment {
129-
SlackAttachment(
130-
type: .fileData(data: data, filename: filename, fileType: fileType),
131-
title: title,
132-
fallback: fallback
133-
)
134-
}
22+
case fileData(data: Data, filename: String)
13523
}

Sources/Slackito/Models/SlackMessage.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Cronista
23

34
/// Root slack message entity holding a nested structure of DSL blocks
45
@MainActor
@@ -17,18 +18,17 @@ public struct SlackMessage: BlockConvertible {
1718
let blocks: [BlockConvertible]
1819
/// File attachments for the message
1920
let attachments: [SlackAttachment]
20-
21+
22+
let logger = Cronista(module: "Slackito", category: "SlackMessage")
23+
2124
public var json: String {
22-
let blocksJson = blocks.json
23-
let attachmentsJson = attachments.isEmpty ? "" : ", \"attachments\": [ \(attachments.map(\.json).joined(separator: ", ")) ]"
24-
2525
if let ts {
26-
return """
27-
{ "channel": "\(channel)", "thread_ts": "\(ts)", "ts": "\(ts)", "blocks": [ \(blocksJson) ]\(attachmentsJson) }
26+
"""
27+
{ "channel": "\(channel)", "thread_ts": "\(ts)", "ts": "\(ts)", "blocks": [ \(blocks.json) ] }
2828
"""
2929
} else {
30-
return """
31-
{ "channel": "\(channel)", "blocks": [ \(blocksJson) ]\(attachmentsJson) }
30+
"""
31+
{ "channel": "\(channel)", "blocks": [ \(blocks.json) ] }
3232
"""
3333
}
3434
}

0 commit comments

Comments
 (0)