From 6a85bea5b58e06c29a389fd0f9e5bf6fdeb35511 Mon Sep 17 00:00:00 2001 From: bobrosoft Date: Tue, 23 Jul 2019 17:07:47 +0400 Subject: [PATCH] Add new "yandexWeather" widget --- MTMR.xcodeproj/project.pbxproj | 4 + MTMR/ItemsParsing.swift | 20 ++++ MTMR/TouchBarController.swift | 4 + MTMR/Widgets/YandexWeatherBarItem.swift | 145 ++++++++++++++++++++++++ README.md | 10 ++ 5 files changed, 183 insertions(+) create mode 100644 MTMR/Widgets/YandexWeatherBarItem.swift diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index 8830654..1d6e959 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; }; 36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; }; 36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; }; + 4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; }; 60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; }; 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; }; 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; }; @@ -91,6 +92,7 @@ 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = ""; }; 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = ""; }; 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = ""; }; + 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = ""; }; 60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = ""; }; 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = ""; }; 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = ""; }; @@ -294,6 +296,7 @@ 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */, 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */, 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */, + 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */, B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, ); @@ -458,6 +461,7 @@ 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */, B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */, + 4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */, diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 569b357..6591b40 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -179,6 +179,20 @@ class SupportedTypesHolder { parameters: [:] ) }, + + "yandexWeather": { decoder in + enum CodingKeys: String, CodingKey { case refreshInterval } + let container = try decoder.container(keyedBy: CodingKeys.self) + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) + let action = try ActionType(from: decoder) + let longAction = try LongActionType(from: decoder) + return ( + item: .yandexWeather(interval: interval ?? 1800.00), + action, + longAction, + parameters: [:] + ) + }, "currency": { decoder in enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to; case full } @@ -335,6 +349,7 @@ enum ItemType: Decodable { case volume() case brightness(refreshInterval: Double) case weather(interval: Double, units: String, api_key: String, icon_type: String) + case yandexWeather(interval: Double) case currency(interval: Double, from: String, to: String, full: Bool) case inputsource() case music(interval: Double, disableMarquee: Bool) @@ -378,6 +393,7 @@ enum ItemType: Decodable { case volume case brightness case weather + case yandexWeather case currency case inputsource case music @@ -427,6 +443,10 @@ enum ItemType: Decodable { let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text" self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type) + + case .yandexWeather: + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + self = .yandexWeather(interval: interval) case .currency: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0 diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 7ad5b4c..09b79d4 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -35,6 +35,8 @@ extension ItemType { return "com.toxblh.mtmr.brightness" case .weather(interval: _, units: _, api_key: _, icon_type: _): return "com.toxblh.mtmr.weather" + case .yandexWeather(interval: _): + return "com.toxblh.mtmr.yandexWeather" case .currency(interval: _, from: _, to: _, full: _): return "com.toxblh.mtmr.currency" case .inputsource(): @@ -269,6 +271,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } case let .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type): barItem = WeatherBarItem(identifier: identifier, interval: interval, units: units, api_key: api_key, icon_type: icon_type) + case let .yandexWeather(interval: interval): + barItem = YandexWeatherBarItem(identifier: identifier, interval: interval) case let .currency(interval: interval, from: from, to: to, full: full): barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full) case .inputsource(): diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift new file mode 100644 index 0000000..0d4f9ab --- /dev/null +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -0,0 +1,145 @@ +// +// YandexWeatherBarItem.swift +// MTMR +// +// Created by bobrosoft on 22/07/2019. +// Copyright © 2018 Anton Palgunov. All rights reserved. +// + +import Cocoa +import CoreLocation + +class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate { + private let activity: NSBackgroundActivityScheduler + private let unitsStr = "°C" + private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"] + private var location: CLLocation! + private var prevLocation: CLLocation! + private var manager: CLLocationManager! + + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) { + activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") + activity.interval = interval + + super.init(identifier: identifier, title: "⏳") + + let status = CLLocationManager.authorizationStatus() + if status == .restricted || status == .denied { + print("User permission not given") + return + } + + if !CLLocationManager.locationServicesEnabled() { + print("Location services not enabled") + return + } + + activity.repeats = true + activity.qualityOfService = .utility + activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in + self.updateWeather() + completion(NSBackgroundActivityScheduler.Result.finished) + } + updateWeather() + + manager = CLLocationManager() + manager.delegate = self + manager.desiredAccuracy = kCLLocationAccuracyHundredMeters + manager.startUpdatingLocation() + + tapClosure = tapClosure ?? defaultTapAction + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func updateWeather() { + var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!) + urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format + + let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in + guard error == nil, let response = data?.utf8string else { + return + } +// print(response) + + var matches: [[String]] + var temperature: String? + matches = response.matchingStrings(regex: "fact__temp.*?temp__value.*?>(.*?)<") + temperature = matches.first?.item(at: 1) + + var icon: String? + matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<") + icon = matches.first?.item(at: 1) + if let _ = icon, let test = self.iconsSource[icon!] { + icon = test + } + + if temperature != nil { + DispatchQueue.main.async { + self.setWeather(text: "\(icon ?? "?") \(temperature!)\(self.unitsStr)") + } + } + } + + task.resume() + } + + func getWeatherUrl() -> String { + if location != nil { + return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)" + } else { + return "https://yandex.ru/pogoda/" // Yandex will try to determine your location by default + } + } + + func setWeather(text: String) { + title = text + } + + func defaultTapAction() { + print(getWeatherUrl()) + if let url = URL(string: getWeatherUrl()) { + NSWorkspace.shared.open(url) + } + } + + func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + let lastLocation = locations.last! + location = lastLocation + if prevLocation == nil { + updateWeather() + } + prevLocation = lastLocation + } + + func locationManager(_: CLLocationManager, didFailWithError error: Error) { + print(error) + } + + func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) { + updateWeather() + } +} + +extension String { + func matchingStrings(regex: String) -> [[String]] { + guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] } + let nsString = self as NSString + let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)) + return results.map { result in + (0.. Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/README.md b/README.md index a950b51..1d5a3fd 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,16 @@ To close a group, use the button: "api_key": "" // you can get the key on openweather ``` +#### `yandexWeather` + +> Provider: https://yandex.ru/pogoda. One click to open up weather forecast in your browser. \ +> Note: you need to allow using "Location Services" in your Mac OS "Security & Privacy" settings for MTMR + +```js + "type": "yandexWeather", + "refreshInterval": 600 // in seconds +``` + #### `currency` > Provider: https://coinbase.com