diff options
| -rw-r--r-- | QSThingsPlugin.xcodeproj/project.pbxproj | 1 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/CompleteTaskAction.h | 2 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/CompleteTaskAction.m | 10 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/SearchThingsAction.h | 2 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/SearchThingsAction.m | 6 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/ShowInThingsAction.h | 1 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/ShowInThingsAction.m | 6 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/KeychainHelper.h | 7 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/KeychainHelper.m | 7 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/ThingsHelper.h | 34 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/ThingsHelper.m | 70 |
11 files changed, 116 insertions, 30 deletions
diff --git a/QSThingsPlugin.xcodeproj/project.pbxproj b/QSThingsPlugin.xcodeproj/project.pbxproj index ced85a8..e46957e 100644 --- a/QSThingsPlugin.xcodeproj/project.pbxproj +++ b/QSThingsPlugin.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ Actions/ShowInThingsAction.m, Helpers/KeychainHelper.m, Helpers/SQLHelper.m, + Helpers/ThingsHelper.m, Parsers/QSThingsAreasParser.m, Parsers/QSThingsProjectsParser.m, Parsers/QSThingsTasksParser.m, diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.h b/QSThingsPlugin/Actions/CompleteTaskAction.h index 19609b9..e542c30 100644 --- a/QSThingsPlugin/Actions/CompleteTaskAction.h +++ b/QSThingsPlugin/Actions/CompleteTaskAction.h @@ -1,5 +1,5 @@ #import "Constants.h" -#import "KeychainHelper.h" +#import "ThingsHelper.h" #import <Foundation/Foundation.h> #import <QSCore/QSCore.h> diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.m b/QSThingsPlugin/Actions/CompleteTaskAction.m index 9673840..5ff1bd0 100644 --- a/QSThingsPlugin/Actions/CompleteTaskAction.m +++ b/QSThingsPlugin/Actions/CompleteTaskAction.m @@ -15,17 +15,15 @@ NS_ASSUME_NONNULL_BEGIN if (authenticationKey && [validTypes containsObject:directObject.primaryType]) { NSDictionary *contents = [directObject objectForType:directObject.primaryType]; - NSString *path = @"update"; + ThingsURLRoute route = ThingsURLRouteUpdate; if (directObject.primaryType == kProjectType) { - path = @"update-project"; + route = ThingsURLRouteUpdateProject; } if (contents) { NSString *uuid = [contents objectForKey:@"uuid"]; if (uuid) { - NSString *urlString = [NSString stringWithFormat:@"things:///%@?id=%@&auth-token=%@&completed=true", path, uuid, authenticationKey]; - NSURL *url = [NSURL URLWithString:urlString]; - - [[NSWorkspace sharedWorkspace] openURL:url]; + NSDictionary *parameters = @{@"id": uuid, @"completed": @"true"}; + [ThingsHelper callRoute:route withParameters:parameters]; } else { NSLog(@"Things object did not have a uuid."); } diff --git a/QSThingsPlugin/Actions/SearchThingsAction.h b/QSThingsPlugin/Actions/SearchThingsAction.h index 5f0ed48..c35bb1a 100644 --- a/QSThingsPlugin/Actions/SearchThingsAction.h +++ b/QSThingsPlugin/Actions/SearchThingsAction.h @@ -1,4 +1,4 @@ -#import "Constants.h" +#import "ThingsHelper.h" #import <Foundation/Foundation.h> #import <QSCore/QSCore.h> diff --git a/QSThingsPlugin/Actions/SearchThingsAction.m b/QSThingsPlugin/Actions/SearchThingsAction.m index 9b042b3..9f941e7 100644 --- a/QSThingsPlugin/Actions/SearchThingsAction.m +++ b/QSThingsPlugin/Actions/SearchThingsAction.m @@ -9,10 +9,8 @@ NS_ASSUME_NONNULL_BEGIN NSString *contents = [directObject displayName]; if (contents) { - NSString *urlString = [NSString stringWithFormat:@"things:///search?query=%@", contents]; - NSURL *url = [NSURL URLWithString:urlString]; - - [[NSWorkspace sharedWorkspace] openURL:url]; + NSDictionary *parameters = @{@"query": contents}; + [ThingsHelper callRoute:ThingsURLRouteSearch withParameters:parameters]; } else { NSLog(@"Could not get the text of the Things object."); } diff --git a/QSThingsPlugin/Actions/ShowInThingsAction.h b/QSThingsPlugin/Actions/ShowInThingsAction.h index 8d7790c..7a2fbc4 100644 --- a/QSThingsPlugin/Actions/ShowInThingsAction.h +++ b/QSThingsPlugin/Actions/ShowInThingsAction.h @@ -1,4 +1,5 @@ #import "Constants.h" +#import "ThingsHelper.h" #import <Foundation/Foundation.h> #import <QSCore/QSCore.h> diff --git a/QSThingsPlugin/Actions/ShowInThingsAction.m b/QSThingsPlugin/Actions/ShowInThingsAction.m index 91fe3ec..cd04f73 100644 --- a/QSThingsPlugin/Actions/ShowInThingsAction.m +++ b/QSThingsPlugin/Actions/ShowInThingsAction.m @@ -14,10 +14,8 @@ NS_ASSUME_NONNULL_BEGIN if (contents) { NSString *uuid = [contents objectForKey:@"uuid"]; if (uuid) { - NSString *urlString = [NSString stringWithFormat:@"things:///show?id=%@", uuid]; - NSURL *url = [NSURL URLWithString:urlString]; - - [[NSWorkspace sharedWorkspace] openURL:url]; + NSDictionary *parameters = @{@"id": uuid}; + [ThingsHelper callRoute:ThingsURLRouteShow withParameters:parameters]; } else { NSLog(@"Things object did not have a uuid."); } diff --git a/QSThingsPlugin/Helpers/KeychainHelper.h b/QSThingsPlugin/Helpers/KeychainHelper.h index d935bec..3665377 100644 --- a/QSThingsPlugin/Helpers/KeychainHelper.h +++ b/QSThingsPlugin/Helpers/KeychainHelper.h @@ -1,10 +1,3 @@ -// -// KeychainHelper.h -// Things Plugin -// -// Created by Ruben Beltran del Rio on 2025-09-10. -// - #import "Constants.h" #import <Foundation/Foundation.h> #import <Security/Security.h> diff --git a/QSThingsPlugin/Helpers/KeychainHelper.m b/QSThingsPlugin/Helpers/KeychainHelper.m index 9ce90f7..fcf8291 100644 --- a/QSThingsPlugin/Helpers/KeychainHelper.m +++ b/QSThingsPlugin/Helpers/KeychainHelper.m @@ -1,10 +1,3 @@ -// -// KeychainHelper.m -// Things Plugin -// -// Created by Ruben Beltran del Rio on 2025-09-10. -// - #import "KeychainHelper.h" NS_ASSUME_NONNULL_BEGIN diff --git a/QSThingsPlugin/Helpers/ThingsHelper.h b/QSThingsPlugin/Helpers/ThingsHelper.h new file mode 100644 index 0000000..29d3092 --- /dev/null +++ b/QSThingsPlugin/Helpers/ThingsHelper.h @@ -0,0 +1,34 @@ +#import "KeychainHelper.h" +#import "Constants.h" +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** + Enum of valid Things URL routes. + */ +typedef NSString* ThingsURLRoute NS_STRING_ENUM; + +extern ThingsURLRoute const ThingsURLRouteSearch; +extern ThingsURLRoute const ThingsURLRouteShow; +extern ThingsURLRoute const ThingsURLRouteUpdate; +extern ThingsURLRoute const ThingsURLRouteUpdateProject; +extern ThingsURLRoute const ThingsURLRouteAdd; +extern ThingsURLRoute const ThingsURLRouteAddProject; + +/** + Helper class to communicate with things using the URL API + */ +@interface ThingsHelper : NSObject + +/** + Calls a Things URL route with the specified parameters + @param route the Things URL route to call. Use ThingsURLRoute enum. + @param parameters NSDictionary of query parameters to include in the URL. Can be nil or empty. + @returns YES if the URL was successfully opened, NO if something went wrong. + */ ++ (BOOL)callRoute:(ThingsURLRoute)route withParameters:(NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Helpers/ThingsHelper.m b/QSThingsPlugin/Helpers/ThingsHelper.m new file mode 100644 index 0000000..cc7267b --- /dev/null +++ b/QSThingsPlugin/Helpers/ThingsHelper.m @@ -0,0 +1,70 @@ +#import "ThingsHelper.h" + +NS_ASSUME_NONNULL_BEGIN + +ThingsURLRoute const ThingsURLRouteSearch = @"search"; +ThingsURLRoute const ThingsURLRouteShow = @"show"; +ThingsURLRoute const ThingsURLRouteUpdate = @"update"; +ThingsURLRoute const ThingsURLRouteUpdateProject = @"update-project"; +ThingsURLRoute const ThingsURLRouteAdd = @"add"; +ThingsURLRoute const ThingsURLRouteAddProject = @"add-project"; + +@implementation ThingsHelper + ++ (instancetype)sharedInstance { + static ThingsHelper *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + ++ (BOOL)requiresAuthentication:(ThingsURLRoute)route { + if ([route isEqualToString:ThingsURLRouteSearch] || [route isEqualToString:ThingsURLRouteShow]) { + return NO; + } + return YES; +} + ++ (BOOL)callRoute:(ThingsURLRoute)route withParameters:(NSDictionary *)parameters { + NSString *authenticationKey = [KeychainHelper authenticationKey]; + if ([self requiresAuthentication:route] && !authenticationKey) { + NSLog(@"Attempted to call an authenticated things URL without an authentication key."); + return NO; + } + + NSString *baseURL = [NSString stringWithFormat:@"things:///%@", route]; + NSMutableArray *queryComponents = [NSMutableArray array]; + + if ([self requiresAuthentication:route]) { + [queryComponents addObject:[NSString stringWithFormat:@"auth-token=%@", authenticationKey]]; + } + + for (NSString *key in parameters) { + id value = parameters[key]; + NSString *encodedKey = [self urlEncodeString:key]; + NSString *encodedValue = [self urlEncodeString:[NSString stringWithFormat:@"%@", value]]; + [queryComponents addObject:[NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue]]; + } + + NSString *urlString = baseURL; + if ([queryComponents count] > 0) { + urlString = [NSString stringWithFormat:@"%@?%@", baseURL, [queryComponents componentsJoinedByString:@"&"]]; + } + + NSURL *url = [NSURL URLWithString:urlString]; + + [[NSWorkspace sharedWorkspace] openURL:url]; + + return YES; +} + ++ (NSString *)urlEncodeString:(NSString *)string { + NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet]; + return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; +} + +@end + +NS_ASSUME_NONNULL_END |