Day27 Plugin 从零开始到上架 - iOS instagram APIs

Modules:


struct UserInfoResponse: Decodable {
    var id : String
    var username : String
    var accountType: String
}

struct InstagramUser: Codable{
    var userId : String
    var accessToken : String
    var expiresIn: Int64
}

AccessTokenRepository:

class AccessTokenRepository {
    static let shared = AccessTokenRepository()
    
    private let boundary = "boundary=\(NSUUID().uuidString)"
    @KeychainStorage("INSTAGRAM_USER_INFO") private var instagramUser :InstagramUser? = nil
    
    func saveInstagramInfo(userId: String, accessToken: String, expiresIn: Int64)  {
        let newExpiresIn = Int64(NSDate().timeIntervalSince1970) + expiresIn
        instagramUser = InstagramUser(userId: userId, accessToken: accessToken, expiresIn: newExpiresIn)
    }
    
    func isTokenValid() -> Bool {
        if(instagramUser != nil){
            print("\nnow: \(Int64(NSDate().timeIntervalSince1970)), expiresIn: \(instagramUser!.expiresIn)\n")
        }
        if(instagramUser == nil || instagramUser!.expiresIn < Int64(NSDate().timeIntervalSince1970)){
            return false
        }else{
            return true
        }
    }
    
    func getUserInfo(completionHandler: @escaping (UserInfoResponse) -> Void) throws {
        guard instagramUser != nil else {
            throw InstagramErrors.tokenEmpty
        }
        
        guard isTokenValid() == true else {
            throw InstagramErrors.tokenExpired
        }
        
        let url = URL(string: "https://graph.instagram.com/me?fields=id,username,account_type&access_token=\(instagramUser!.accessToken)")!
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) {
            data, response, error in
            if let data = data {
                do {
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    
                    let response = try decoder.decode(UserInfoResponse.self, from: data)
                    print("response \(response)")
                    completionHandler(response)
                    
                }catch(let error) {
                    print(error.localizedDescription)
                }
            } else {
                print("No Data")
            }
        }.resume()
    }
    
    func getMedias(completionHandler: @escaping ([[String : Any]]) -> Void) throws {
        guard instagramUser != nil else {
            throw InstagramErrors.tokenEmpty
        }
        
        guard isTokenValid() == true else {
            throw InstagramErrors.tokenExpired
        }
        
        let url = URL(string: "https://graph.instagram.com/me/media?fields=id,caption,media_type,timestamp,permalink,media_url,thumbnail_url&access_token=\(instagramUser!.accessToken)")!
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) {
            data, response, error in
            if let data = data {
                
                do {
                    if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
                        // try to read out a dictionary
                        print("json = \(json)")
                        if let data = json["data"] as? [[String:Any]] {
                            print("\n\n\n\ndata = \(data)")
                            completionHandler(data)
                        }
                    }
                } catch let error as NSError {
                    print("Failed to load: \(error.localizedDescription)")
                }
            } else {
                print("No Data")
            }
        }.resume()
    }
    
    func getAlbumDetail(albumId: String, completionHandler: @escaping ([[String : Any]]) -> Void) throws {
        guard instagramUser != nil else {
            throw InstagramErrors.tokenEmpty
        }
        
        guard isTokenValid() == true else {
            throw InstagramErrors.tokenExpired
        }
        
        let url = URL(string: "https://graph.instagram.com/\(albumId)/children?fields=id,media_type,media_url,timestamp,thumbnail_url&access_token=\(instagramUser!.accessToken)")!
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) {
            data, response, error in
            if let data = data {
                
                do {
                    if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
                        // try to read out a dictionary
                        print("json = \(json)")
                        if let data = json["data"] as? [[String:Any]] {
                            print("\n\n\n\ndata = \(data)")
                            completionHandler(data)
                        }
                    }
                } catch let error as NSError {
                    print("Failed to load: \(error.localizedDescription)")
                }
            } else {
                print("No Data")
            }
        }.resume()
    }
    
    func getMediaItem(mediaId: String, completionHandler: @escaping ([String : Any]) -> Void) throws {
        guard instagramUser != nil else {
            throw InstagramErrors.tokenEmpty
        }
        
        guard isTokenValid() == true else {
            throw InstagramErrors.tokenExpired
        }
        
        let url = URL(string: "https://graph.instagram.com/\(mediaId)?fields=id,caption,media_type,timestamp,permalink,media_url,thumbnail_url&access_token=\(instagramUser!.accessToken)")!
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) {
            data, response, error in
            if let data = data {
                
                do {
                    if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
                        print("\n\n\n\njson = \(json)")
                        completionHandler(json)
                    }
                } catch let error as NSError {
                    print("Failed to load: \(error.localizedDescription)")
                }
            } else {
                print("No Data")
            }
        }.resume()
    }
    
    func getShortAccessTokenInfo(clientId: String, clientSecret: String, code: String, redirectUri: String, completionHandler: @escaping (ShortAccessTokenResponse) -> Void) {
        
        let url = URL(string: "https://api.instagram.com/oauth/access_token")!
        
        
        let headers = [
            "content-type": "multipart/form-data; boundary=\(boundary)"
        ]
        let parameters = [
            [
                "name": "client_id",
                "value": clientId
            ],
            [
                "name": "client_secret",
                "value": clientSecret
            ],
            [
                "name": "grant_type",
                "value": "authorization_code"
            ],
            [
                "name": "redirect_uri",
                "value": redirectUri
            ],
            [
                "name": "code",
                "value": code
            ]
        ]
        
        var request = URLRequest(url: url)
        let postData = getFormBody(parameters, boundary)
        
        request.allHTTPHeaderFields = headers
        request.httpMethod = "POST"
        
        request.httpBody = postData
        
        URLSession.shared.dataTask(with: request) {
            data, response, error in
            if let data = data {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                do {
                    let response = try decoder.decode(ShortAccessTokenResponse.self, from: data)
                    completionHandler(response)
                } catch {
                    print(error.localizedDescription)
                }
            } else {
                print("No Data")
            }
        }.resume()
    }
    
    func getLongAccessTokenInfo(accessToken: String,clientSecret: String,grantType: String, completionHandler: @escaping (LongAccessTokenResponse) -> Void) {
        
        let url = URL(string: "https://graph.instagram.com/access_token?access_token=\(accessToken)&client_secret=\(clientSecret)&grant_type=\(grantType)")!
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) {
            data, response, error in
            if let data = data {
                do {
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    
                    let response = try decoder.decode(LongAccessTokenResponse.self, from: data)
                    
                    completionHandler(response)
                    
                }catch(let error) {
                    print(error.localizedDescription)
                }
            } else {
                print("No Data")
            }
        }.resume()
    }
    
    func logout() {
        instagramUser = nil
    }
    
    private func getFormBody(_ parameters: [[String : String]], _ boundary: String) -> Data {
        var body = ""
        let error: NSError? = nil
        for param in parameters {
            let paramName = param["name"]!
            body += "--\(boundary)\r\n"
            body += "Content-Disposition:form-data; name=\"\(paramName)\""
            if let filename = param["fileName"] {
                let contentType = param["content-type"]!
                var fileContent: String = ""
                do { fileContent = try String(contentsOfFile: filename, encoding: String.Encoding.utf8)}
                catch {
                    print(error)
                }
                if (error != nil) {
                    print(error!)
                }
                body += "; filename=\"\(filename)\"\r\n"
                body += "Content-Type: \(contentType)\r\n\r\n"
                body += fileContent
            } else if let paramValue = param["value"] {
                body += "\r\n\r\n\(paramValue)"
            }
        }
        return body.data(using: .utf8)!
    }
}

@propertyWrapper
struct KeychainStorage<Value: Codable> {
    
    let key: String
    let service: String
    let initialValue: Value?
    
    private let keychain: KeychainAccess.Keychain
    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()
    
    init(wrappedValue initialValue: Value?, _ key: String, service: String? = nil) {
        self.initialValue = initialValue
        self.key = key
        self.service = service ?? Bundle.main.bundleIdentifier ?? "com.kishikawakatsumi.KeychainAccess"
        self.keychain = KeychainAccess.Keychain(service: self.service)
    }
    
    var wrappedValue: Value? {
        get {
            guard let data = try? keychain.getData(key),
                  let value = try? decoder.decode(Value.self, from: data) else {
                return initialValue
            }
            return value
        }
        set {
            guard newValue != nil,
                let newData = try? encoder.encode(newValue) else {
                try? keychain.remove("INSTAGRAM_USER_INFO")
                return
            }
            try? keychain.set(newData, key: key)
        }
    }
}

enum InstagramErrors: Error {
    case tokenEmpty
    case tokenExpired
}


<<:  Day29 NiFi 与其他工具的比较

>>:  Day27 Data Storage in iOS 03 - File System & Sqlite

利用世界第八大奇蹟,让你的财富横向扩展

HPA 当电商网站举办周年庆活动时,网站的浏览流量一定会面临到网站挂掉或网站速度很慢,当然解决办法也...

DAY12:玉山人工智慧挑战赛-中文手写字辨识(前言)

参赛契机 之前参加资策会,结训时都会做个专题啦,但因为我自己对我们组的专题挺不满意,而且对於深度学习...

Day 29 RSpec 里的 Factories 和 Fixtures

该文章同步发布於:我的部落格 测试有一个必要的条件就是要有资料来做测试,有三种方法可以 Rails...

[DAY15]跟 Vue.js 认识的30天 - Vue 动态模组(Dynamic Components)

这次写的内容是之前都没使用过的,所以就尽量让自己有概念,希望之後能使用到。 使用 v-bind:is...

【D20】修改食谱#1:根据市价,模拟改价

前言 假日没有行情,所以只能平日来做取得行情资料的工作,所以今天的文章是根据期货行情,模拟价格修改的...