Skip to content

Commit

Permalink
Added support for PhotoAlbum upload
Browse files Browse the repository at this point in the history
  • Loading branch information
TheM4hd1 committed Nov 1, 2020
1 parent 6455b95 commit 96cbc36
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 16 deletions.
106 changes: 104 additions & 2 deletions SwiftyInsta/API/Handlers/MediaHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public final class MediaHandler: Handler {
let optionalImageData = photo.image.jpegData(compressionQuality: 1)
#endif
guard let imageData = optionalImageData else {
return completionHandler(.failure(GenericError.custom("Invalid request.")))
return completionHandler(.failure(GenericError.custom("Invalid Image.")))
}

// swiftlint:disable line_length
Expand Down Expand Up @@ -219,7 +219,7 @@ public final class MediaHandler: Handler {
// prepare body.
let signature = "SIGNATURE.\(payload)"
let body: [String: Any] = [
Constants.igSignatureKey: signature.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
Constants.igSignatureKey: signature.addingPercentEncoding(withAllowedCharacters: .rfc3986Unreserved)!
]

requests.request(Upload.Response.Picture.self,
Expand All @@ -229,6 +229,108 @@ public final class MediaHandler: Handler {
completion: completionHandler)
}

public func upload(album: Upload.Album,
completionHandler: @escaping (Result<Upload.Response.Album, Error>) -> Void) {
guard let storage = handler.response?.storage else {
return completionHandler(.failure(GenericError.custom("Invalid `Authentication.Response` in `APIHandler.respone`. Log in again.")))
}
let device = handler.settings.device
guard let user = storage.user else {
return completionHandler(.failure(GenericError.custom("Invalid request.")))
}
var uploadIds: [String] = []
let group = DispatchGroup()
DispatchQueue.global().async { [weak self] in
guard let me = self, let handler = me.handler else {
return completionHandler(.failure(GenericError.weakObjectReleased))
}
for photo in album.images {
group.enter()
let uploadId = String(Date().millisecondsSince1970 / 1000)
// swiftlint:disable line_length
let rUploadParams = "{\"image_compression\":\"{\\\"quality\\\":48,\\\"lib_version\\\":\\\"1676.104000\\\",\\\"ssim\\\":0.99717170000076294,\\\"colorspace\\\":\\\"kCGColorSpaceDeviceRGB\\\",\\\"lib_name\\\":\\\"uikit\\\"}\",\"upload_id\":\"\(uploadId)\",\"xsharing_user_ids\":[],\"is_sidecar\":\"1\",\"media_type\":1}"
#if os(macOS)
let optionalImageData = photo.tiffRepresentation
#else
let optionalImageData = photo.jpegData(compressionQuality: 1)
#endif
guard let imageData = optionalImageData else {
return completionHandler(.failure(GenericError.custom("Invalid Image.")))
}
let headers = ["Content-Type": "application/octet-stream",
"X-Entity-Name": "image.jpeg",
"X-Entity-Type": "image/jpeg",
"x_fb_photo_waterfall_id": UUID.init().uuidString.md5(),
"X-Entity-Length": "\(imageData.count)",
"Content-Length": "\(imageData.count)",
"X-Instagram-Rupload-Params": rUploadParams,
"Accept-Encoding": "gzip, deflate",
"Offset": "0",
"IG-U-Ds-User-ID": storage.dsUserId]

me.requests.request(Upload.Response.Picture.self,
method: .post,
endpoint: Endpoint.Upload.photo.upload(uploadId.md5()),
body: .data(imageData),
headers: headers,
options: .deliverOnResponseQueue) {
switch $0 {
case .failure(let error):
handler.settings.queues.response.async {
completionHandler(.failure(error))
}
case .success(let decoded):
guard let uploadId = decoded.uploadId else {
return handler.settings.queues.response.async {
completionHandler(.failure(GenericError.unknown))
}
}
uploadIds.append(uploadId)
group.leave()
}
}
group.wait()
}

let childrens = uploadIds.map { ConfigureChildren.init(uploadId: $0,
disableComments: album.disableComments,
sourceType: "library",
isStoriesDraft: false,
allowMultiConfigures: false,
cameraPosition: "unknown",
geotagEnabled: false) }
let timestamp = String(Date().millisecondsSince1970 / 1000)
let sidecarId = String(Date().millisecondsSince1970 / 1000)
let configure = ConfigurePhotoAlbum(uuid: device.deviceGuid.uuidString,
uid: user.identity.primaryKey ?? -1,
csrfToken: storage.csrfToken,
caption: album.caption,
clientSidecarId: sidecarId,
geotagEnabled: false,
disableComments: album.disableComments,
deviceId: device.deviceGuid.uuidString,
waterfallId: UUID().uuidString,
timezoneOffset: "\(TimeZone.current.secondsFromGMT())",
clientTimestamp: timestamp,
childrenMetadata: childrens)
// prepare body
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
guard let payload = try? String(data: encoder.encode(configure), encoding: .utf8) else {
return completionHandler(.failure(GenericError.custom("Invalid request.")))
}
let signature = "SIGNATURE.\(payload)"
let body: [String: Any] = [
Constants.igSignatureKey: signature.addingPercentEncoding(withAllowedCharacters: .rfc3986Unreserved)!
]
me.requests.request(Upload.Response.Album.self,
method: .post,
endpoint: Endpoint.Media.configureAlbum,
body: .parameters(body),
completion: completionHandler)
}
}

@available(*, unavailable, message: "Instagram changed this endpoint. We're working on making it work again.")
// Make sure file is valid (correct format, codecs, width, height and aspect ratio)
// also its important to provide fileName.extenstion in InstaVideo
Expand Down
26 changes: 12 additions & 14 deletions SwiftyInsta/Local/Requests/Legacy/UploadMediaModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@

import Foundation

public struct UploadPhotoAlbumResponse: Codable, StatusEnforceable {
var clientSidecarId: String?
var media: Media?
var status: String?
}

struct ConfigurePhoto: Codable {
enum CodingKeys: String, CodingKey {
case uuid = "_uuid", uid = "_uid", csrfToken = "_csrftoken"
Expand Down Expand Up @@ -47,10 +41,11 @@ struct ConfigureEdits: Codable {
let cropZoom: Int
}

struct ConfigurePhotoAlbumModel: Codable {
struct ConfigurePhotoAlbum: Codable {
enum CodingKeys: String, CodingKey {
case uuid = "_uuid", uid = "_uid", csrfToken = "_csrftoken"
case caption, clientSidecarId, geotagEnabled, disableComments, childrenMetadata
case caption, clientSidecarId, geotagEnabled, disableComments,
childrenMetadata, deviceId, waterfallId, timezoneOffset, clientTimestamp
}

let uuid: String
Expand All @@ -60,18 +55,21 @@ struct ConfigurePhotoAlbumModel: Codable {
let clientSidecarId: String
let geotagEnabled: Bool
let disableComments: Bool
let deviceId: String
let waterfallId: String
let timezoneOffset: String
let clientTimestamp: String
let childrenMetadata: [ConfigureChildren]
}

struct ConfigureChildren: Codable {
let sceneCaptureType: String
let masOptIn: String
let cameraPosition: String
let uploadId: String
let disableComments: Bool
let sourceType: String
let isStoriesDraft: Bool
let allowMultiConfigures: Bool
let cameraPosition: String
let geotagEnabled: Bool
let disableComments: Bool
let sourceType: Int
let uploadId: String
}

struct ConfigureVideoModel: Codable {
Expand Down
28 changes: 28 additions & 0 deletions SwiftyInsta/Local/Responses/UploadResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,34 @@ public extension Upload {
try container.encode(rawResponse.data())
}
}
/// A `struct` holding reference to a successful `Upload.picture`.
public struct Album: ParsedResponse, StatusEnforceable {
/// Init with `rawResponse`.
public init?(rawResponse: DynamicResponse) {
guard rawResponse != .none else { return nil }
self.rawResponse = rawResponse
}

/// The `rawResponse`.
public let rawResponse: DynamicResponse

/// The media.
public var media: Media? { return Media(rawResponse: rawResponse.media) }
/// The upload id.
public var sidecarId: String? { return rawResponse.clientSidecarId.string }
/// The status.
public var status: String? { return rawResponse.status.string }

// MARK: Codable
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.rawResponse = try DynamicResponse(data: container.decode(Data.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawResponse.data())
}
}
/// A `struct` holding reference to a successful `Upload.video`.
public struct Video: ParsedResponse, StatusEnforceable {
/// Init with `rawResponse`.
Expand Down

0 comments on commit 96cbc36

Please sign in to comment.