mirror of
https://github.com/qinyuhang/ShadowsocksX-NG-R.git
synced 2026-10-21 15:25:49 +00:00
Add Subscribe Class
Subscribe Class Now Can download the Server Profiles To local. TODO add manual update subscribe and period update subscribe and finish subscribe manager functions
This commit is contained in:
@@ -13,10 +13,25 @@ class Subscribe: NSObject{
|
||||
|
||||
var subscribeFeed = ""
|
||||
var isActive = true
|
||||
var maxCount = -1 // -1 is not limited
|
||||
var groupName = ""
|
||||
var token = ""
|
||||
|
||||
var profileMgr: ServerProfileManager!
|
||||
|
||||
init(initUrlString:String){
|
||||
init(initUrlString:String, initGroupName: String, initToken: String, initMaxCount: Int){
|
||||
super.init()
|
||||
subscribeFeed = initUrlString
|
||||
groupName = initGroupName
|
||||
token = initToken
|
||||
|
||||
if initMaxCount == 0 {
|
||||
setMaxCount()
|
||||
}
|
||||
else {
|
||||
maxCount = initMaxCount
|
||||
}
|
||||
profileMgr = ServerProfileManager.instance
|
||||
}
|
||||
func getFeed() -> String{
|
||||
return subscribeFeed
|
||||
@@ -30,30 +45,74 @@ class Subscribe: NSObject{
|
||||
func activateSubscribe(){
|
||||
isActive = true
|
||||
}
|
||||
func updateServerFromFeed(){
|
||||
if (!isActive){ return }
|
||||
Alamofire.request(subscribeFeed)
|
||||
.responseString {
|
||||
func setGroupName(newGroupName: String) -> Bool {
|
||||
groupName = newGroupName
|
||||
return true
|
||||
}
|
||||
func getGroupName() -> String {
|
||||
return groupName
|
||||
}
|
||||
func getMaxCount() -> Int {
|
||||
return maxCount
|
||||
}
|
||||
fileprivate func sendRequest(url: String, options: Any, callback: @escaping (String) -> Void) {
|
||||
let headers: HTTPHeaders = [
|
||||
// "Authorization": "Basic U2hhZG93c29ja1gtTkctUg==",
|
||||
// "Accept": "application/json",
|
||||
"token": self.token,
|
||||
"User-Agent": "ShadowsocksX-NG-R"
|
||||
]
|
||||
|
||||
Alamofire.request(url, headers: headers)
|
||||
.responseString{
|
||||
response in
|
||||
if response.result.isSuccess {
|
||||
if let v = response.result.value {
|
||||
// HERE DO the loop and check things IF exist update; if Duplicated skip
|
||||
print(v)
|
||||
let profile = ServerProfile()
|
||||
profile.remark = "New Server".localized
|
||||
self.profileMgr.profiles.append(profile)
|
||||
self.profileMgr.save()
|
||||
}
|
||||
callback(response.result.value!)
|
||||
}
|
||||
else{
|
||||
// res failed pushNotification
|
||||
print(response.result)
|
||||
callback("")//give a empty callback
|
||||
// alse push a notification indicate that the profile may not available
|
||||
}
|
||||
}
|
||||
}
|
||||
func setMaxCount() {
|
||||
sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
|
||||
|
||||
let maxCountReg = "MAX=[0-9]+"
|
||||
let decodeRes = decode64(resString)!
|
||||
let range = decodeRes.range(of: maxCountReg, options: .regularExpression)
|
||||
if range != nil {
|
||||
let result = decodeRes.substring(with:range!)
|
||||
self.maxCount = Int(result.replacingOccurrences(of: "MAX=", with: ""))!
|
||||
}
|
||||
else{
|
||||
self.maxCount = -1
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
func updateServerFromFeed(){
|
||||
if (!isActive){ return }
|
||||
sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
|
||||
let decodeRes = decode64(resString)!
|
||||
let ssrregexp = "ssr://([A-Za-z0-9_-]+)"
|
||||
let urls = splitor(url: decodeRes, regexp: ssrregexp)
|
||||
let maxN = self.maxCount == -1 ? urls.count: self.maxCount
|
||||
for index in 0..<maxN {
|
||||
|
||||
let profielDict = ParseAppURLSchemes(URL(string: urls[index]))//ParseSSURL(url)
|
||||
if let profielDict = profielDict {
|
||||
let profile = ServerProfile.fromDictionary(profielDict as [String : AnyObject])
|
||||
self.profileMgr.profiles.append(profile)
|
||||
}
|
||||
}
|
||||
self.profileMgr.save()
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateServersMenu()
|
||||
})
|
||||
}
|
||||
func feedValidator() -> Bool{
|
||||
// is the right format
|
||||
return true
|
||||
return subscribeFeed != "" && groupName != "" && token != ""
|
||||
}
|
||||
fileprivate func pushNotification(){
|
||||
|
||||
@@ -67,4 +126,7 @@ class Subscribe: NSObject{
|
||||
// if duplicated skip
|
||||
return false
|
||||
}
|
||||
fileprivate func isSame(source: Subscribe, target: Subscribe) -> Bool {
|
||||
return source.subscribeFeed == target.subscribeFeed && source.token == target.token && source.maxCount == target.maxCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,7 @@ class SubscribeManager:NSObject{
|
||||
defaults.set(subscribes, forKey: "Subscribe")
|
||||
return true
|
||||
}
|
||||
func updateAllServerFromSubscribe(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,30 @@ class SubscribePreferenceWindowController: NSWindowController {
|
||||
|
||||
@IBOutlet weak var FeedLabel: NSTextField!
|
||||
@IBOutlet weak var OKButton: NSButton!
|
||||
|
||||
@IBOutlet weak var FeedTextField: NSTextField!
|
||||
@IBOutlet weak var TokenTextField: NSTextField!
|
||||
@IBOutlet weak var GroupTextField: NSTextField!
|
||||
@IBOutlet weak var MaxCountTextField: NSTextField!
|
||||
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
|
||||
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
|
||||
// TODO load stored subscribes and prepare the manager
|
||||
}
|
||||
|
||||
@IBAction func onOk(_ sender: NSButton) {
|
||||
print(FeedTextField.stringValue)
|
||||
// TODO append to manager and save
|
||||
var subscribe = Subscribe.init(initUrlString: FeedTextField.stringValue, initGroupName: GroupTextField.stringValue, initToken: TokenTextField.stringValue, initMaxCount: MaxCountTextField.integerValue)
|
||||
window?.performClose(self)
|
||||
}
|
||||
|
||||
func bindSubscribe(index: Int){
|
||||
if index >= 0 && index <= SubscribeManager.instance.subscribes.count {
|
||||
var editSubscribe = SubscribeManager.instance.subscribes[index]
|
||||
FeedTextField.bind("value", to: editSubscribe, withKeyPath: "subscribeFeed", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SubscribePreferenceWindowController" customModule="ShadowsocksX_NG" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="FeedLabel" destination="uYX-0C-bOD" id="odZ-Tm-7SC"/>
|
||||
<outlet property="FeedTextField" destination="p1W-Md-Wfw" id="zh0-1b-0zw"/>
|
||||
<outlet property="GroupTextField" destination="MrH-Ft-1tn" id="txD-SB-Kpr"/>
|
||||
<outlet property="MaxCountTextField" destination="vQO-bq-Igs" id="feo-Jx-AXK"/>
|
||||
<outlet property="OKButton" destination="rC3-mg-tMh" id="RK5-lm-9qc"/>
|
||||
<outlet property="TokenTextField" destination="hnP-yo-KBb" id="aDw-vO-ZID"/>
|
||||
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
@@ -33,16 +37,16 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="05M-8P-vtL">
|
||||
<rect key="frame" x="20" y="124" width="124" height="101"/>
|
||||
<rect key="frame" x="20" y="86" width="124" height="139"/>
|
||||
<clipView key="contentView" id="g4O-N9-54h">
|
||||
<rect key="frame" x="1" y="1" width="122" height="99"/>
|
||||
<rect key="frame" x="1" y="1" width="122" height="137"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="sqe-fs-fHA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="122" height="99"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="122" height="137"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="minSize" width="122" height="99"/>
|
||||
<size key="minSize" width="122" height="137"/>
|
||||
<size key="maxSize" width="463" height="10000000"/>
|
||||
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
@@ -54,7 +58,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="bib-Nw-Xom">
|
||||
<rect key="frame" x="107" y="1" width="16" height="99"/>
|
||||
<rect key="frame" x="107" y="1" width="16" height="137"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
@@ -83,7 +87,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nzj-fK-Vip">
|
||||
<rect key="frame" x="20" y="105" width="20" height="20"/>
|
||||
<rect key="frame" x="20" y="20" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="CfY-uG-c0X"/>
|
||||
<constraint firstAttribute="height" constant="18" id="Lkb-PM-8fV"/>
|
||||
@@ -94,7 +98,7 @@
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jSv-em-0Gp">
|
||||
<rect key="frame" x="40" y="105" width="20" height="20"/>
|
||||
<rect key="frame" x="40" y="20" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="aEO-oC-a3N"/>
|
||||
</constraints>
|
||||
@@ -127,8 +131,26 @@
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vQO-bq-Igs">
|
||||
<rect key="frame" x="236" y="86" width="224" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="3kt-n3-uTF">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XLx-TZ-soe">
|
||||
<rect key="frame" x="184" y="88" width="41" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Count" id="wbG-HT-bdN">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rC3-mg-tMh">
|
||||
<rect key="frame" x="407" y="80" width="59" height="32"/>
|
||||
<rect key="frame" x="407" y="13" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="CfK-8Y-YNP">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -150,19 +172,19 @@ DQ
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="top" secondItem="hnP-yo-KBb" secondAttribute="bottom" constant="19" id="DsI-9d-P9b"/>
|
||||
<constraint firstAttribute="trailing" secondItem="MrH-Ft-1tn" secondAttribute="trailing" constant="20" symbolic="YES" id="L6b-S2-rXW"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="leading" secondItem="p1W-Md-Wfw" secondAttribute="leading" id="NbP-CB-QGM"/>
|
||||
<constraint firstItem="rC3-mg-tMh" firstAttribute="top" secondItem="MrH-Ft-1tn" secondAttribute="bottom" constant="16" id="TKp-sn-xiU"/>
|
||||
<constraint firstAttribute="bottom" secondItem="nzj-fK-Vip" secondAttribute="bottom" constant="106" id="Y3H-aQ-YNb"/>
|
||||
<constraint firstItem="rC3-mg-tMh" firstAttribute="top" secondItem="MrH-Ft-1tn" secondAttribute="bottom" constant="83" id="TKp-sn-xiU"/>
|
||||
<constraint firstAttribute="bottom" secondItem="nzj-fK-Vip" secondAttribute="bottom" constant="21" id="Y3H-aQ-YNb"/>
|
||||
<constraint firstItem="nzj-fK-Vip" firstAttribute="leading" secondItem="uYX-0C-bOD" secondAttribute="leading" id="Za5-x5-eJr"/>
|
||||
<constraint firstItem="Fkq-Nl-5rY" firstAttribute="centerY" secondItem="MrH-Ft-1tn" secondAttribute="centerY" id="bK9-gp-UzV"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="leading" secondItem="05M-8P-vtL" secondAttribute="trailing" constant="92" id="gLs-QP-U8s"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="trailing" secondItem="p1W-Md-Wfw" secondAttribute="trailing" id="iTW-Og-aCE"/>
|
||||
<constraint firstItem="P7L-eo-l3V" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="leading" id="lCH-5t-qVb"/>
|
||||
<constraint firstItem="nzj-fK-Vip" firstAttribute="top" secondItem="05M-8P-vtL" secondAttribute="bottom" id="mXA-9N-UNt"/>
|
||||
<constraint firstItem="nzj-fK-Vip" firstAttribute="top" secondItem="05M-8P-vtL" secondAttribute="bottom" constant="47" id="mXA-9N-UNt"/>
|
||||
<constraint firstItem="nzj-fK-Vip" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="20" symbolic="YES" id="mn4-pg-oMy"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="trailing" secondItem="hnP-yo-KBb" secondAttribute="trailing" id="pp9-gb-IcG"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="leading" secondItem="hnP-yo-KBb" secondAttribute="leading" id="qIK-qK-NsQ"/>
|
||||
<constraint firstItem="p1W-Md-Wfw" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="trailing" constant="26" id="tgx-dn-JvH"/>
|
||||
<constraint firstItem="05M-8P-vtL" firstAttribute="bottom" secondItem="MrH-Ft-1tn" secondAttribute="bottom" id="w2c-Wi-tzl"/>
|
||||
<constraint firstItem="05M-8P-vtL" firstAttribute="bottom" secondItem="MrH-Ft-1tn" secondAttribute="bottom" constant="38" id="w2c-Wi-tzl"/>
|
||||
<constraint firstItem="jSv-em-0Gp" firstAttribute="centerY" secondItem="nzj-fK-Vip" secondAttribute="centerY" id="w6C-hd-DIa"/>
|
||||
<constraint firstItem="05M-8P-vtL" firstAttribute="trailing" secondItem="uYX-0C-bOD" secondAttribute="trailing" id="wDW-Xw-AUf"/>
|
||||
<constraint firstItem="P7L-eo-l3V" firstAttribute="top" secondItem="uNY-1k-LwL" secondAttribute="bottom" constant="21" id="wl6-VJ-tLB"/>
|
||||
|
||||
@@ -39,7 +39,7 @@ func splitProfile(url: String, max: Int) -> [String] {
|
||||
return [""]
|
||||
}
|
||||
|
||||
fileprivate func splitor(url: String, regexp: String) -> [String] {
|
||||
func splitor(url: String, regexp: String) -> [String] {
|
||||
var ret: [String] = []
|
||||
var ssrUrl = url
|
||||
while ssrUrl.range(of:regexp, options: .regularExpression) != nil{
|
||||
|
||||
Reference in New Issue
Block a user