├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── SwordRPC │ ├── Delegate.swift │ ├── Events.swift │ ├── RPC.swift │ ├── Register.swift │ ├── SwordRPC.swift │ ├── Types │ ├── Enums.swift │ ├── JoinRequest.swift │ └── RichPresence.swift │ └── Utils.swift └── SwordRPC.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build 3 | Packages 4 | *.xcodeproj 5 | xcuserdata/ 6 | Tests 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alejandro Alonso 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Socket", 6 | "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f33e2d3e6da0b5b286073e7091eefd0ca56ebc63", 10 | "version": "0.12.78" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwordRPC", 7 | products: [ 8 | .library( 9 | name: "SwordRPC", 10 | targets: ["SwordRPC"] 11 | ) 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/IBM-Swift/BlueSocket.git", from: "0.12.78") 15 | ], 16 | targets: [ 17 | .target( 18 | name: "SwordRPC", 19 | dependencies: ["Socket"] 20 | ) 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwordRPC - A Discord Rich Presence Library for Swift 2 | 3 | [![Swift Version](https://img.shields.io/badge/Swift-4.0-orange.svg?style=flat-square)](https://swift.org) [![Tag](https://img.shields.io/github/tag/Azoy/SwordRPC.svg?style=flat-square&label=release&colorB=)](https://github.com/Azoy/SwordRPC/releases) 4 | 5 | ## Requirements 6 | 1. macOS, Linux 7 | 2. Swift 4.0 8 | 9 | ## Adding SwordRP 10 | ### CocoaPods 11 | Edit your Podfile to add this dependency: 12 | 13 | ```ruby 14 | platform :osx, '10.11' 15 | 16 | target 'yourappnamehere' do 17 | use_frameworks! 18 | pod 'SwordRPC' 19 | end 20 | ``` 21 | 22 | ## Example 23 | ### Callbacks 24 | ```swift 25 | import SwordRPC 26 | 27 | /// Additional arguments: 28 | /// handlerInterval: Int = 1000 (decides how fast to check discord for updates, 1000ms = 1s) 29 | /// autoRegister: Bool = true (automatically registers your application to discord's url scheme (discord-appid://)) 30 | /// steamId: String? = nil (this is for steam games on these platforms) 31 | let rpc = SwordRPC(appId: "123") 32 | 33 | rpc.onConnect { rpc in 34 | var presence = RichPresence() 35 | presence.details = "Ranked | Mode: \(mode)" 36 | presence.state = "In a Group" 37 | presence.timestamps.start = Date() 38 | presence.timestamps.end = Date() + 600 // 600s = 10m 39 | presence.assets.largeImage = "map1" 40 | presence.assets.largeText = "Map 1" 41 | presence.assets.smallImage = "character1" 42 | presence.assets.smallText = "Character 1" 43 | presence.party.max = 5 44 | presence.party.size = 3 45 | presence.party.id = "partyId" 46 | presence.secrets.match = "matchSecret" 47 | presence.secrets.join = "joinSecret" 48 | presence.secrets.joinRequest = "joinRequestSecret" 49 | 50 | rpc.setPresence(presence) 51 | } 52 | 53 | rpc.onDisconnect { rpc, code, msg in 54 | print("It appears we have disconnected from Discord") 55 | } 56 | 57 | rpc.onError { rpc, code, msg in 58 | print("It appears we have discovered an error!") 59 | } 60 | 61 | rpc.onJoinGame { rpc, secret in 62 | print("We have found us a join game secret!") 63 | } 64 | 65 | rpc.onSpectateGame { rpc, secret in 66 | print("Our user wants to spectate!") 67 | } 68 | 69 | rpc.onJoinRequest { rpc, request, secret in 70 | print("Some user wants to play with us!") 71 | print(request.username) 72 | print(request.avatar) 73 | print(request.discriminator) 74 | print(request.userId) 75 | 76 | rpc.reply(to: request, with: .yes) // or .no or .ignore 77 | } 78 | 79 | rpc.connect() 80 | ``` 81 | 82 | ### Delegation 83 | ```swift 84 | import SwordRPC 85 | 86 | class ViewController { 87 | override func viewDidLoad() { 88 | let rpc = SwordRPC(appId: "123") 89 | rpc.delegate = self 90 | rpc.connect() 91 | } 92 | } 93 | 94 | extension ViewController: SwordRPCDelegate { 95 | func swordRPCDidConnect( 96 | _ rpc: SwordRPC 97 | ) {} 98 | 99 | func swordRPCDidDisconnect( 100 | _ rpc: SwordRPC, 101 | code: Int?, 102 | message msg: String? 103 | ) {} 104 | 105 | func swordRPCDidReceiveError( 106 | _ rpc: SwordRPC, 107 | code: Int, 108 | message msg: String 109 | ) {} 110 | 111 | func swordRPCDidJoinGame( 112 | _ rpc: SwordRPC, 113 | secret: String 114 | ) {} 115 | 116 | func swordRPCDidSpectateGame( 117 | _ rpc: SwordRPC, 118 | secret: String 119 | ) {} 120 | 121 | func swordRPCDidReceiveJoinRequest( 122 | _ rpc: SwordRPC, 123 | request: JoinRequest, 124 | secret: String 125 | ) {} 126 | } 127 | ``` 128 | 129 | ## Links 130 | Join the [API Channel](https://discord.gg/99a3xNk) to ask questions! 131 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Delegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Delegate.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | public protocol SwordRPCDelegate: class { 10 | func swordRPCDidConnect( 11 | _ rpc: SwordRPC 12 | ) 13 | 14 | func swordRPCDidDisconnect( 15 | _ rpc: SwordRPC, 16 | code: Int?, 17 | message msg: String? 18 | ) 19 | 20 | func swordRPCDidReceiveError( 21 | _ rpc: SwordRPC, 22 | code: Int, 23 | message msg: String 24 | ) 25 | 26 | func swordRPCDidJoinGame( 27 | _ rpc: SwordRPC, 28 | secret: String 29 | ) 30 | 31 | func swordRPCDidSpectateGame( 32 | _ rpc: SwordRPC, 33 | secret: String 34 | ) 35 | 36 | func swordRPCDidReceiveJoinRequest( 37 | _ rpc: SwordRPC, 38 | request: JoinRequest, 39 | secret: String 40 | ) 41 | } 42 | 43 | extension SwordRPCDelegate { 44 | public func swordRPCDidConnect( 45 | _ rpc: SwordRPC 46 | ) {} 47 | 48 | public func swordRPCDidDisconnect( 49 | _ rpc: SwordRPC, 50 | code: Int?, 51 | message msg: String? 52 | ) {} 53 | 54 | public func swordRPCDidReceiveError( 55 | _ rpc: SwordRPC, 56 | code: Int, 57 | message msg: String 58 | ) {} 59 | 60 | public func swordRPCDidJoinGame( 61 | _ rpc: SwordRPC, 62 | secret: String 63 | ) {} 64 | 65 | public func swordRPCDidSpectateGame( 66 | _ rpc: SwordRPC, 67 | secret: String 68 | ) {} 69 | 70 | public func swordRPCDidReceiveJoinRequest( 71 | _ rpc: SwordRPC, 72 | request: JoinRequest, 73 | secret: String 74 | ) {} 75 | } 76 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Events.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Events.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | extension SwordRPC { 10 | 11 | public func onConnect( 12 | handler: @escaping (_ rpc: SwordRPC) -> () 13 | ) { 14 | self.connectHandler = handler 15 | } 16 | 17 | public func onDisconnect( 18 | handler: @escaping (_ rpc: SwordRPC, _ code: Int?, _ msg: String?) -> () 19 | ) { 20 | self.disconnectHandler = handler 21 | } 22 | 23 | public func onError( 24 | handler: @escaping (_ rpc: SwordRPC, _ code: Int, _ msg: String) -> () 25 | ) { 26 | self.errorHandler = handler 27 | } 28 | 29 | public func onJoinGame( 30 | handler: @escaping (_ rpc: SwordRPC, _ secret: String) -> () 31 | ) { 32 | self.joinGameHandler = handler 33 | } 34 | 35 | public func onSpectateGame( 36 | handler: @escaping (_ rpc: SwordRPC, _ secret: String) -> () 37 | ) { 38 | self.spectateGameHandler = handler 39 | } 40 | 41 | public func onJoinRequest( 42 | handler: @escaping (_ rpc: SwordRPC, _ request: JoinRequest, _ secret: String) -> () 43 | ) { 44 | self.joinRequestHandler = handler 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SwordRPC/RPC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RPC.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Socket 11 | 12 | extension SwordRPC { 13 | 14 | func createSocket() { 15 | do { 16 | self.socket = try Socket.create(family: .unix, proto: .unix) 17 | try self.socket?.setBlocking(mode: false) 18 | }catch { 19 | guard let error = error as? Socket.Error else { 20 | print("[SwordRPC] Unable to create rpc socket") 21 | return 22 | } 23 | 24 | print("[SwordRPC] Error creating rpc socket: \(error)") 25 | } 26 | } 27 | 28 | func send(_ msg: String, _ op: OP) throws { 29 | let payload = msg.data(using: .utf8)! 30 | 31 | var buffer = UnsafeMutableRawBufferPointer.allocate(count: 8 + payload.count) 32 | 33 | defer { buffer.deallocate() } 34 | 35 | buffer.copyBytes(from: payload) 36 | buffer[8...] = buffer[...allocate(capacity: 8) 57 | let headerRawPtr = UnsafeRawPointer(headerPtr) 58 | 59 | defer { 60 | free(headerPtr) 61 | } 62 | 63 | var response = try self.socket?.read(into: headerPtr, bufSize: 8, truncate: true) 64 | 65 | guard response! > 0 else { 66 | return 67 | } 68 | 69 | let opValue = headerRawPtr.load(as: UInt32.self) 70 | let length = headerRawPtr.load(fromByteOffset: 4, as: UInt32.self) 71 | 72 | guard length > 0, let op = OP(rawValue: opValue) else { 73 | return 74 | } 75 | 76 | let payloadPtr = UnsafeMutablePointer.allocate(capacity: Int(length)) 77 | 78 | defer { 79 | free(payloadPtr) 80 | } 81 | 82 | response = try self.socket?.read(into: payloadPtr, bufSize: Int(length), truncate: true) 83 | 84 | guard response! > 0 else { 85 | return 86 | } 87 | 88 | let data = Data(bytes: UnsafeRawPointer(payloadPtr), count: Int(length)) 89 | 90 | self.handlePayload(op, data) 91 | 92 | }catch { 93 | return 94 | } 95 | } 96 | } 97 | 98 | func handshake() { 99 | do { 100 | let json = """ 101 | { 102 | "v": 1, 103 | "client_id": "\(self.appId)" 104 | } 105 | """ 106 | 107 | try self.send(json, .handshake) 108 | }catch { 109 | print("[SwordRPC] Unable to handshake with Discord") 110 | self.socket?.close() 111 | } 112 | } 113 | 114 | func subscribe(_ event: String) { 115 | let json = """ 116 | { 117 | "cmd": "SUBSCRIBE", 118 | "evt": "\(event)", 119 | "nonce": "\(UUID().uuidString)" 120 | } 121 | """ 122 | 123 | try? self.send(json, .frame) 124 | } 125 | 126 | func handlePayload(_ op: OP, _ json: Data) { 127 | switch op { 128 | case .close: 129 | let data = self.decode(json) 130 | let code = data["code"] as! Int 131 | let message = data["message"] as! String 132 | self.socket?.close() 133 | self.disconnectHandler?(self, code, message) 134 | self.delegate?.swordRPCDidDisconnect(self, code: code, message: message) 135 | 136 | case .ping: 137 | try? self.send(String(data: json, encoding: .utf8)!, .pong) 138 | 139 | case .frame: 140 | self.handleEvent(self.decode(json)) 141 | 142 | default: 143 | return 144 | } 145 | } 146 | 147 | func handleEvent(_ data: [String: Any]) { 148 | guard let evt = data["evt"] as? String, 149 | let event = Event(rawValue: evt) else { 150 | return 151 | } 152 | 153 | let data = data["data"] as! [String: Any] 154 | 155 | switch event { 156 | case.error: 157 | let code = data["code"] as! Int 158 | let message = data["message"] as! String 159 | self.errorHandler?(self, code, message) 160 | self.delegate?.swordRPCDidReceiveError(self, code: code, message: message) 161 | 162 | case .join: 163 | let secret = data["secret"] as! String 164 | self.joinGameHandler?(self, secret) 165 | self.delegate?.swordRPCDidJoinGame(self, secret: secret) 166 | 167 | case .joinRequest: 168 | let requestData = data["user"] as! [String: Any] 169 | let joinRequest = try! self.decoder.decode( 170 | JoinRequest.self, from: self.encode(requestData) 171 | ) 172 | let secret = data["secret"] as! String 173 | self.joinRequestHandler?(self, joinRequest, secret) 174 | self.delegate?.swordRPCDidReceiveJoinRequest(self, request: joinRequest, secret: secret) 175 | 176 | case .ready: 177 | self.connectHandler?(self) 178 | self.delegate?.swordRPCDidConnect(self) 179 | self.updatePresence() 180 | 181 | case.spectate: 182 | let secret = data["secret"] as! String 183 | self.spectateGameHandler?(self, secret) 184 | self.delegate?.swordRPCDidSpectateGame(self, secret: secret) 185 | } 186 | } 187 | 188 | func updatePresence() { 189 | self.worker.asyncAfter(deadline: .now() + .seconds(15)) { [unowned self] in 190 | self.updatePresence() 191 | 192 | guard let presence = self.presence else { 193 | return 194 | } 195 | 196 | self.presence = nil 197 | 198 | let json = """ 199 | { 200 | "cmd": "SET_ACTIVITY", 201 | "args": { 202 | "pid": \(self.pid), 203 | "activity": \(String(data: try! self.encoder.encode(presence), encoding: .utf8)!) 204 | }, 205 | "nonce": "\(UUID().uuidString)" 206 | } 207 | """ 208 | 209 | try? self.send(json, .frame) 210 | } 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Register.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Register.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if !os(Linux) 12 | import CoreServices 13 | #else 14 | import Glibc 15 | #endif 16 | 17 | extension SwordRPC { 18 | 19 | func createFile(_ name: String, at path: String, with data: String) { 20 | let fm = FileManager.default 21 | 22 | try? fm.createDirectory( 23 | atPath: NSHomeDirectory() + path, 24 | withIntermediateDirectories: true, 25 | attributes: nil 26 | ) 27 | 28 | fm.createFile( 29 | atPath: path + "/" + name, 30 | contents: data.data(using: .utf8), 31 | attributes: nil 32 | ) 33 | } 34 | 35 | func registerUrl() { 36 | #if !os(Linux) 37 | guard self.steamId == nil else { 38 | self.registerSteamGame() 39 | return 40 | } 41 | 42 | guard let bundleId = Bundle.main.bundleIdentifier else { 43 | return 44 | } 45 | 46 | let scheme = "discord-\(self.appId)" as CFString 47 | var response = LSSetDefaultHandlerForURLScheme(scheme, bundleId as CFString) 48 | 49 | guard response == 0 else { 50 | print("[SwordRPC] Error creating URL scheme: \(response)") 51 | return 52 | } 53 | 54 | let bundleUrl = Bundle.main.bundleURL as CFURL 55 | response = LSRegisterURL(bundleUrl, true) 56 | 57 | if response != 0 { 58 | print("[SwordRPC] Error registering application: \(response)") 59 | } 60 | #else 61 | var execPath = "" 62 | 63 | if let steamId = self.steamId { 64 | execPath = "xdg-open steam://rungameid/\(steamId)" 65 | }else { 66 | let exec = UnsafeMutablePointer.allocate(capacity: Int(PATH_MAX) + 1) 67 | 68 | defer { 69 | free(exec) 70 | } 71 | 72 | let n = readLink("/proc/self/exe", exec, Int(PATH_MAX)) 73 | guard n >= 0 else { 74 | print("[SwordRPC] Error getting game's execution path") 75 | return 76 | } 77 | exec[n] = 0 78 | 79 | execPath = String(cString: exec) 80 | } 81 | 82 | self.createFile( 83 | "discord-\(self.appId).desktop", 84 | at: "/.local/share/applications", 85 | with: """ 86 | [Desktop Entry] 87 | Name=Game \(self.appId) 88 | Exec=\(execPath) %u 89 | Type=Application 90 | NoDisplay=true 91 | Categories=Discord;Games; 92 | MimeType=x-scheme-handler/discord-\(self.appId) 93 | """ 94 | ) 95 | 96 | let command = "xdg-mime default discord-\(self.appId).desktop x-scheme-handler/discord-\(self.appId)" 97 | 98 | if system(command) < 0 { 99 | print("[SwordRPC] Error registering URL scheme") 100 | } 101 | #endif 102 | } 103 | 104 | #if !os(Linux) 105 | func registerSteamGame() { 106 | self.createFile( 107 | "\(self.appId).json", 108 | at: "/Library/Application Support/discord/games", 109 | with: """ 110 | { 111 | "command": "steam://rungameid/\(self.steamId!)" 112 | } 113 | """ 114 | ) 115 | } 116 | #endif 117 | 118 | } 119 | -------------------------------------------------------------------------------- /Sources/SwordRPC/SwordRPC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwordRPC.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Socket 11 | 12 | public class SwordRPC { 13 | 14 | // MARK: App Info 15 | public let appId: String 16 | public var handlerInterval: Int 17 | public let autoRegister: Bool 18 | public let steamId: String? 19 | 20 | // MARK: Technical stuff 21 | let pid: Int32 22 | var socket: Socket? = nil 23 | let worker: DispatchQueue 24 | let encoder = JSONEncoder() 25 | let decoder = JSONDecoder() 26 | var presence: RichPresence? = nil 27 | 28 | // MARK: Event Handlers 29 | public weak var delegate: SwordRPCDelegate? = nil 30 | var connectHandler: ((_ rpc: SwordRPC) -> ())? = nil 31 | var disconnectHandler: ((_ rpc: SwordRPC, _ code: Int?, _ msg: String?) -> ())? = nil 32 | var errorHandler: ((_ rpc: SwordRPC, _ code: Int, _ msg: String) -> ())? = nil 33 | var joinGameHandler: ((_ rpc: SwordRPC, _ secret: String) -> ())? = nil 34 | var spectateGameHandler: ((_ rpc: SwordRPC, _ secret: String) -> ())? = nil 35 | var joinRequestHandler: ((_ rpc: SwordRPC, _ request: JoinRequest, _ secret: String) -> ())? = nil 36 | 37 | public init( 38 | appId: String, 39 | handlerInterval: Int = 1000, 40 | autoRegister: Bool = true, 41 | steamId: String? = nil 42 | ) { 43 | self.appId = appId 44 | self.handlerInterval = handlerInterval 45 | self.autoRegister = autoRegister 46 | self.steamId = steamId 47 | 48 | self.pid = ProcessInfo.processInfo.processIdentifier 49 | self.worker = DispatchQueue( 50 | label: "me.azoy.swordrpc.\(pid)", 51 | qos: .userInitiated 52 | ) 53 | self.encoder.dateEncodingStrategy = .secondsSince1970 54 | 55 | self.createSocket() 56 | 57 | self.registerUrl() 58 | } 59 | 60 | public func connect() { 61 | let tmp = NSTemporaryDirectory() 62 | 63 | guard let socket = self.socket else { 64 | print("[SwordRPC] Unable to connect") 65 | return 66 | } 67 | 68 | for i in 0 ..< 10 { 69 | try? socket.connect(to: "\(tmp)/discord-ipc-\(i)") 70 | 71 | guard !socket.isConnected else { 72 | self.handshake() 73 | self.receive() 74 | 75 | self.subscribe("ACTIVITY_JOIN") 76 | self.subscribe("ACTIVITY_SPECTATE") 77 | self.subscribe("ACTIVITY_JOIN_REQUEST") 78 | 79 | return 80 | } 81 | } 82 | 83 | print("[SwordRPC] Discord not detected") 84 | } 85 | 86 | public func setPresence(_ presence: RichPresence) { 87 | self.presence = presence 88 | } 89 | 90 | public func reply(to request: JoinRequest, with reply: JoinReply) { 91 | let json = """ 92 | { 93 | "cmd": "\( 94 | reply == .yes ? "SEND_ACTIVITY_JOIN_INVITE" : "CLOSE_ACTIVITY_JOIN_REQUEST" 95 | )", 96 | "args": { 97 | "user_id": "\(request.userId)" 98 | } 99 | } 100 | """ 101 | 102 | try? self.send(json, .frame) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Types/Enums.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Enums.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | enum OP: UInt32 { 10 | case handshake 11 | case frame 12 | case close 13 | case ping 14 | case pong 15 | } 16 | 17 | enum Event: String { 18 | case error = "ERROR" 19 | case join = "ACTIVITY_JOIN" 20 | case joinRequest = "ACTIVITY_JOIN_REQUEST" 21 | case ready = "READY" 22 | case spectate = "ACTIVITY_SPECTATE" 23 | } 24 | 25 | public enum JoinReply: Int { 26 | case no 27 | case yes 28 | case ignore 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Types/JoinRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JoinRequest.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | public struct JoinRequest: Decodable { 10 | let avatar: String 11 | let discriminator: String 12 | let userId: String 13 | let username: String 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case avatar 17 | case discriminator 18 | case userId = "id" 19 | case username 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Types/RichPresence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichPresence.swift 3 | // SwordRPC 4 | // 5 | // Created by Alejandro Alonso 6 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct RichPresence: Encodable { 12 | public var assets = Assets() 13 | public var details = "" 14 | public var instance = true 15 | public var party = Party() 16 | public var secrets = Secrets() 17 | public var state = "" 18 | public var timestamps = Timestamps() 19 | 20 | public init() {} 21 | } 22 | 23 | extension RichPresence { 24 | public struct Timestamps: Encodable { 25 | public var end: Date? = nil 26 | public var start: Date? = nil 27 | } 28 | 29 | public struct Assets: Encodable { 30 | public var largeImage: String? = nil 31 | public var largeText: String? = nil 32 | public var smallImage: String? = nil 33 | public var smallText: String? = nil 34 | 35 | enum CodingKeys: String, CodingKey { 36 | case largeImage = "large_image" 37 | case largeText = "large_text" 38 | case smallImage = "small_image" 39 | case smallText = "small_text" 40 | } 41 | } 42 | 43 | public struct Party: Encodable { 44 | public var id: String? = nil 45 | public var max: Int? = nil 46 | public var size: Int? = nil 47 | 48 | enum CodingKeys: String, CodingKey { 49 | case id 50 | case size 51 | } 52 | 53 | public func encode(to encoder: Encoder) throws { 54 | var container = encoder.container(keyedBy: CodingKeys.self) 55 | try container.encodeIfPresent(self.id, forKey: .id) 56 | 57 | guard let max = self.max, let size = self.size else { 58 | return 59 | } 60 | 61 | try container.encode([size, max], forKey: .size) 62 | } 63 | } 64 | 65 | public struct Secrets: Encodable { 66 | public var join: String? = nil 67 | public var match: String? = nil 68 | public var spectate: String? = nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/SwordRPC/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // SwordRPC 4 | // 5 | // 6 | // Utils.swift 7 | // SwordRPC 8 | // 9 | // Created by Alejandro Alonso 10 | // Copyright © 2017 Alejandro Alonso. All rights reserved. 11 | // 12 | 13 | import Foundation 14 | 15 | extension SwordRPC { 16 | 17 | func encode(_ value: Any) -> Data { 18 | do { 19 | return try JSONSerialization.data(withJSONObject: value, options: []) 20 | }catch { 21 | return Data() 22 | } 23 | } 24 | 25 | func decode(_ json: Data) -> [String: Any] { 26 | do { 27 | return try JSONSerialization.jsonObject(with: json, options: []) as! [String: Any] 28 | }catch { 29 | return [:] 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SwordRPC.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwordRPC' 3 | s.version = '0.2.0' 4 | s.summary = 'A Discord Rich Presence Library for Swift' 5 | s.homepage = 'https://github.com/Azoy/SwordRPC' 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = 'Azoy' 8 | s.source = { :git => 'https://github.com/Azoy/SwordRPC.git', :tag => s.version } 9 | 10 | s.osx.deployment_target = '10.11' 11 | s.pod_target_xcconfig = { 12 | 'SWIFT_VERSION' => '4.0.0' 13 | } 14 | 15 | s.source_files = 'Sources/SwordRPC/*.swift', 'Sources/SwordRPC/Types/*.swift' 16 | s.dependency 'BlueSocket', '~> 0.12' 17 | end 18 | --------------------------------------------------------------------------------