8
8
9
9
import Cocoa
10
10
import Alamofire
11
+ import Yams
12
+
13
+ class Subscribe : NSObject {
11
14
12
- class Subscribe : NSObject {
13
-
14
15
@objc var subscribeFeed = " "
15
16
var isActive = true
16
17
@objc var maxCount = 0 // -1 is not limited
17
18
@objc var groupName = " "
18
19
@objc var token = " "
19
20
var cache = " "
20
-
21
+
21
22
var profileMgr : ServerProfileManager !
22
-
23
- init ( initUrlString: String , initGroupName: String , initToken: String , initMaxCount: Int ) {
23
+
24
+ init ( initUrlString: String , initGroupName: String , initToken: String , initMaxCount: Int ) {
24
25
super. init ( )
25
26
subscribeFeed = initUrlString
26
27
27
28
token = initToken
28
-
29
+
29
30
setMaxCount ( initMaxCount: initMaxCount)
30
31
setGroupName ( newGroupName: initGroupName)
31
32
profileMgr = ServerProfileManager . instance
32
33
}
33
- func getFeed( ) -> String {
34
+ func getFeed( ) -> String {
34
35
return subscribeFeed
35
36
}
36
- func setFeed( newFeed: String ) {
37
+ func setFeed( newFeed: String ) {
37
38
subscribeFeed = newFeed
38
39
}
39
- func diactivateSubscribe( ) {
40
+ func diactivateSubscribe( ) {
40
41
isActive = false
41
42
}
42
- func activateSubscribe( ) {
43
+ func activateSubscribe( ) {
43
44
isActive = true
44
45
}
45
46
func setGroupName( newGroupName: String ) {
46
47
func getGroupNameFromRes( resString: String ) {
47
48
let decodeRes = decode64 ( resString) !
48
49
let ssrregexp = " ssr://([A-Za-z0-9_-]+) "
49
50
let urls = splitor ( url: decodeRes, regexp: ssrregexp)
50
- let profile = ServerProfile . fromDictionary ( parseAppURLSchemes ( URL ( string: urls [ 0 ] ) ) ! as [ String : AnyObject ] )
51
+ let profile = ServerProfile . fromDictionary ( parseAppURLSchemes ( URL ( string: urls [ 0 ] ) ) ! as [ String : AnyObject ] )
51
52
self . groupName = profile. ssrGroup
52
53
}
53
54
if newGroupName != " " { return groupName = newGroupName }
54
55
if self . cache != " " { return getGroupNameFromRes ( resString: cache) }
55
- sendRequest ( url: self . subscribeFeed, options: " " , callback: { resString in
56
+ sendRequest ( url: self . subscribeFeed, options: " " , callback: { resString, contentType in
56
57
if resString == " " { return self . groupName = " New Subscribe " }
57
58
getGroupNameFromRes ( resString: resString)
58
59
self . cache = resString
@@ -64,11 +65,11 @@ class Subscribe: NSObject{
64
65
func getMaxCount( ) -> Int {
65
66
return maxCount
66
67
}
67
- static func fromDictionary( _ data: [ String : AnyObject ] ) -> Subscribe {
68
- var feed : String = " "
69
- var group : String = " "
70
- var token : String = " "
71
- var maxCount : Int = - 1
68
+ static func fromDictionary( _ data: [ String : AnyObject ] ) -> Subscribe {
69
+ var feed : String = " "
70
+ var group : String = " "
71
+ var token : String = " "
72
+ var maxCount : Int = - 1
72
73
for (key, value) in data {
73
74
switch key {
74
75
case " feed " :
@@ -86,33 +87,32 @@ class Subscribe: NSObject{
86
87
return Subscribe . init ( initUrlString: feed, initGroupName: group, initToken: token, initMaxCount: maxCount)
87
88
}
88
89
static func toDictionary( _ data: Subscribe ) -> [ String : AnyObject ] {
89
- var ret : [ String : AnyObject ] = [ : ]
90
+ var ret : [ String : AnyObject ] = [ : ]
90
91
ret [ " feed " ] = data. subscribeFeed as AnyObject
91
92
ret [ " group " ] = data. groupName as AnyObject
92
93
ret [ " token " ] = data. token as AnyObject
93
94
ret [ " maxCount " ] = data. maxCount as AnyObject
94
95
return ret
95
96
}
96
- fileprivate func sendRequest( url: String , options: Any , callback: @escaping ( String ) -> Void ) {
97
+ fileprivate func sendRequest( url: String , options: Any , callback: @escaping ( String , String ) -> Void ) {
97
98
let headers : HTTPHeaders = [
98
- // "Authorization": "Basic U2hhZG93c29ja1gtTkctUg==",
99
- // "Accept": "application/json",
100
99
" token " : self . token,
101
100
" User-Agent " : " ShadowsocksX-NG-R " + ( getLocalInfo ( ) [ " CFBundleShortVersionString " ] as! String )
102
101
]
103
-
102
+
104
103
AF . request ( url, headers: headers)
105
- . responseString { response in
106
- switch response. result {
107
- case . success:
108
- callback ( response. value!)
109
- case . failure( _) :
110
- callback ( " " )
111
- self . pushNotification ( title: " 请求失败 " , subtitle: " " , info: " 发送到 \( url) 的请求失败,请检查您的网络 " )
112
- }
104
+ . responseString { response in
105
+ switch response. result {
106
+ case . success:
107
+ let contentType = response. response? . allHeaderFields [ " Content-Type " ] as? String ;
108
+ callback ( response. value!, contentType!)
109
+ case . failure( _) :
110
+ callback ( " " , " " )
111
+ self . pushNotification ( title: " 请求失败 " , subtitle: " " , info: " 发送到 \( url) 的请求失败,请检查您的网络 " )
112
+ }
113
113
}
114
114
}
115
- func setMaxCount( initMaxCount: Int ) {
115
+ func setMaxCount( initMaxCount: Int ) {
116
116
func getMaxFromRes( resString: String ) {
117
117
let maxCountReg = " MAX=[0-9]+ "
118
118
let decodeRes = decode64 ( resString) !
@@ -121,71 +121,102 @@ class Subscribe: NSObject{
121
121
let result = String ( decodeRes [ range!] )
122
122
self . maxCount = Int ( result. replacingOccurrences ( of: " MAX= " , with: " " ) ) !
123
123
}
124
- else {
124
+ else {
125
125
self . maxCount = - 1
126
126
}
127
127
}
128
128
if initMaxCount != 0 { return self . maxCount = initMaxCount }
129
129
if cache != " " { return getMaxFromRes ( resString: cache) }
130
- sendRequest ( url: self . subscribeFeed, options: " " , callback: { resString in
130
+ sendRequest ( url: self . subscribeFeed, options: " " , callback: { resString, contentType in
131
131
if resString == " " { return } // Also should hold if token is wrong feedback
132
132
getMaxFromRes ( resString: resString)
133
133
self . cache = resString
134
134
} )
135
135
}
136
- func updateServerFromFeed( ) {
137
- func updateServerHandler( resString: String ) {
138
- let decodeRes = decode64 ( resString) !
139
- let ssrregexp = " ssr://([A-Za-z0-9_-]+) "
140
- let urls = splitor ( url: decodeRes, regexp: ssrregexp)
141
- // hold if user fill a maxCount larger then server return
142
- // Should push a notification about it and correct the user filled maxCOunt?
143
- let maxN = ( self . maxCount > urls. count) ? urls. count : ( self . maxCount == - 1 ) ? urls. count: self . maxCount
144
- // TODO change the loop into random pick
136
+ func updateServerFromFeed( ) {
137
+ func updateServerHandler( resString: String , _ contentType: String ) {
138
+ if ( !contentType. contains ( " application/octet-stream " ) && !contentType. contains ( " application/yaml " ) ) {
139
+ NSLog ( " unsupport content type " )
140
+ return
141
+ }
142
+
143
+ var proxiesOrUrls : [ Any ] = [ ]
144
+ if ( contentType. contains ( " application/octet-stream " ) ) {
145
+ let decodeRes = decode64 ( resString) !
146
+ if decodeRes. hasPrefix ( " ss:// " ) {
147
+ NSLog ( " unsupport ss type " )
148
+ } else if decodeRes. hasPrefix ( " ssr:// " ) {
149
+ proxiesOrUrls = splitor ( url: decodeRes, regexp: " ssr://([A-Za-z0-9_-]+) " )
150
+ }
151
+ } else if ( contentType. contains ( " application/yaml " ) ) {
152
+ do {
153
+ proxiesOrUrls = try YAMLDecoder ( ) . decode ( YamlContent . self, from: resString) . proxies
154
+ } catch {
155
+ NSLog ( " parse yaml failed " )
156
+ }
157
+ }
158
+
159
+ let maxN = ( self . maxCount > proxiesOrUrls. count) ? proxiesOrUrls. count : ( self . maxCount == - 1 ) ? proxiesOrUrls. count : self . maxCount
160
+
161
+ var profileDict : [ String : Any ] = [ : ]
145
162
for index in 0 ..< maxN {
146
- if let profileDict = parseAppURLSchemes ( URL ( string: urls [ index] ) ) {
147
- let profile = ServerProfile . fromDictionary ( profileDict as [ String : AnyObject ] )
148
- let ( dupResult, _) = self . profileMgr. isDuplicated ( profile: profile)
149
- let ( existResult, existIndex) = self . profileMgr. isExisted ( profile: profile)
150
- if dupResult {
151
- continue
163
+ if ( contentType. contains ( " application/octet-stream " ) ) {
164
+ let tempProfileDict = parseAppURLSchemes ( URL ( string: proxiesOrUrls [ index] as! String ) )
165
+ if ( tempProfileDict == nil ) {
166
+ return
152
167
}
153
- if existResult {
154
- self . profileMgr. profiles. replaceSubrange ( existIndex..< existIndex + 1 , with: [ profile] )
155
- continue
168
+
169
+ profileDict = tempProfileDict! as [ String : Any ]
170
+ } else if ( contentType. contains ( " application/yaml " ) ) {
171
+ let proxy = proxiesOrUrls [ index] as! Proxy
172
+ if ( proxy. type != " ss " ) {
173
+ NSLog ( " proxy type not ss " )
156
174
}
157
- self . profileMgr. profiles. append ( profile)
175
+ profileDict = [ " ServerHost " : proxy. server, " ServerPort " : proxy. port, " Method " : proxy. cipher, " Password " : proxy. password, " Remark " : proxy. name]
176
+ }
177
+
178
+ let profile = ServerProfile . fromDictionary ( profileDict)
179
+ let ( dupResult, _) = self . profileMgr. isDuplicated ( profile: profile)
180
+ let ( existResult, existIndex) = self . profileMgr. isExisted ( profile: profile)
181
+ if dupResult {
182
+ continue
183
+ }
184
+ if existResult {
185
+ self . profileMgr. profiles. replaceSubrange ( existIndex..< existIndex + 1 , with: [ profile] )
186
+ continue
158
187
}
188
+ self . profileMgr. profiles. append ( profile)
159
189
}
190
+
160
191
self . profileMgr. save ( )
161
192
pushNotification ( title: " 成功更新订阅 " , subtitle: " " , info: " 更新来自 \( subscribeFeed) 的订阅 " )
162
193
( NSApplication . shared. delegate as! AppDelegate ) . updateServersMenu ( )
163
194
( NSApplication . shared. delegate as! AppDelegate ) . updateRunningModeMenu ( )
164
195
}
165
-
166
- if ( !isActive) { return }
167
196
168
- sendRequest ( url: self . subscribeFeed, options: " " , callback: { resString in
197
+ if ( !isActive) { return }
198
+
199
+ sendRequest ( url: self . subscribeFeed, options: " " , callback: { resString, contentType in
169
200
if resString == " " { return }
170
- updateServerHandler ( resString: resString)
201
+ updateServerHandler ( resString: resString, contentType )
171
202
self . cache = resString
172
203
} )
173
204
}
174
- func feedValidator( ) -> Bool {
205
+ func feedValidator( ) -> Bool {
175
206
// is the right format
176
207
// should be http or https reg
177
208
// but we should not support http only feed
178
209
// TODO refine the regular expression
179
210
let feedRegExp = " http[s]?://[A-Za-z0-9-_/.=?]* "
180
- return subscribeFeed. range ( of: feedRegExp, options: . regularExpression) != nil
211
+ return subscribeFeed. range ( of: feedRegExp, options: . regularExpression) != nil
181
212
}
182
- fileprivate func pushNotification( title: String , subtitle: String , info: String ) {
213
+ fileprivate func pushNotification( title: String , subtitle: String , info: String ) {
183
214
let userNote = NSUserNotification ( )
184
215
userNote. title = title
185
216
userNote. subtitle = subtitle
186
217
userNote. informativeText = info
187
218
userNote. soundName = NSUserNotificationDefaultSoundName
188
-
219
+
189
220
NSUserNotificationCenter . default
190
221
. deliver ( userNote) ;
191
222
}
@@ -195,4 +226,21 @@ class Subscribe: NSObject{
195
226
func isExist( _ target: Subscribe ) -> Bool {
196
227
return self . subscribeFeed == target. subscribeFeed
197
228
}
229
+
230
+ //proxies:
231
+ //- {"name":"🇭🇰 Hong Kong 01","type":"ss","server":"hk.it.dev","port":1344,"cipher":"chacha20","password":"xoW","udp":true}
232
+ //- {"name":"🇭🇰 Hong Kong 02","type":"ss","server":"hk.kit.dev","port":1044,"cipher":"chacha20","password":"xoW","udp":true}
233
+ struct YamlContent : Codable {
234
+ var proxies : [ Proxy ]
235
+ }
236
+
237
+ struct Proxy : Codable {
238
+ var name : String
239
+ var type : String
240
+ var server : String
241
+ var port : Int
242
+ var cipher : String
243
+ var password : String
244
+ var udp : Bool
245
+ }
198
246
}
0 commit comments