読者です 読者をやめる 読者になる 読者になる

面白きことは良きことなり

拙く未熟なiOSエンジニアの備忘録と戯言

iOS(Swift)から3ステップでMastodonに画像付き投稿を行う

まえがき

  • 前回は、登録、ログイン、投稿の3ステップを記載
  • 前回の延長線上で画像付きの投稿もやってみよと思い立つ
  • 画像やmime/typeは固定で記述してるので適宜変更してください。
  • 今回もmastdn.jpを使用しています。適宜変更してください。
  • タイトルのことが出来る処理の流れを主軸に記述しているため、ForceUnwrap等よくない書き方は意図的にしています。(早めに直します・・・)

今回もQiitaに投稿済み (今後も技術記事は両方に投稿&Wメンテ予定。これに伴い、はてな記法からMarkdownに変更)

準備

extension Data {
    public mutating func append(_ string: String) {
        let data = Data(string.utf8)
        return self.append(data)
    }
}
// 今回はPNG固定でファイル名とmime/typeを使用
let filename = "hoge.png"
let mimetype = "image/png"

// Create body for media
func createBodyWith(parameters: [String: String]?, filePathKey: String?, imageData: Data, boundary: String) -> Data {
    var body = Data();

    if let parameters = parameters {
        for (key, value) in parameters {
            body.append("--\(boundary)\r\n")
            body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
            body.append("\(value)\r\n")
        }
    }

    body.append("--\(boundary)\r\n")
    body.append("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
    body.append("Content-Type: \(mimetype)\r\n\r\n")
    body.append(imageData)
    body.append("\r\n")
    body.append("--\(boundary)--\r\n")

    return body
}

func generateBoundaryString() -> String {
    return "Boundary-\(NSUUID().uuidString)"
}

Step.1 前回の内容を使ってログイン

aryzae.hatenablog.com

上記の前記事を参考に、access_tokenを取得します。

Step.2 画像をアップロード

// 画像アップロード先URL
let uploadUrl = URL(string: "https://mastdn.jp/api/v1/media")!

// params生成
// access_token: Step.1で取得しているもの
let params: [String: String] = ["access_token": responseJson["access_token"] as! String]

// imageData生成
let imageData = UIImagePNGRepresentation(UIImage(named: filename)!)!

// boudary生成
let boundary = generateBoundaryString()

// request生成(前回作ったメソッドが使えないため、地道にValue追加)
var request = URLRequest(url: urlString!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = createBodyWith(parameters: params, filePathKey: "file", imageData: datas, boundary: boundary)

// 画像アップロードPOST
let task = session.dataTask(with: request, completionHandler: { data, response, error in
    do {
        self.responseJson = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary<String, AnyObject>
    } catch {
    }
})
task.resume()
// responseJsonの中身
{
    "id": 12356,
    "preview_url": "https://mastdn.jp/system/media_attachments/files/000/069/064/small/b804bb6a6c8db3d2.png?1492450358",
    "text_url": "https://mastdn.jp/media/hOgVBgaqylfQM-IolQw",
    "type": "image",
    "url": "https://mastdn.jp/system/media_attachments/files/000/069/064/original/b804bb6a6c8db3d2.png?1492450358"
}

Step.3 画像ID付きでTootの投稿

// 前回のToot投稿に画像アップロード時のidを付加するだけ
// TootURL
let tootUrl = URL(string: "https://mastdn.jp/api/v1/statuses")!

// access_token: Step.1で取得しているもの
// status: Tootの内容
// media_ids: Step.2で取得したid(最大4つまで)。値は配列に格納。
// visibility: 公開範囲。(省略可能)
let body: [String: String] = ["access_token": responseJson["access_token"] as! String, "status": "開発アプリからのテスト投稿", "visibility": "public", "media_ids": [responseJson["id"]] as AnyObject]

// Toot POST
do {
    try post(url: tootUrl, body: body) { data, response, error in
        // dataは返ってくるが、投稿できるまでの処理を書くだけなので省略
    }
} catch {
}

参考にしたURL

  1. Tootsuite documentation
  2. Image upload example with Swift and PHP