Skip to content

Commit

Permalink
Add 'remote_notifications' module
Browse files Browse the repository at this point in the history
  • Loading branch information
ColdGrub1384 committed May 3, 2020
1 parent ac09e67 commit 4d1240d
Show file tree
Hide file tree
Showing 26 changed files with 604 additions and 17 deletions.
75 changes: 75 additions & 0 deletions Lib/remote_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Receive remote notifications
This module has everything needed to receive push notifications from a server.
"""

from pyto import __Class__
from time import sleep
from typing import Dict


__RemoteNotifications__ = __Class__("RemoteNotifications")


def remove_category(category: str):
"""
Removes a category with its given identifier.
:param category: The identifier of the category to remove.
"""

__RemoteNotifications__.removeCategory(category)


def add_category(id: str, actions: Dict[str, str]):
"""
Adds a category of notification.
A category contains set of actions for a certain type of notifications.
:param id: The unique identifier of the category.
:param actions: A dictionary with actions displayed by the notification.
The actions are in a dictionary. Each key is the name of an action and its value is a URL that will be opened when the action is pressed.
The ``"{}"`` characters on URLs will be replaced by a percent encoded data sent by the server.
Example:
.. highlight:: python
.. code-block:: python
import remote_notifications as rn
actions = {
"Google": "https://www.google.com/search?q={}"
}
rn.add_category("google", actions)
In the example above, if a notification with the category id `"google"` is received, an action will be added to the notification.
When it's pressed, Pyto will search on Google for the data passed by the server.
"""

__RemoteNotifications__.addCategory(id, actions=actions)


def register() -> str:
"""
Registers the device for push notifications.
Returns a token to use with the api.
:rtype: str
"""

__RemoteNotifications__.register()

while True:

if __RemoteNotifications__.error is not None:
raise RuntimeError(str(__RemoteNotifications__.error))

if __RemoteNotifications__.deviceToken is not None:
return str(__RemoteNotifications__.deviceToken)

sleep(0.2)
2 changes: 2 additions & 0 deletions Pyto Watch Extension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@
</dict>
<key>WKExtensionDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
<key>WKRunsIndependentlyOfCompanionApp</key>
<false/>
</dict>
</plist>
24 changes: 14 additions & 10 deletions Pyto.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
C91321D721FB684900437845 /* BlockBasedSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = C9425A7421F49B1200E7CC35 /* BlockBasedSelector.m */; };
C91321D821FB684C00437845 /* BlockBasedSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9425A7521F49B1200E7CC35 /* BlockBasedSelector.swift */; };
C91321D921FB684E00437845 /* PySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9425A7A21F49B8400E7CC35 /* PySelector.swift */; };
C91376B6245E365E00AA0935 /* RemoteNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91376B5245E365E00AA0935 /* RemoteNotifications.swift */; };
C916F44B226CFD4800FA2FB6 /* modules_inspector.py in Resources */ = {isa = PBXBuildFile; fileRef = C916F44A226CFD4700FA2FB6 /* modules_inspector.py */; };
C91B639D24007B6200FCED1F /* ttconv.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C91B639A24007B4C00FCED1F /* ttconv.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C91B639E24007B6700FCED1F /* _backend_agg.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C9E9DF7723E08172004CF05A /* _backend_agg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -1999,6 +2000,7 @@
C91048CE2414055000DB89A2 /* PyTurtle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PyTurtle.swift; sourceTree = "<group>"; };
C913202A21DC12A300EEE677 /* AcknowledgmentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgmentsViewController.swift; sourceTree = "<group>"; };
C91321C821FB500300437845 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
C91376B5245E365E00AA0935 /* RemoteNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteNotifications.swift; sourceTree = "<group>"; };
C916F44A226CFD4700FA2FB6 /* modules_inspector.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = modules_inspector.py; sourceTree = "<group>"; };
C91B639A24007B4C00FCED1F /* ttconv.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ttconv.framework; sourceTree = "<group>"; };
C91BE6A823F99BF600B6ED6B /* FileBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileBrowserViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4995,6 +4997,7 @@
C9E501AD220C85B1002719CE /* PyMainThread.swift */,
C9624EF321A9905400DDA9A7 /* PyThread.swift */,
C95A79E7214DC9D0007A1B90 /* PyAlert.swift */,
C91376B5245E365E00AA0935 /* RemoteNotifications.swift */,
C9425A7921F49B5A00E7CC35 /* Selectors */,
C9B3FDEB21503DE7000FC9A8 /* PyExtensionContext.swift */,
);
Expand Down Expand Up @@ -7480,6 +7483,7 @@
C924ADE622A9FBF6001C8E94 /* Default.swift in Sources */,
C99E8D6C21F0CC7800C71884 /* WWDC16.swift in Sources */,
C93D61DD2145CD0800C8B5E3 /* PyInputHelper.swift in Sources */,
C91376B6245E365E00AA0935 /* RemoteNotifications.swift in Sources */,
C9C0E78F21F0BC640057C63E /* ReadOnlyTheme.swift in Sources */,
C904EB7321445D5800585C28 /* Python.swift in Sources */,
C927187B22C99CEA0025341B /* PyControl.swift in Sources */,
Expand Down Expand Up @@ -7794,7 +7798,7 @@
"$(PROJECT_DIR)/Pyto\\ Widget",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
OTHER_LDFLAGS = "-ObjC";
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = "ch.marcela.ada.Pyto.Pyto-Widget";
Expand Down Expand Up @@ -7855,7 +7859,7 @@
"$(PROJECT_DIR)/Pyto\\ Widget",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
OTHER_LDFLAGS = "-ObjC";
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = "ch.marcela.ada.Pyto.Pyto-Widget";
Expand Down Expand Up @@ -8008,7 +8012,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
PRODUCT_BUNDLE_IDENTIFIER = ch.marcela.ada.Pyto.watchkitapp.watchkitextension;
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
Expand All @@ -8034,7 +8038,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
PRODUCT_BUNDLE_IDENTIFIER = ch.marcela.ada.Pyto.watchkitapp.watchkitextension;
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
Expand All @@ -8055,7 +8059,7 @@
DEVELOPMENT_TEAM = Y96PL6G497;
IBSC_MODULE = Pyto_Watch_Extension;
INFOPLIST_FILE = "Pyto Watch/Info.plist";
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
PRODUCT_BUNDLE_IDENTIFIER = ch.marcela.ada.Pyto.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
Expand All @@ -8076,7 +8080,7 @@
DEVELOPMENT_TEAM = Y96PL6G497;
IBSC_MODULE = Pyto_Watch_Extension;
INFOPLIST_FILE = "Pyto Watch/Info.plist";
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
PRODUCT_BUNDLE_IDENTIFIER = ch.marcela.ada.Pyto.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
Expand Down Expand Up @@ -8104,7 +8108,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
PRODUCT_BUNDLE_IDENTIFIER = ch.marcela.ada.Pyto.Intents;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -8133,7 +8137,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
PRODUCT_BUNDLE_IDENTIFIER = ch.marcela.ada.Pyto.Intents;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -8424,7 +8428,7 @@
"$(PROJECT_DIR)/OpenCV",
);
LLVM_LTO = YES_THIN;
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = (
Expand Down Expand Up @@ -8542,7 +8546,7 @@
"$(PROJECT_DIR)/OpenCV",
);
LLVM_LTO = YES_THIN;
MARKETING_VERSION = 11.9;
MARKETING_VERSION = 11.9.1;
ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = (
Expand Down
86 changes: 86 additions & 0 deletions Pyto/Python Bridging/RemoteNotifications.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// RemoteNotifications.swift
// Pyto
//
// Created by Adrian Labbé on 02-05-20.
// Copyright © 2020 Adrian Labbé. All rights reserved.
//

import UIKit
import UserNotifications

@objc class RemoteNotifications: NSObject {

@objc static var deviceToken: String?

@objc static var error: String?

@objc static func removeCategory(_ id: String) {
DispatchQueue.main.async {
UNUserNotificationCenter.current().getNotificationCategories { categories in

var new = [UNNotificationCategory]()

for category in categories {
if category.identifier != id {
new.append(category)
}
}

UNUserNotificationCenter.current().setNotificationCategories(Set(new))
}
}
}

@objc static func addCategory(_ id: String, actions: [String:String]) {
UNUserNotificationCenter.current().getNotificationCategories { categories in

var notificationActions = [UNNotificationAction]()

for action in actions {
notificationActions.append(UNNotificationAction(identifier: action.value, title: action.key, options: .foreground))
}

var new = [UNNotificationCategory]()

for category in categories {
if category.identifier != id {
new.append(category)
}
}

let category = UNNotificationCategory(identifier: id, actions: notificationActions, intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: "", options: [])

new.append(category)

UNUserNotificationCenter.current().setNotificationCategories(Set(new))
}
}

@objc static func register() {
DispatchQueue.main.async {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
if settings.authorizationStatus == .denied {
RemoteNotifications.error = "Notifications are disabled."
} else if settings.authorizationStatus == .notDetermined {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (success, error) in
RemoteNotifications.error = error?.localizedDescription
if !success && error == nil {
RemoteNotifications.error = "Notifications are disabled."
}

if RemoteNotifications.error == nil {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
} else {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
}
}
2 changes: 2 additions & 0 deletions Pyto/Pyto.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.ch.marcela.ada.Pyto</string>
Expand Down
2 changes: 1 addition & 1 deletion Pyto/ScriptSettingsViewController.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="0Tn-zV-2ha">
<rect key="frame" x="16" y="213" width="343" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<textInputTraits key="textInputTraits" autocorrectionType="no"/>
<connections>
<outlet property="delegate" destination="uq0-xl-YIb" id="HBV-aU-3Ew"/>
</connections>
Expand Down
18 changes: 17 additions & 1 deletion Pyto/Singletons/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ import WatchConnectivity
return true
}

public func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
RemoteNotifications.deviceToken = nil
RemoteNotifications.error = error.localizedDescription
}

public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
RemoteNotifications.error = nil
RemoteNotifications.deviceToken = deviceToken.map { String(format: "%02hhx", $0) }.joined()
}

#if MAIN

public func applicationWillResignActive(_ application: UIApplication) {
Expand Down Expand Up @@ -263,7 +273,13 @@ import WatchConnectivity

if response.actionIdentifier != UNNotificationDefaultActionIdentifier && response.actionIdentifier != UNNotificationDismissActionIdentifier {

if let url = URL(string: response.actionIdentifier) {
var link = response.actionIdentifier

if let data = (response.notification.request.content.userInfo["data"] as? String)?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
link = link.replacingOccurrences(of: "{}", with: data)
}

if let url = URL(string: link) {
UIApplication.shared.open(url, options: [:]) { (_) in
completionHandler()
}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Exclusive libraries:
- pyto_core - Extend the editor
- notification_center - Customize Today Widgets
- notifications - Schedule notifications
- remote_notifications - Receive remote notifications
- sharing - Share items and pick files
- pasteboard - Clipboard access
- userkeys - Save values on disk
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The following modules are integrated with Pyto.
:caption: OS Technologies

notifications
remote_notifications
music
photos
location
Expand Down
24 changes: 24 additions & 0 deletions docs/remote_notifications.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
remote_notifications
====================

.. automodule:: remote_notifications
:members:


Sending notifications
---------------------

To send a notification, first you need a token for the device that will receive the notification. Use the :func:`~remote_notifications.register` function.

The API endpoint to send remote notifications is ``https://push.pyto.app``

Parameters
**********

To send a notification, construct a POST request with the following parameters.

:token: The token generated by the device that will receive the notification. (Required)
:title: The title of the notification. (Optional)
:message: The body of the notification. (Optional)
:category: The category of the notification. See :func:`~remote_notifications.add_category`. (Optional)
:data: Additional data passed to action URLs. See :func:`~remote_notifications.add_category`. (Optional)
Binary file modified docs_build/doctrees/environment.pickle
Binary file not shown.
Binary file modified docs_build/doctrees/index.doctree
Binary file not shown.
Binary file added docs_build/doctrees/remote_notifications.doctree
Binary file not shown.
1 change: 1 addition & 0 deletions docs_build/html/_sources/index.rst.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The following modules are integrated with Pyto.
:caption: OS Technologies

notifications
remote_notifications
music
photos
location
Expand Down
Loading

0 comments on commit 4d1240d

Please sign in to comment.