diff --git a/Per mille.xcodeproj/project.pbxproj b/Per mille.xcodeproj/project.pbxproj index b6f5756..bc008bc 100644 --- a/Per mille.xcodeproj/project.pbxproj +++ b/Per mille.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 02B3528524B59C0B0040A23E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02B3528324B59C0B0040A23E /* Main.storyboard */; }; 02B3528724B59C160040A23E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02B3528624B59C160040A23E /* Assets.xcassets */; }; 02B3528A24B59C160040A23E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02B3528824B59C160040A23E /* LaunchScreen.storyboard */; }; + 02B44A5924C4FDC6001083C9 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B44A5724C4F6B7001083C9 /* Extensions.swift */; }; + 02B44A5A24C4FDCD001083C9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E9E8D4248CE7160086352A /* ViewController.swift */; }; 02B7EEF82491899800738B62 /* DataLoaderYouTube.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B7EEF72491899800738B62 /* DataLoaderYouTube.swift */; }; 02B7EEFA2491995400738B62 /* DataLoaderInstagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B7EEF92491995400738B62 /* DataLoaderInstagram.swift */; }; 02DDA4A824A7751E00F163A9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DDA4A724A7751E00F163A9 /* AppDelegate.swift */; }; @@ -28,7 +30,6 @@ 02DDA4BA24A7BD6400F163A9 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DDA4B924A7BD6400F163A9 /* ServiceManagement.framework */; }; 02DDA4BB24A7BD7800F163A9 /* LauncherApplication.app in Resources */ = {isa = PBXBuildFile; fileRef = 02DDA4A524A7751E00F163A9 /* LauncherApplication.app */; }; 02E9E8D3248CE7160086352A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E9E8D2248CE7160086352A /* AppDelegate.swift */; }; - 02E9E8D5248CE7160086352A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E9E8D4248CE7160086352A /* ViewController.swift */; }; 02E9E8D7248CE71D0086352A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02E9E8D6248CE71D0086352A /* Assets.xcassets */; }; 02E9E8DA248CE71D0086352A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02E9E8D8248CE71D0086352A /* Main.storyboard */; }; /* End PBXBuildFile section */ @@ -124,6 +125,7 @@ 02B3528924B59C160040A23E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 02B3528B24B59C160040A23E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 02B3528F24B59C910040A23E /* Per mille iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Per mille iOS.entitlements"; sourceTree = ""; }; + 02B44A5724C4F6B7001083C9 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 02B7EEF72491899800738B62 /* DataLoaderYouTube.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoaderYouTube.swift; sourceTree = ""; }; 02B7EEF92491995400738B62 /* DataLoaderInstagram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoaderInstagram.swift; sourceTree = ""; }; 02DDA49D24A774E700F163A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -236,9 +238,10 @@ 02E9E8D2248CE7160086352A /* AppDelegate.swift */, 02E9E8D6248CE71D0086352A /* Assets.xcassets */, 02B7EEF92491995400738B62 /* DataLoaderInstagram.swift */, - 0224C85824963AD400EEC521 /* DataLoaderTwitter.swift */, 027D73A624AFBF8800031CE0 /* DataLoaderTikTok.swift */, + 0224C85824963AD400EEC521 /* DataLoaderTwitter.swift */, 02B7EEF72491899800738B62 /* DataLoaderYouTube.swift */, + 02B44A5724C4F6B7001083C9 /* Extensions.swift */, 025B3EB424A8A2DE000D0E2B /* Graphics */, 02E9E8DB248CE71D0086352A /* Info.plist */, 02E9E8D8248CE71D0086352A /* Main.storyboard */, @@ -411,11 +414,12 @@ buildActionMask = 2147483647; files = ( 02B7EEF82491899800738B62 /* DataLoaderYouTube.swift in Sources */, - 02E9E8D5248CE7160086352A /* ViewController.swift in Sources */, 0288C5A22496544E0074E25F /* APIKeys.swift in Sources */, + 02B44A5924C4FDC6001083C9 /* Extensions.swift in Sources */, 02E9E8D3248CE7160086352A /* AppDelegate.swift in Sources */, 027D73A724AFBF8800031CE0 /* DataLoaderTikTok.swift in Sources */, 02B7EEFA2491995400738B62 /* DataLoaderInstagram.swift in Sources */, + 02B44A5A24C4FDCD001083C9 /* ViewController.swift in Sources */, 0224C85924963AD400EEC521 /* DataLoaderTwitter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 2ee2334..aa5ced8 100644 --- a/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -14,8 +14,8 @@ filePath = "Per mille/ViewController.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "53" - endingLineNumber = "53" + startingLineNumber = "66" + endingLineNumber = "66" landmarkName = "viewDidLoad()" landmarkType = "7"> @@ -30,8 +30,8 @@ filePath = "Per mille/ViewController.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "83" - endingLineNumber = "83" + startingLineNumber = "96" + endingLineNumber = "96" landmarkName = "instagramSlider(_:)" landmarkType = "7"> diff --git a/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcschemes/xcschememanagement.plist b/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcschemes/xcschememanagement.plist index 174aefa..3f4ea5e 100644 --- a/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Per mille.xcodeproj/xcuserdata/darragh.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ LauncherApplication.xcscheme_^#shared#^_ orderHint - 2 + 1 Per mille Launcher Application.xcscheme_^#shared#^_ @@ -27,7 +27,7 @@ Per mille iOS.xcscheme_^#shared#^_ orderHint - 1 + 2 Per mille.xcscheme_^#shared#^_ diff --git a/Per mille/AppDelegate.swift b/Per mille/AppDelegate.swift index 04bccbb..672c36e 100644 --- a/Per mille/AppDelegate.swift +++ b/Per mille/AppDelegate.swift @@ -277,7 +277,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { DataLoaderYouTube().loadYouTubeDataChannel() DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: { - DataLoaderYouTube().loadYouTubeDataActivities() + DataLoaderYouTube().loadYouTubeDataSearch() }) DispatchQueue.main.asyncAfter(deadline: .now() + 4.0, execute: { @@ -328,7 +328,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } if twitterData.includes?.tweets[0].publicMetrics.likeCount != nil { - self.twitterPinnedTweet.title = "Pinned Tweet: ♺ \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.retweetCount as! Int))), ❝❞ \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.quoteCount as! Int))), ♥ \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.likeCount as! Int))), 🗨 \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.replyCount as! Int)))" + self.twitterPinnedTweet.title = "Pinned Tweet: ♺ \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.retweetCount as! Int))), ❝❞ \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.quoteCount as! Int))), ♥ \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.likeCount as! Int))), 🗨 \(String(format: "%U", locale: Locale.current, (twitterData.includes?.tweets[0].publicMetrics.replyCount as? Int ?? 0)))" } else { self.twitterPinnedTweet.title = "Pinned Tweet: None" @@ -366,7 +366,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { if youTubeDataVideos.pageInfo?.resultsPerPage == 1 { let youTubeLastVideoViews = youTubeDataVideos.items?[0].statistics.viewCount as! String let youTubeLastVideoLikes = youTubeDataVideos.items?[0].statistics.likeCount as! String - let youTubeLastVideoComments = youTubeDataVideos.items?[0].statistics.commentCount as! String + let youTubeLastVideoComments = youTubeDataVideos.items?[0].statistics.commentCount as? String ?? "0" self.youTubeLastVideo.title = "Last Video: ▶ \(youTubeLastVideoViews), ♥ \(youTubeLastVideoLikes), 🗨 \(youTubeLastVideoComments)" } else { self.youTubeLastVideo.title = "Error; if internet connectivity & Channel ID okay, problem is with YouTube API. Try later" diff --git a/Per mille/Base.lproj/Main.storyboard b/Per mille/Base.lproj/Main.storyboard index 6a22771..4e74234 100644 --- a/Per mille/Base.lproj/Main.storyboard +++ b/Per mille/Base.lproj/Main.storyboard @@ -686,11 +686,11 @@ - + - + @@ -699,7 +699,7 @@ - + @@ -708,7 +708,7 @@ - + @@ -720,7 +720,7 @@ - + @@ -729,7 +729,7 @@ - + @@ -738,7 +738,7 @@ - + - + - + - + @@ -772,17 +772,8 @@ - - - - - - - - - - + @@ -791,7 +782,7 @@ - + @@ -803,7 +794,7 @@ - + @@ -812,7 +803,7 @@ - + @@ -821,7 +812,7 @@ - + - + - + @@ -852,7 +843,7 @@ - + @@ -864,7 +855,7 @@ - + @@ -873,7 +864,7 @@ - + @@ -882,7 +873,7 @@ - + - - - - - - - - - + @@ -917,7 +900,7 @@ - + @@ -951,7 +934,7 @@ - + @@ -960,7 +943,7 @@ - + @@ -969,7 +952,7 @@ - + - + - + @@ -1000,7 +983,7 @@ + + + + + + + + + + + + + + + + + + @@ -1079,12 +1095,13 @@ + - + diff --git a/Per mille/DataLoaderTwitter.swift b/Per mille/DataLoaderTwitter.swift index 14a23c8..02bc832 100644 --- a/Per mille/DataLoaderTwitter.swift +++ b/Per mille/DataLoaderTwitter.swift @@ -86,7 +86,7 @@ struct Tweet: Codable { // MARK: - TweetPublicMetrics struct TweetPublicMetrics: Codable { - var retweetCount, replyCount, likeCount, quoteCount: Int + var retweetCount, replyCount, likeCount, quoteCount: Int? enum CodingKeys: String, CodingKey { case retweetCount = "retweet_count" diff --git a/Per mille/DataLoaderYouTube.swift b/Per mille/DataLoaderYouTube.swift index 3362dc4..2b0079b 100644 --- a/Per mille/DataLoaderYouTube.swift +++ b/Per mille/DataLoaderYouTube.swift @@ -88,76 +88,69 @@ struct PageInfo: Codable { var resultsPerPage: Int = 0 } +// MARK: - - -struct YouTubeDataStructureActivities: Codable { - var kind: String = "" - var etag: String = "" - var items: [ActivitiesItem]? -// var nextPageToken: String = "" -// var pageInfo: ActivitiesPageInfo? +struct YouTubeDataStructureSearch: Codable { +// var kind, etag, nextPageToken, regionCode: String +// var pageInfo: PageInfo? + var items: [SearchItem]? + } -struct ActivitiesItem: Codable { - var kind: String = "" - var etag: String = "" - var id: String = "" -// var snippet: ActivitiesSnippet - var contentDetails: ActivitiesContentDetails -} - -struct ActivitiesContentDetails: Codable { - var upload: ActivitiesUpload +struct SearchItem: Codable { +// var kind, etag: String + var id: ID +// var snippet: Snippet } -struct ActivitiesUpload: Codable { - var videoID: String = "" +struct ID: Codable { + var kind, videoID: String enum CodingKeys: String, CodingKey { + case kind case videoID = "videoId" } } -struct ActivitiesSnippet: Codable { - var publishedAt: Date - var channelID, title, snippetDescription: String - var thumbnails: ActivitiesThumbnails - var channelTitle: String = "" - var type: String = "" - var groupID: String = "" - - enum CodingKeys: String, CodingKey { - case publishedAt - case channelID = "channelId" - case title - case snippetDescription = "description" - case thumbnails, channelTitle, type - case groupID = "groupId" - } -} +//struct Snippet: Codable { +// var publishedAt: Date +// var channelID, title, snippetDescription: String +// var thumbnails: Thumbnails +// var channelTitle, liveBroadcastContent: String +// var publishTime: Date +// +// enum CodingKeys: String, CodingKey { +// case publishedAt +// case channelID = "channelId" +// case title +// case snippetDescription = "description" +// case thumbnails, channelTitle, liveBroadcastContent, publishTime +// } +//} -struct ActivitiesThumbnails: Codable { - var thumbnailsDefault, medium, high, standard: ActivitiesDefault - var maxres: ActivitiesDefault - enum CodingKeys: String, CodingKey { - case thumbnailsDefault = "default" - case medium, high, standard, maxres - } -} +//struct Thumbnails: Codable { +// var thumbnailsDefault, medium, high: Default +// +// enum CodingKeys: String, CodingKey { +// case thumbnailsDefault = "default" +// case medium, high +// } +//} -struct ActivitiesDefault: Codable { - var url: String - var width, height: Int -} -struct ActivitiesPageInfo: Codable { - var totalResults, resultsPerPage: Int -} +//struct Default: Codable { +// var url: String +// var width, height: Int +//} +//struct PageInfo: Codable { +// var totalResults, resultsPerPage: Int +//} +// MARK: - @@ -235,7 +228,7 @@ struct VideosStatistics: Codable { var likeCount: String var dislikeCount: String var favoriteCount: String - var commentCount: String + var commentCount: String? = "0" } struct VideosPageInfo: Codable { @@ -252,7 +245,7 @@ struct VideosPageInfo: Codable { // define an instance of the youtube data that can be filled by the data loader and read by the menu var youTubeData = YouTubeDataStructureChannels() -var youTubeDataActivities = YouTubeDataStructureActivities() +var youTubeDataSearch = YouTubeDataStructureSearch() var youTubeDataVideos = YouTubeDataStructureVideos() @@ -306,17 +299,17 @@ var youTubeDataVideos = YouTubeDataStructureVideos() dataTask.resume() } +//MARK: - - - func loadYouTubeDataActivities() { + func loadYouTubeDataSearch() { let headers = [ // "Authorization": "Bearer [YOUR_ACCESS_TOKEN]", "Accept": "application/json" ] - let request = NSMutableURLRequest(url: NSURL(string: "https://www.googleapis.com/youtube/v3/activities?part=snippet%2CcontentDetails&channelId=\(AppDelegate().defaults.object(forKey:"YouTubeChannelID") as? String ?? String())&key=\(APIKeyYouTube)&maxResults=1")! as URL, + let request = NSMutableURLRequest(url: NSURL(string: "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=\(AppDelegate().defaults.object(forKey:"YouTubeChannelID") as? String ?? String())&order=date&key=\(APIKeyYouTube)&maxResults=1")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "GET" @@ -328,7 +321,7 @@ var youTubeDataVideos = YouTubeDataStructureVideos() print(error) } else { let httpResponse = response as? HTTPURLResponse -// print("Received from the YouTube Activities API") +// print("Received from the YouTube Search API") // if let data = data, // let urlContent = NSString(data: data, encoding: String.Encoding.ascii.rawValue) { // print(urlContent) @@ -338,11 +331,11 @@ var youTubeDataVideos = YouTubeDataStructureVideos() //Parse JSON let decoder = JSONDecoder() do { - let dataFromYouTubeActivities = try decoder.decode(YouTubeDataStructureActivities.self, from: data!) - youTubeDataActivities = dataFromYouTubeActivities + let dataFromYouTubeSearch = try decoder.decode(YouTubeDataStructureSearch.self, from: data!) + youTubeDataSearch = dataFromYouTubeSearch } catch { - print("Error in YouTube Activities JSON parsing") + print("Error in YouTube Search JSON parsing") // print(youTubeData) } } @@ -359,7 +352,7 @@ var youTubeDataVideos = YouTubeDataStructureVideos() "Accept": "application/json" ] - let request = NSMutableURLRequest(url: NSURL(string: "https://www.googleapis.com/youtube/v3/videos?part=snippet%2CcontentDetails%2Cstatistics&id=\(youTubeDataActivities.items?[0].contentDetails.upload.videoID ?? "")&key=\(APIKeyYouTube)")! as URL, + let request = NSMutableURLRequest(url: NSURL(string: "https://www.googleapis.com/youtube/v3/videos?part=snippet%2CcontentDetails%2Cstatistics&id=\(youTubeDataSearch.items?[0].id.videoID ?? "")&key=\(APIKeyYouTube)")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "GET" diff --git a/Per mille/Extensions.swift b/Per mille/Extensions.swift new file mode 100644 index 0000000..0f16a25 --- /dev/null +++ b/Per mille/Extensions.swift @@ -0,0 +1,54 @@ +// +// Extensions.swift +// RegexMania +// +// Created by Alex Nagy on 12/02/2019. +// Copyright © 2019 Alex Nagy. All rights reserved. +// + +import Foundation + +// more info here https://emailregex.com/regular-expressions-cheat-sheet/ + +extension String { + + enum ValidityType { + case youTubeChannelID +// case age +// case email +// case password +// case website + } + + enum Regex: String { + case youTubeChannelID = "[-_A-Za-z0-9]{21,23}[AQgw]" +// case age = "[0-9]{2,2}" +// case email = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" +// case password = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@$!%*?&#])[A-Za-z\\d$@$!%*?&#]{6,25}" +// case website = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?" // any site url +// case website = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.(org|com)(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?" // any site url with .com or .org and subpages +// case website = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.(org|com)" // any site url with .com or .org and NO subpages +// case website = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.org" // any site url with only .org and NO subpages + } + + func isValid(_ validityType: ValidityType) -> Bool { + let format = "SELF MATCHES %@" + var regex = "" + + switch validityType { + case .youTubeChannelID: + regex = Regex.youTubeChannelID.rawValue +// case .age: +// regex = Regex.age.rawValue +// case .email: +// regex = Regex.email.rawValue +// case .password: +// regex = Regex.password.rawValue +// case .website: +// regex = Regex.website.rawValue + } + + return NSPredicate(format: format, regex).evaluate(with: self) + } + +} diff --git a/Per mille/ViewController.swift b/Per mille/ViewController.swift index 21e7877..1ce1fcf 100644 --- a/Per mille/ViewController.swift +++ b/Per mille/ViewController.swift @@ -10,6 +10,17 @@ import Cocoa import ServiceManagement class ViewController: NSViewController { + + let validityType: String.ValidityType = .youTubeChannelID + + @objc fileprivate func handleTextChange() { + guard let text = youTubeField?.stringValue else { return } + if text.isValid(validityType) { + youTubeGuidanceField.isHidden = true + } else { + youTubeGuidanceField.isHidden = false + } + } @IBOutlet weak var autorunAtStartup: NSSwitch! @@ -28,6 +39,8 @@ class ViewController: NSViewController { @IBOutlet weak var youTubeSlider: NSSwitch! @IBOutlet weak var youTubeLabel: NSTextField! @IBOutlet weak var youTubeField: NSTextField! + @IBOutlet weak var youTubeGuidanceField: NSTextField! + @IBOutlet weak var perMilleVersion: NSTextField! @@ -39,7 +52,7 @@ class ViewController: NSViewController { super.viewDidLoad() // Do any additional setup after loading the view. - + perMilleVersion.stringValue = (nsObject.self ?? 1.00 as AnyObject) as! String autorunAtStartup.state.self = AppDelegate().defaults.object(forKey:"AutorunAtStartup") as? NSControl.StateValue ?? NSControl.StateValue(0) @@ -167,6 +180,7 @@ class ViewController: NSViewController { @IBAction func youTubeButtonClicked(_ sender: Any) { AppDelegate().defaults.set(youTubeField.stringValue, forKey: "YouTubeChannelID") youTubeLabel.stringValue = AppDelegate().defaults.object(forKey:"YouTubeChannelID") as? String ?? String() + handleTextChange() if youTubeLabel.stringValue == ""{ AppDelegate().defaults.set(0, forKey: "YouTubeInUse") } @@ -180,9 +194,8 @@ class ViewController: NSViewController { @IBAction func youTubeHelp(_ sender: Any) { - NSWorkspace.shared.open(URL(string: "https://support.google.com/youtube/answer/3250431?hl=en")!) + NSWorkspace.shared.open(URL(string: "https://commentpicker.com/youtube-channel-id.php#youtube-channel-id")!) } - }