diff options
| -rw-r--r-- | Info.plist | 39 | ||||
| -rw-r--r-- | QSThingsPlugin.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/CompleteTaskAction.h | 12 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/CompleteTaskAction.m | 41 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h | 11 | ||||
| -rw-r--r-- | QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m | 21 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/KeychainHelper.h | 20 | ||||
| -rw-r--r-- | QSThingsPlugin/Helpers/KeychainHelper.m | 83 | ||||
| -rw-r--r-- | QSThingsPlugin/Parsers/QSThingsAreasParser.m | 4 | ||||
| -rw-r--r-- | QSThingsPlugin/QSThingsPluginSource.m | 3 | ||||
| -rw-r--r-- | QSThingsPlugin/QSThingsPluginSource.xib | 50 | ||||
| -rw-r--r-- | QSThingsPlugin/Types/Constants.h | 4 | ||||
| -rw-r--r-- | QSThingsPlugin/Types/Constants.m | 4 |
13 files changed, 241 insertions, 55 deletions
@@ -22,6 +22,45 @@ <string>QSThingsPluginSource</string> <key>QSActions</key> <dict> + <key>CompleteTaskAction</key> + <dict> + <key>actionClass</key> + <string>CompleteTaskAction</string> + <key>actionSelector</key> + <string>completeTask:</string> + <key>directTypes</key> + <array> + <string>things.project</string> + <string>things.task</string> + </array> + <key>enabled</key> + <true/> + <key>displaysResult</key> + <false/> + <key>name</key> + <string>Mark as Complete</string> + <key>icon</key> + <string>com.culturedcode.ThingsMac</string> + </dict> + <key>SetThingsAuthenticationKeyAction</key> + <dict> + <key>actionClass</key> + <string>SetThingsAuthenticationKeyAction</string> + <key>actionSelector</key> + <string>setAuthenticationKey:</string> + <key>directTypes</key> + <array> + <string>NSStringPboardType</string> + </array> + <key>enabled</key> + <true/> + <key>displaysResult</key> + <false/> + <key>name</key> + <string>Set Things Authentication Key</string> + <key>icon</key> + <string>com.culturedcode.ThingsMac</string> + </dict> <key>SearchThingsAction</key> <dict> <key>actionClass</key> diff --git a/QSThingsPlugin.xcodeproj/project.pbxproj b/QSThingsPlugin.xcodeproj/project.pbxproj index 2b09a13..ced85a8 100644 --- a/QSThingsPlugin.xcodeproj/project.pbxproj +++ b/QSThingsPlugin.xcodeproj/project.pbxproj @@ -39,14 +39,16 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( /Localized/Localizable.strings, + Actions/CompleteTaskAction.m, Actions/SearchThingsAction.m, + Actions/SetThingsAuthenticationKeyAction.m, Actions/ShowInThingsAction.m, + Helpers/KeychainHelper.m, Helpers/SQLHelper.m, Parsers/QSThingsAreasParser.m, Parsers/QSThingsProjectsParser.m, Parsers/QSThingsTasksParser.m, QSThingsPluginSource.m, - QSThingsPluginSource.xib, Types/Constants.m, ); target = 8D1AC9600486D14A00FE50C9 /* Things Plugin */; diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.h b/QSThingsPlugin/Actions/CompleteTaskAction.h new file mode 100644 index 0000000..19609b9 --- /dev/null +++ b/QSThingsPlugin/Actions/CompleteTaskAction.h @@ -0,0 +1,12 @@ +#import "Constants.h" +#import "KeychainHelper.h" +#import <Foundation/Foundation.h> +#import <QSCore/QSCore.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface CompleteTaskAction : QSActionProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.m b/QSThingsPlugin/Actions/CompleteTaskAction.m new file mode 100644 index 0000000..9673840 --- /dev/null +++ b/QSThingsPlugin/Actions/CompleteTaskAction.m @@ -0,0 +1,41 @@ +#import "CompleteTaskAction.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation CompleteTaskAction + +- (QSObject *)completeTask:(QSObject *)directObject +{ + NSSet *validTypes = [NSSet setWithObjects:kProjectType, kTaskType, nil]; + NSString *authenticationKey = [KeychainHelper authenticationKey]; + + if (!authenticationKey) { + NSLog(@"There is no authentication key."); + } + + if (authenticationKey && [validTypes containsObject:directObject.primaryType]) { + NSDictionary *contents = [directObject objectForType:directObject.primaryType]; + NSString *path = @"update"; + if (directObject.primaryType == kProjectType) { + path = @"update-project"; + } + 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]; + } else { + NSLog(@"Things object did not have a uuid."); + } + } else { + NSLog(@"Could not get the contents of the Things object."); + } + } + return directObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h new file mode 100644 index 0000000..376d6d4 --- /dev/null +++ b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h @@ -0,0 +1,11 @@ +#import "KeychainHelper.h" +#import <Foundation/Foundation.h> +#import <QSCore/QSCore.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface SetThingsAuthenticationKeyAction : QSActionProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m new file mode 100644 index 0000000..b9e7031 --- /dev/null +++ b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m @@ -0,0 +1,21 @@ +#import "SetThingsAuthenticationKeyAction.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SetThingsAuthenticationKeyAction + +- (QSObject *)setAuthenticationKey:(QSObject *)directObject +{ + + NSString *contents = [directObject displayName]; + if (contents) { + [KeychainHelper setAuthenticationKey:contents]; + } else { + [KeychainHelper deleteAuthenticationKey]; + } + return directObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Helpers/KeychainHelper.h b/QSThingsPlugin/Helpers/KeychainHelper.h new file mode 100644 index 0000000..d935bec --- /dev/null +++ b/QSThingsPlugin/Helpers/KeychainHelper.h @@ -0,0 +1,20 @@ +// +// 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> + +NS_ASSUME_NONNULL_BEGIN + +@interface KeychainHelper : NSObject ++ (NSString *)authenticationKey; ++ (OSStatus)setAuthenticationKey:(NSString *)authenticationKey; ++ (OSStatus)deleteAuthenticationKey; +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Helpers/KeychainHelper.m b/QSThingsPlugin/Helpers/KeychainHelper.m new file mode 100644 index 0000000..9ce90f7 --- /dev/null +++ b/QSThingsPlugin/Helpers/KeychainHelper.m @@ -0,0 +1,83 @@ +// +// KeychainHelper.m +// Things Plugin +// +// Created by Ruben Beltran del Rio on 2025-09-10. +// + +#import "KeychainHelper.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation KeychainHelper + ++ (NSString *)authenticationKey { + const char *service = [kKeychainService UTF8String]; + const char *account = [kAuthenticationKeyKeychainKey UTF8String]; + + UInt32 authenticationKeyLength = 0; + void *authenticationKeyData = NULL; + + OSStatus status = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + &authenticationKeyLength, &authenticationKeyData, NULL); + + if (status == errSecSuccess && authenticationKeyData != NULL) { + NSString *authenticationKey = [[NSString alloc] initWithBytes:authenticationKeyData + length:authenticationKeyLength + encoding:NSUTF8StringEncoding]; + SecKeychainItemFreeContent(NULL, authenticationKeyData); + return authenticationKey; + } + + return nil; +} + ++ (OSStatus)setAuthenticationKey:(NSString *)authenticationKey { + const char *service = [kKeychainService UTF8String]; + const char *account = [kAuthenticationKeyKeychainKey UTF8String]; + const char *authenticationKeyCString = [authenticationKey UTF8String]; + + // First try to find existing item + SecKeychainItemRef item = NULL; + OSStatus findStatus = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + NULL, NULL, &item); + + OSStatus status; + if (findStatus == errSecSuccess) { + // Update existing item + status = SecKeychainItemModifyAttributesAndData( + item, NULL, (UInt32)strlen(authenticationKeyCString), authenticationKeyCString); + CFRelease(item); + } else { + // Create new item + status = SecKeychainAddGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), + account, (UInt32)strlen(authenticationKeyCString), authenticationKeyCString, NULL); + } + + return status; +} + ++ (OSStatus) deleteAuthenticationKey { + const char *service = [kKeychainService UTF8String]; + const char *account = [kAuthenticationKeyKeychainKey UTF8String]; + + SecKeychainItemRef item = NULL; + OSStatus findStatus = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + NULL, NULL, &item); + + if (findStatus == errSecSuccess) { + OSStatus deleteStatus = SecKeychainItemDelete(item); + CFRelease(item); + return deleteStatus; + } + + return findStatus; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Parsers/QSThingsAreasParser.m b/QSThingsPlugin/Parsers/QSThingsAreasParser.m index d4f9b5f..f147603 100644 --- a/QSThingsPlugin/Parsers/QSThingsAreasParser.m +++ b/QSThingsPlugin/Parsers/QSThingsAreasParser.m @@ -21,9 +21,9 @@ QSObject *newObject = [QSObject makeObjectWithIdentifier:[dictionary objectForKey:@"uuid"]]; - [newObject setObject:dictionary forType:kProjectType]; + [newObject setObject:dictionary forType:kAreaType]; [newObject setName:[dictionary objectForKey:@"title"]]; - [newObject setPrimaryType:kProjectType]; + [newObject setPrimaryType:kAreaType]; [objects addObject:newObject]; }]; diff --git a/QSThingsPlugin/QSThingsPluginSource.m b/QSThingsPlugin/QSThingsPluginSource.m index 08984ef..f73b5ca 100644 --- a/QSThingsPlugin/QSThingsPluginSource.m +++ b/QSThingsPlugin/QSThingsPluginSource.m @@ -45,8 +45,7 @@ #pragma mark - Object Handler Methods - (void)setQuickIconForObject:(QSObject *)object { - [object - setIcon:[QSResourceManager imageNamed:@"com.culturedcode.ThingsMac"]]; + [object setIcon:[QSResourceManager imageNamed:@"com.culturedcode.ThingsMac"]]; } - (BOOL)objectHasChildren:(QSObject *)object { diff --git a/QSThingsPlugin/QSThingsPluginSource.xib b/QSThingsPlugin/QSThingsPluginSource.xib deleted file mode 100644 index ac6254e..0000000 --- a/QSThingsPlugin/QSThingsPluginSource.xib +++ /dev/null @@ -1,50 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> - <dependencies> - <deployment identifier="macosx"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/> - <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> - </dependencies> - <objects> - <customObject id="-2" userLabel="File's Owner" customClass="QSThingsPluginSource"> - <connections> - <outlet property="settingsView" destination="settings-view" id="settings-view-outlet"/> - </connections> - </customObject> - <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> - <customObject id="-3" userLabel="Application" customClass="NSObject"/> - <customView translatesAutoresizingMaskIntoConstraints="NO" id="settings-view"> - <rect key="frame" x="0.0" y="0.0" width="249" height="252"/> - <subviews> - <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="site-label"> - <rect key="frame" x="58" y="118" width="133" height="16"/> - <constraints> - <constraint firstAttribute="height" constant="16" id="Eoo-3h-Uin"/> - </constraints> - <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Nothing to configure!" id="site-label-cell"> - <font key="font" metaFont="system"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - </subviews> - <constraints> - <constraint firstItem="site-label" firstAttribute="centerX" secondItem="settings-view" secondAttribute="centerX" id="D0Z-9a-PUk"/> - <constraint firstItem="site-label" firstAttribute="centerY" secondItem="settings-view" secondAttribute="centerY" id="KkB-XD-j1l"/> - </constraints> - <point key="canvasLocation" x="155.5" y="180"/> - </customView> - <objectController id="JpK-62-U8F" userLabel="Settings"> - <declaredKeys> - <string>info.username</string> - <string>info.site</string> - <string>info.includeTags</string> - <string>info.host</string> - </declaredKeys> - <connections> - <binding destination="-2" name="contentObject" keyPath="self.selectedEntry" id="r1i-y5-4Mi"/> - <outlet property="content" destination="-2" id="UlK-eW-ayD"/> - </connections> - </objectController> - </objects> -</document> diff --git a/QSThingsPlugin/Types/Constants.h b/QSThingsPlugin/Types/Constants.h index e365a8f..044dcde 100644 --- a/QSThingsPlugin/Types/Constants.h +++ b/QSThingsPlugin/Types/Constants.h @@ -5,3 +5,7 @@ extern NSString *const kProjectType; extern NSString *const kAreaType; extern NSString *const kTagType; extern NSString *const kTaskType; + +#pragma mark - Keychain +extern NSString *const kKeychainService; +extern NSString *const kAuthenticationKeyKeychainKey; diff --git a/QSThingsPlugin/Types/Constants.m b/QSThingsPlugin/Types/Constants.m index 4abf9f7..e6158a2 100644 --- a/QSThingsPlugin/Types/Constants.m +++ b/QSThingsPlugin/Types/Constants.m @@ -5,3 +5,7 @@ NSString *const kProjectType = @"things.project"; NSString *const kAreaType = @"things.area"; NSString *const kTagType = @"things.tag"; NSString *const kTaskType = @"things.task"; + +#pragma mark - Keychain +NSString *const kKeychainService = @"QSThingsPlugin"; +NSString *const kAuthenticationKeyKeychainKey = @"authenticationKey"; |