├── .gitattributes ├── .github └── workflow │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── img ├── delphi-notify.png └── ntfy.svg ├── samples ├── Examples.groupproj ├── ExamplesAndroid.groupproj ├── android │ ├── AndroidManifest.xml │ ├── NtfyAndroid.dpr │ ├── NtfyAndroid.dproj │ ├── README.md │ ├── assets │ │ ├── android-demo.PNG │ │ ├── android-intent.png │ │ ├── android-notifi.png │ │ └── android-sync.png │ ├── services │ │ └── ntfy-service │ │ │ ├── NtfyServiceLocal.dpr │ │ │ ├── NtfyServiceLocal.dproj │ │ │ └── src │ │ │ ├── Service.Local.Ntfy.dfm │ │ │ └── Service.Local.Ntfy.pas │ └── src │ │ ├── Intent.Service.Helper.pas │ │ ├── View.Main.fmx │ │ └── View.Main.pas ├── console │ ├── publisher │ │ ├── ConsolePublisher.dpr │ │ ├── ConsolePublisher.dproj │ │ ├── ConsolePublisher.res │ │ ├── README.md │ │ └── src │ │ │ ├── Example.Action.HTTP.pas │ │ │ ├── Example.Action.Header.pas │ │ │ ├── Example.Action.View.pas │ │ │ ├── Example.Advanced.Configs.pas │ │ │ ├── Example.Attachments.pas │ │ │ ├── Example.Basic.pas │ │ │ ├── Example.Email.pas │ │ │ ├── Example.Emojis.pas │ │ │ ├── Example.Icons.pas │ │ │ ├── Example.Messages.pas │ │ │ ├── Example.Priorities.pas │ │ │ ├── Example.Schedule.Delivery.pas │ │ │ └── Example.URL.Attachments.pas │ └── subscriber │ │ ├── ConsoleSubscriber.dpr │ │ ├── ConsoleSubscriber.dproj │ │ ├── ConsoleSubscriber.res │ │ └── README.md ├── openssl │ ├── README.md │ ├── lib.zip │ ├── lib32Bits.zip │ └── lib64Bits.zip ├── vcl │ ├── publisher │ │ ├── Ntfy-54x.ico │ │ ├── README.md │ │ ├── VCLPublisher.dpr │ │ ├── VCLPublisher.dproj │ │ ├── VCLPublisher.res │ │ ├── VCLPublisher_Icon.ico │ │ ├── img │ │ │ ├── lego-wars.jpg │ │ │ ├── orion.jpg │ │ │ └── vcl-sample.PNG │ │ └── src │ │ │ ├── Example.Notification.pas │ │ │ ├── View.Main.dfm │ │ │ └── View.Main.pas │ └── subscriber │ │ ├── Ntfy-54x.ico │ │ ├── README.md │ │ ├── VCLSubscriber.Artwork │ │ └── Windows │ │ │ ├── AppIcon.icns │ │ │ ├── AppIcon.ico │ │ │ ├── Uwp_150.png │ │ │ └── Uwp_44.png │ │ ├── VCLSubscriber.dpr │ │ ├── VCLSubscriber.dproj │ │ ├── VCLSubscriber.res │ │ ├── img │ │ └── subscriber-vcl.png │ │ └── src │ │ ├── Example.Push.Notifications.pas │ │ ├── View.Main.dfm │ │ └── View.Main.pas └── win-service │ └── README.md ├── src ├── NX.Horizon.pas ├── Notify.Action.Contract.pas ├── Notify.Action.DTO.pas ├── Notify.Action.pas ├── Notify.Api.Contract.pas ├── Notify.Api.Factory.pas ├── Notify.Api.Indy.pas ├── Notify.Api.NetHTTP.pas ├── Notify.Api.Response.pas ├── Notify.Attachment.Contract.pas ├── Notify.Attachment.DTO.pas ├── Notify.Attachment.pas ├── Notify.Config.Contract.pas ├── Notify.Config.pas ├── Notify.Core.Contract.pas ├── Notify.Core.pas ├── Notify.Custom.Types.pas ├── Notify.Error.pas ├── Notify.Event.Contract.pas ├── Notify.Event.DTO.pas ├── Notify.Event.pas ├── Notify.Facade.pas ├── Notify.JSON.Parser.pas ├── Notify.Logs.pas ├── Notify.Notification.Contract.pas ├── Notify.Notification.DTO.pas ├── Notify.Notification.pas ├── Notify.Response.Data.pas ├── Notify.SmartPointer.pas ├── Notify.Subscription.Event.pas ├── Notify.Types.pas └── Notify.pas └── tests ├── NtfyForDelphiTests.dpr ├── NtfyForDelphiTests.dproj ├── NtfyForDelphiTests.res ├── README.md ├── docker-compose.yml ├── server.yml └── src ├── Index.pas ├── Test.Action.HTTP.pas ├── Test.Action.Header.pas ├── Test.Action.View.pas ├── Test.Attachments.pas ├── Test.Constants.pas ├── Test.Email.pas ├── Test.Emojis.pas ├── Test.Icons.pas ├── Test.Simple.Message.pas ├── Test.Subscription.pas └── Test.URL.Attachments.pas /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflow/tests.yml: -------------------------------------------------------------------------------- 1 | name: Delphi CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - net-http 7 | 8 | jobs: 9 | build: 10 | runs-on: alpine:latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Install Wine 17 | run: sudo apt-get update && sudo apt-get install wine64 18 | 19 | - name: Run Delphi Tests 20 | run: | 21 | wine ../../bin/win32/NtfyForDelphiTests.exe 22 | $status=$? 23 | if [ $status -eq 0 ]; then 24 | echo "Tests passed" 25 | else 26 | echo "Tests failed" 27 | fi 28 | exit $status 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | *.res 48 | *.rc 49 | 50 | 51 | # Delphi autogenerated files (duplicated info) 52 | *.cfg 53 | *.hpp 54 | *Resource.rc 55 | 56 | # Delphi local files (user-specific info) 57 | *.local 58 | *.identcache 59 | *.projdata 60 | *.tvsconfig 61 | *.dsk 62 | 63 | # Delphi history and backups 64 | __history/ 65 | __recovery/ 66 | *.~* 67 | 68 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 69 | *.stat 70 | 71 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 72 | modules/ 73 | 74 | #Android 75 | *Android64/ 76 | *Android32/ 77 | *.res 78 | *.xml 79 | *.deployproj 80 | *res/ 81 | *bin/ 82 | *samples/android/**/*.java 83 | *JavaClasses/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Samuel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | ## Ntfy for Delphi 6 | 7 | This library is a client-side implementation for [ntfy](https://ntfy.sh/) server, made in Delphi. You can send and receive instant notifications via http. The maintainer of [ntfy](https://ntfy.sh/) is [Philipp C. Heckel](https://github.com/binwiederhier). As he stated, this service will remain free, and every kind of support to help affording with cloud hosting will be warmly received. You too can self-host a ntfy server. Visit [docs.ntfy.sh](https://docs.ntfy.sh/) to get started and also don't forget to leave a star [on his project](https://github.com/binwiederhier/ntfy). 8 | 9 | ## Manual Installation 10 | 11 | You need to add ```src``` folder to the library path or search path of your project. 12 | 13 | ## Quickstart 14 | 15 | You can push notifications in topics. Topics are like channels and the name you choose will become a public url, so make sure to not choose an easy one to guess. 16 | 17 | ``` pascal 18 | 19 | uses 20 | Notify; 21 | 22 | begin 23 | Ntfy.Notification( 24 | New.Notification 25 | .Topic('your-very-secret-topic') 26 | .Title('⚾ Go to the game') 27 | .MessageContent('Tomorrow at 10:00hs ') 28 | ); 29 | 30 | Ntfy.Publish; 31 | end; 32 | 33 | ``` 34 | 35 | ## Subscribe to a topic 36 | 37 | You can subscribe by many ways. For instance, the [Web App](https://ntfy.sh/app), [Android](https://docs.ntfy.sh/subscribe/phone/), [CLI](https://docs.ntfy.sh/subscribe/cli/) or you can use this library this way: 38 | 39 | ``` pascal 40 | uses 41 | Notify; 42 | 43 | begin 44 | Ntfy.Subscribe('your-very-secret-topic', 45 | procedure (AEvent: INotifyEvent) 46 | begin 47 | WriteLn('You received a message: ' + AEvent.MessageContent) 48 | end); 49 | end; 50 | 51 | ``` 52 | 53 | ## Supported Versions & Platforms 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | IOS still rely on FCM and "inteligently" decides to kill background/foreground processes when it wants. It was not properly tested yet. All other platforms have been tested and can either publish or maintain a subscription background activity suspended for long hours without having any issues. Refer to these [samples](https://github.com/hazzelnuts/ntfy-for-delphi/tree/main/sample) to learn to use in your project. No tests have been performed on Linux at the present moment. 64 | 65 | ## Built-in dependencies 66 | 67 | Ntfy for Delphi makes use of a few libraries to subscribe and publish. There is no need to install them. Respective credit is awarded to the creators: 68 | 69 | * [NxHorizon](https://github.com/dalijap/nx-horizon) by Dalija Prasnikar. 70 | * [JsonToDelphiClass](https://github.com/PKGeorgiev) by Petar Georgiev. 71 | * [NetHTTP](https://docwiki.embarcadero.com/RADStudio/Rio/en/Using_an_HTTP_Client) native components. 72 | * [Indy10](https://github.com/IndySockets/Indy) by IndySockets. 73 | 74 | 75 | ## Wiki 76 | 77 | Check the [wiki](https://github.com/hazzelnuts/ntfy-for-delphi/wiki) page for specific instructions, updates or tutorials. I've created this implementation for passion and curiosity and it will remain an open source project under the MIT license. Feel free to use, contribute and improve this project! -------------------------------------------------------------------------------- /img/delphi-notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/img/delphi-notify.png -------------------------------------------------------------------------------- /img/ntfy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/Examples.groupproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {F264D633-D2C1-4727-905B-2D467A832EC5} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Default.Personality.12 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /samples/ExamplesAndroid.groupproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {DEF0B80B-12A3-4468-895D-2D1A735A084A} 4 | 5 | 6 | 7 | android\services\ntfy-service\NtfyServiceLocal.dproj 8 | 9 | 10 | 11 | 12 | 13 | 14 | Default.Personality.12 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /samples/android/NtfyAndroid.dpr: -------------------------------------------------------------------------------- 1 | program NtfyAndroid; 2 | 3 | uses 4 | System.StartUpCopy, 5 | FMX.Forms, 6 | View.Main in 'src\View.Main.pas' {Form1}, 7 | Intent.Service.Helper in 'src\Intent.Service.Helper.pas', 8 | Service.Local.Ntfy in 'services\ntfy-service\src\Service.Local.Ntfy.pas' {DM: TAndroidService}, 9 | FMX.Skia {DM: TAndroidService}; 10 | 11 | {$R *.res} 12 | 13 | begin 14 | GlobalUseSkia := True; 15 | 16 | Application.Initialize; 17 | Application.CreateForm(TForm1, Form1); 18 | Application.Run; 19 | end. 20 | -------------------------------------------------------------------------------- /samples/android/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Android 4 | 5 |
6 | 7 | Compile and explore the Android FMX Demo. Also check this [link](https://github.com/p-samuel/delphi-notify/tree/dev-psamuel/sample/console/publisher) for other resources. 8 | 9 |
10 | 11 | 12 | 13 | 14 |
15 | 16 | ## Notes 17 | 18 | This demo brings only the essential methods you may need to implement the publisher and subscriber in your project. It was developed on Delphi 12 and comes with Skia enabled. 19 | -------------------------------------------------------------------------------- /samples/android/assets/android-demo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/android/assets/android-demo.PNG -------------------------------------------------------------------------------- /samples/android/assets/android-intent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/android/assets/android-intent.png -------------------------------------------------------------------------------- /samples/android/assets/android-notifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/android/assets/android-notifi.png -------------------------------------------------------------------------------- /samples/android/assets/android-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/android/assets/android-sync.png -------------------------------------------------------------------------------- /samples/android/services/ntfy-service/NtfyServiceLocal.dpr: -------------------------------------------------------------------------------- 1 | program NtfyServiceLocal; 2 | 3 | uses 4 | System.Android.ServiceApplication, 5 | Service.Local.Ntfy in 'src\Service.Local.Ntfy.pas' {DM: TAndroidService}, 6 | Intent.Service.Helper in '..\..\src\Intent.Service.Helper.pas'; 7 | 8 | {$R *.res} 9 | 10 | begin 11 | Application.Initialize; 12 | Application.CreateForm(TDM, DM); 13 | Application.Run; 14 | end. 15 | -------------------------------------------------------------------------------- /samples/android/services/ntfy-service/src/Service.Local.Ntfy.dfm: -------------------------------------------------------------------------------- 1 | object DM: TDM 2 | OnCreate = AndroidServiceCreate 3 | OnDestroy = AndroidServiceDestroy 4 | OnStartCommand = AndroidServiceStartCommand 5 | Height = 238 6 | Width = 324 7 | object NotificationCenter: TNotificationCenter 8 | Left = 128 9 | Top = 94 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /samples/android/services/ntfy-service/src/Service.Local.Ntfy.pas: -------------------------------------------------------------------------------- 1 | unit Service.Local.Ntfy; 2 | 3 | interface 4 | 5 | uses 6 | System.Android.Service, 7 | System.Classes, 8 | System.SysUtils, 9 | System.Generics.Collections, 10 | System.Notification, 11 | System.SyncObjs, 12 | System.DateUtils, 13 | Androidapi.JNI.GraphicsContentViewText, 14 | Androidapi.JNI.App, 15 | Androidapi.JNI.Os, 16 | Androidapi.JNI.JavaTypes, 17 | Androidapi.JNI.Support, 18 | Androidapi.JNIBridge, 19 | Androidapi.Helpers, 20 | Notify; 21 | 22 | type 23 | 24 | TDM = class(TAndroidService) 25 | NotificationCenter: TNotificationCenter; 26 | function AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer; 27 | procedure AndroidServiceCreate(Sender: TObject); 28 | procedure AndroidServiceDestroy(Sender: TObject); 29 | private 30 | FNotificationManager: JNotificationManager; 31 | FNotificationBuilder: Japp_NotificationCompat_Builder; 32 | FChannelId: String; 33 | FThreads: TObjectList; 34 | FWakeLock: JPowerManager_WakeLock; 35 | procedure ShowUpNotification(const Text: String); 36 | procedure ShowUpNtfy(AEvent: INotifyEvent); 37 | end; 38 | 39 | var 40 | DM: TDM; 41 | 42 | implementation 43 | 44 | uses 45 | Intent.Service.Helper; 46 | 47 | const 48 | FOREGROUND_SERVICE_TYPE_DATA_SYNC = $00000001; 49 | 50 | {%CLASSGROUP 'FMX.Controls.TControl'} 51 | 52 | {$R *.dfm} 53 | 54 | { TDM } 55 | 56 | procedure TDM.AndroidServiceCreate(Sender: TObject); 57 | var 58 | Context: JContext; 59 | LChannel: JNotificationChannel; 60 | LPowerManager: JPowerManager; 61 | begin 62 | try 63 | 64 | //Create threads object 65 | FThreads := TObjectList.Create; 66 | 67 | // Initialize Notification Manager and Builder 68 | FChannelId := 'ntfy-for-delphi'; 69 | FNotificationManager := TJNotificationManager.Wrap((TAndroidHelper.Context.getSystemService(TJContext.JavaClass.NOTIFICATION_SERVICE))); 70 | FNotificationBuilder := TJapp_NotificationCompat_Builder.JavaClass.init(TAndroidHelper.Context); 71 | FNotificationBuilder.setSmallIcon(TAndroidHelper.Context.getApplicationInfo.icon); 72 | FNotificationBuilder.setContentTitle(StrToJCharSequence('Ntfy service started')); 73 | FNotificationBuilder.setContentText(StrToJCharSequence('Syncing...')); 74 | FNotificationBuilder.setAutoCancel(True); 75 | FNotificationBuilder.setChannelId(StringToJString(FChannelId)); 76 | 77 | //Creates notification channel if not exists 78 | if TJBuild_VERSION.JavaClass.SDK_INT >= 26 then 79 | begin 80 | if FNotificationManager.getNotificationChannel(StringToJString(FChannelId)) = nil then 81 | begin 82 | LChannel := TJNotificationChannel.JavaClass.init( 83 | StringToJString(FChannelId), 84 | StrToJCharSequence('Ntfy Subscription'), 85 | TJNotificationManager.JavaClass.IMPORTANCE_HIGH); 86 | LChannel.setLightColor(TJColor.JavaClass.BLUE); 87 | LChannel.setLockscreenVisibility(TJNotification.JavaClass.VISIBILITY_PRIVATE); 88 | FNotificationManager.createNotificationChannel(LChannel); 89 | end; 90 | end; 91 | 92 | // Creates wake lock object 93 | LPowerManager := TJPowerManager.Wrap(TAndroidHelper.Context.getSystemService(TJContext.JavaClass.POWER_SERVICE)); 94 | FWakeLock := LPowerManager.newWakeLock(TJPowerManager.JavaClass.PARTIAL_WAKE_LOCK, StringToJString('MyApp::NtfyWakeLock')); 95 | 96 | except 97 | on E: Exception do 98 | ShowUpNotification('Create: ' + E.Message); 99 | end; 100 | 101 | end; 102 | 103 | procedure TDM.AndroidServiceDestroy(Sender: TObject); 104 | begin 105 | FThreads.Free; 106 | if FWakeLock <> nil then 107 | begin 108 | if FWakeLock.isHeld then 109 | FWakeLock.release(); 110 | FWakeLock := nil; 111 | end; 112 | end; 113 | 114 | function TDM.AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer; 115 | var 116 | Created: String; 117 | LThread: TThread; 118 | LIntentService: TIntentServiceHelper; 119 | begin 120 | try 121 | //1 - Will result to restart the service with the same intent 122 | Result := TJService.JavaClass.START_REDELIVER_INTENT; 123 | LIntentService := TIntentServiceHelper.Create(Intent); 124 | 125 | if LIntentService.Data.IsEmpty then 126 | Exit; 127 | 128 | //2 - Create a thread to be inserted in the thread object list to let the service running without stopping 129 | LThread := TThread.CreateAnonymousThread( 130 | procedure 131 | var 132 | LEvent: TEvent; 133 | LJNotification: JNotification; 134 | begin 135 | try 136 | while True do 137 | begin 138 | //3 - Builds a notification and an event object 139 | LJNotification := FNotificationBuilder.build(); 140 | LEvent := TEvent.Create; 141 | 142 | //4 - Subscribe to Ntfy 143 | Ntfy.Subscribe(LIntentService.Data, ShowUpNtfy); 144 | 145 | //5 - Starts as foreground and notifies 146 | TJService.Wrap(JavaService).startForeground(1, LJNotification, FOREGROUND_SERVICE_TYPE_DATA_SYNC); 147 | FNotificationManager.notify(1, LJNotification); 148 | 149 | //6 - Waits until infinite, so that the service can be kept running "forever" 150 | LEvent.WaitFor(INFINITE); 151 | JavaService.stopSelf(StartId); 152 | end; 153 | except 154 | on E: Exception do 155 | ShowUpNotification('Thread error: ' + sLineBreak + E.Message); 156 | end; 157 | end); 158 | 159 | // 7 - Insert and start the thread 160 | LThread.FreeOnTerminate := False; 161 | FThreads.Add(LThread); 162 | LThread.Start; 163 | 164 | except 165 | on E: Exception do 166 | ShowUpNotification('Start error: ' + sLineBreak + E.Message); 167 | end; 168 | end; 169 | 170 | procedure TDM.ShowUpNotification(const Text: String); 171 | var 172 | LNotification: TNotification; 173 | begin 174 | LNotification := NotificationCenter.CreateNotification(); 175 | try 176 | LNotification.ChannelId := FChannelId; 177 | LNotification.Name := IntToStr(Random(1000)); 178 | LNotification.Title := 'Ntfy Service'; 179 | LNotification.AlertBody := Text; 180 | NotificationCenter.PresentNotification(LNotification); 181 | finally 182 | LNotification.Free; 183 | end; 184 | end; 185 | 186 | procedure TDM.ShowUpNtfy(AEvent: INotifyEvent); 187 | var 188 | LNotification: TNotification; 189 | begin 190 | if FWakeLock <> nil then 191 | FWakeLock.acquire; 192 | 193 | try 194 | LNotification := NotificationCenter.CreateNotification(); 195 | try 196 | LNotification.ChannelId := FChannelId; 197 | LNotification.Name := IntToStr(Random(1000)); 198 | LNotification.Title := AEvent.Title; 199 | LNotification.AlertBody := AEvent.MessageContent; 200 | NotificationCenter.PresentNotification(LNotification); 201 | finally 202 | LNotification.Free; 203 | end; 204 | finally 205 | if FWakeLock <> nil then 206 | if FWakeLock.isHeld then 207 | FWakeLock.release(); 208 | end; 209 | end; 210 | 211 | end. 212 | 213 | -------------------------------------------------------------------------------- /samples/android/src/Intent.Service.Helper.pas: -------------------------------------------------------------------------------- 1 | unit Intent.Service.Helper; 2 | 3 | interface 4 | 5 | uses 6 | AndroidApi.JNI.GraphicsContentViewText; 7 | 8 | type 9 | TIntentServiceHelper = record 10 | private 11 | FIntent: JIntent; 12 | FCode: Integer; 13 | FData: string; 14 | public 15 | class function Create(const Intent: JIntent): TIntentServiceHelper; overload; static; 16 | class function Create(const AServiceName: string; Code: Integer; Data: string): TIntentServiceHelper; overload; static; 17 | property Code: Integer read FCode; 18 | property Data: string read FData; 19 | property Intent: JIntent read FIntent; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | System.SysUtils, 26 | Androidapi.Helpers, 27 | Androidapi.Jni, 28 | Androidapi.JNIBridge, 29 | Androidapi.JNI.JavaTypes; 30 | 31 | { TIntentServiceHelper } 32 | 33 | class function TIntentServiceHelper.Create(const Intent: JIntent): TIntentServiceHelper; 34 | begin 35 | Result.FCode := Intent.getIntExtra(TAndroidHelper.StringToJString('Code'), -1); 36 | Result.FData := TAndroidHelper.JStringToString(Intent.getStringExtra(TAndroidHelper.StringToJString('Data'))); 37 | end; 38 | 39 | class function TIntentServiceHelper.Create(const AServiceName: string; Code: Integer; Data: string): TIntentServiceHelper; 40 | var 41 | LService: string; 42 | begin 43 | Result.FIntent := TJIntent.Create; 44 | LService := AServiceName; 45 | if not LService.StartsWith('com.embarcadero.services.') then 46 | LService := 'com.embarcadero.services.' + LService; 47 | Result.FIntent.setClassName(TAndroidHelper.Context.getPackageName(), TAndroidHelper.StringToJString(LService)); 48 | Result.FIntent.putExtra(TAndroidHelper.StringToJString('Code'), Code); 49 | Result.FIntent.putExtra(TAndroidHelper.StringToJString('Data'), TAndroidHelper.StringToJString(Data)); 50 | end; 51 | 52 | end. 53 | -------------------------------------------------------------------------------- /samples/android/src/View.Main.fmx: -------------------------------------------------------------------------------- 1 | object Form1: TForm1 2 | Left = 0 3 | Top = 0 4 | Caption = 'V' 5 | ClientHeight = 641 6 | ClientWidth = 385 7 | Fill.Color = claNull 8 | Fill.Kind = Solid 9 | FormFactor.Width = 320 10 | FormFactor.Height = 480 11 | FormFactor.Devices = [Desktop] 12 | DesignerMasterStyle = 3 13 | object CornerButton1: TCornerButton 14 | StyledSettings = [Style] 15 | Position.X = 56.000000000000000000 16 | Position.Y = 380.000000000000000000 17 | Sides = [Top, Left, Bottom, Right] 18 | Size.Width = 273.000000000000000000 19 | Size.Height = 35.000000000000000000 20 | Size.PlatformDefault = False 21 | TabOrder = 1 22 | Text = 'Start Service (Subscribe)' 23 | TextSettings.Font.Family = 'Consolas' 24 | TextSettings.Font.Size = 11.000000000000000000 25 | TextSettings.FontColor = claWhitesmoke 26 | TextSettings.Trimming = None 27 | XRadius = 3.000000000000000000 28 | YRadius = 3.000000000000000000 29 | OnClick = CornerButton1Click 30 | end 31 | object CornerButton2: TCornerButton 32 | StyledSettings = [Style] 33 | Position.X = 56.000000000000000000 34 | Position.Y = 337.000000000000000000 35 | Sides = [Top, Left, Bottom, Right] 36 | Size.Width = 273.000000000000000000 37 | Size.Height = 35.000000000000000000 38 | Size.PlatformDefault = False 39 | TabOrder = 0 40 | Text = 'Send Notification (Ntfy)' 41 | TextSettings.Font.Family = 'Consolas' 42 | TextSettings.Font.Size = 11.000000000000000000 43 | TextSettings.FontColor = claWhitesmoke 44 | TextSettings.Trimming = None 45 | XRadius = 3.000000000000000000 46 | YRadius = 3.000000000000000000 47 | OnClick = CornerButton2Click 48 | end 49 | object Text1: TText 50 | Position.X = 96.000000000000000000 51 | Position.Y = 217.000000000000000000 52 | Size.Width = 193.000000000000000000 53 | Size.Height = 48.000000000000000000 54 | Size.PlatformDefault = False 55 | Text = 'Inform topic to subscribe, start service and close the app.' 56 | TextSettings.FontColor = claWhitesmoke 57 | end 58 | object Edit1: TEdit 59 | Touch.InteractiveGestures = [LongTap, DoubleTap] 60 | StyleLookup = 'editstyle' 61 | TabOrder = 3 62 | Text = 'your-very-secret-topic' 63 | TextSettings.Font.Family = 'Consolas' 64 | TextSettings.FontColor = claWhitesmoke 65 | TextSettings.HorzAlign = Center 66 | Position.X = 56.000000000000000000 67 | Position.Y = 281.000000000000000000 68 | Size.Width = 273.000000000000000000 69 | Size.Height = 32.000000000000000000 70 | Size.PlatformDefault = False 71 | StyledSettings = [Style] 72 | end 73 | object SkSvg1: TSkSvg 74 | Position.X = 168.000000000000000000 75 | Position.Y = 144.000000000000000000 76 | Svg.Source = 77 | '' 123 | object Spin: TFloatAnimation 124 | AnimationType = InOut 125 | Delay = 0.200000002980232200 126 | Duration = 0.800000011920929000 127 | Interpolation = Exponential 128 | PropertyName = 'RotationAngle' 129 | StartValue = 0.000000000000000000 130 | StopValue = 360.000000000000000000 131 | end 132 | end 133 | object Text2: TText 134 | Align = Bottom 135 | Position.Y = 593.000000000000000000 136 | Size.Width = 385.000000000000000000 137 | Size.Height = 48.000000000000000000 138 | Size.PlatformDefault = False 139 | Text = 'Demo' 140 | TextSettings.Font.Size = 9.000000000000000000 141 | TextSettings.FontColor = claWhitesmoke 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /samples/android/src/View.Main.pas: -------------------------------------------------------------------------------- 1 | unit View.Main; 2 | 3 | interface 4 | 5 | uses 6 | System.Skia, FMX.Skia, System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 7 | FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, 8 | FMX.Controls.Presentation, FMX.StdCtrls, FMX.Objects, FMX.Layouts, 9 | FMX.ExtCtrls, FMX.Edit, FMX.Ani; 10 | 11 | type 12 | 13 | TForm1 = class(TForm) 14 | CornerButton1: TCornerButton; 15 | CornerButton2: TCornerButton; 16 | Text1: TText; 17 | Edit1: TEdit; 18 | SkSvg1: TSkSvg; 19 | Spin: TFloatAnimation; 20 | Text2: TText; 21 | procedure StartService; 22 | procedure CornerButton1Click(Sender: TObject); 23 | procedure CornerButton2Click(Sender: TObject); 24 | private 25 | procedure Toast(const Text: String); 26 | end; 27 | 28 | var 29 | Form1: TForm1; 30 | 31 | implementation 32 | 33 | uses 34 | System.Android.Service, 35 | Intent.Service.Helper, 36 | AndroidAPI.Helpers, 37 | AndroidAPI.JNI.Widget, 38 | System.Threading, 39 | Notify, Androidapi.JNI.JavaTypes; 40 | 41 | {$R *.fmx} 42 | 43 | procedure TForm1.CornerButton1Click(Sender: TObject); 44 | begin 45 | StartService; 46 | end; 47 | 48 | procedure TForm1.CornerButton2Click(Sender: TObject); 49 | begin 50 | TTask.Create(procedure 51 | begin 52 | 53 | Ntfy.Notification( 54 | New.Notification 55 | .Topic(Edit1.Text) 56 | .Title('🧪 Android Test') 57 | .MessageContent('Should receive in this app') 58 | ).Publish; 59 | 60 | TThread.Synchronize(nil, procedure 61 | begin 62 | Spin.Start; 63 | Toast('Notification published successfully.'); 64 | end); 65 | end).Start; 66 | end; 67 | 68 | procedure TForm1.StartService; 69 | var 70 | LIntentService: TIntentServiceHelper; 71 | begin 72 | try {serice name} { topic } 73 | LIntentService := TIntentServiceHelper.Create('NtfyServiceLocal', 0, Edit1.Text); 74 | TAndroidHelper.Activity.startForegroundService(LIntentService.Intent); 75 | TThread.Synchronize(nil, procedure 76 | begin 77 | Spin.Start; 78 | Toast('Intent sent successfully, you may close the app now.'); 79 | end); 80 | except 81 | on E: Exception do 82 | ShowMessage(E.Message); 83 | end; 84 | end; 85 | 86 | procedure TForm1.Toast(const Text: String); 87 | var 88 | JText: JString; 89 | begin 90 | JText := StringToJString(Text); 91 | TJToast.JavaClass.makeText( 92 | TAndroidHelper.Context, 93 | TJCharSequence.Wrap(JText), 94 | TJToast.JavaClass.LENGTH_SHORT 95 | ).show; 96 | end; 97 | 98 | end. 99 | -------------------------------------------------------------------------------- /samples/console/publisher/ConsolePublisher.dpr: -------------------------------------------------------------------------------- 1 | program ConsolePublisher; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | uses 6 | Example.Basic in 'src\Example.Basic.pas', 7 | Example.Action.Header in 'src\Example.Action.Header.pas', 8 | Example.Priorities in 'src\Example.Priorities.pas', 9 | Example.Messages in 'src\Example.Messages.pas', 10 | Example.Emojis in 'src\Example.Emojis.pas', 11 | Example.Schedule.Delivery in 'src\Example.Schedule.Delivery.pas', 12 | Example.Action.View in 'src\Example.Action.View.pas', 13 | Example.Action.HTTP in 'src\Example.Action.HTTP.pas', 14 | Example.Attachments in 'src\Example.Attachments.pas', 15 | Example.URL.Attachments in 'src\Example.URL.Attachments.pas', 16 | Example.Icons in 'src\Example.Icons.pas', 17 | Example.Email in 'src\Example.Email.pas', 18 | Example.Advanced.Configs in 'src\Example.Advanced.Configs.pas', 19 | Notify; 20 | 21 | begin 22 | 23 | UseSimpleMessage; 24 | //UsePriorities; 25 | //UseMessage; 26 | //UseTags; 27 | //UseActionView; 28 | //UseActionHTTP; 29 | //UseAttachments; 30 | //UseURLAttachments; 31 | //UseIcons; 32 | //UseEmail; 33 | 34 | Ntfy.Topic('notify-delphi-integration-8jh27d').Publish; 35 | 36 | end. 37 | -------------------------------------------------------------------------------- /samples/console/publisher/ConsolePublisher.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/console/publisher/ConsolePublisher.res -------------------------------------------------------------------------------- /samples/console/publisher/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | # Console Publisher Sample 8 | 9 |
10 | 11 | 12 | Simple message 13 | 14 | ``` pascal 15 | Ntfy.Notification( 16 | New.Notification 17 | .Title('Simple message sent') 18 | .MessageContent('A message body...') 19 | ); 20 | ``` 21 | 22 | Setup Priority Levels 23 | 24 | ``` pascal 25 | Ntfy.Notification( 26 | New.Notification 27 | .Priority(TNotifyPriority.HIGH) 28 | ); 29 | ``` 30 | 31 | Emojis/Tags 32 | 33 | ``` pascal 34 | Ntfy.Notification( 35 | New.Notification 36 | .Tags(TArray.Create('partying_face', 'warning', 'rotating_light')) 37 | ); 38 | ``` 39 | 40 | Actions Buttons (view) 41 | 42 | ``` pascal 43 | Ntfy.Notification(New.Notification 44 | .Action(New.Action 45 | .&Type(TNotifyActionType.VIEW) 46 | .Url('geo:40.765819,-73.975866') 47 | .&Label('Open Google Maps')) 48 | ); 49 | ``` 50 | 51 | Actions Buttons (http) 52 | 53 | ``` pascal 54 | Ntfy.Notification(New.Notification 55 | .Action(New.Action 56 | .&Type(TNotifyActionType.HTTP) 57 | .Url('https://viacep.com.br/ws/01001000/json/') 58 | .&Label('Send GET request to viacep') 59 | .Method('GET')) 60 | ); 61 | ``` 62 | 63 | File attachments 64 | 65 | ``` pascal 66 | Ntfy.Notification( 67 | New.Notification 68 | .FilePath('..\img\delphi-notify.png') 69 | ); 70 | ``` 71 | 72 | URL attachments 73 | 74 | ``` pascal 75 | Ntfy.Notification( 76 | New.Notification 77 | .Attach('https://i.picsum.photos/id/1002/200/200.jpg') 78 | ); 79 | ``` 80 | 81 | URL icons attachments 82 | 83 | ``` pascal 84 | Ntfy.Notification( 85 | New.Notification 86 | .Icon('https://communityIcon_xnt6chtnr2j21.png') 87 | ); 88 | ``` 89 | 90 | Email notifications 91 | 92 | ``` pascal 93 | Ntfy.Notification( 94 | New.Notification 95 | .Title('Email test') 96 | .MessageContent('Emails with a title and a message') 97 | .Email('someemail@gmail.com') 98 | ); 99 | ``` 100 | 101 | Basic Logs 102 | 103 | ``` pascal 104 | Ntfy.SaveLog(True).LogPath('..\logs_folder'); 105 | ``` 106 | 107 | Other ntfy API configs 108 | 109 | ``` pascal 110 | // Username and password for protected topics 111 | // Warning! Username and password are not 112 | // encrypted, only encoded! Use HTTPS. You can 113 | // use this option when you are self-hosting 114 | Ntfy.UserName('username').Password('password'); 115 | 116 | // Disabling Firebase will significantly increase 117 | // the amount of time messages are delivered 118 | // in Android 119 | Ntfy.DisableFireBase(True); 120 | 121 | // Disabling cache will cause messages no longer 122 | // to be delivered to unsubscribed clients 123 | Ntfy.Cache(False); 124 | 125 | // You can delay messages up to three days 126 | Ntfy.Delay('4h'); 127 | 128 | ``` 129 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Action.HTTP.pas: -------------------------------------------------------------------------------- 1 | unit Example.Action.HTTP; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using action view 7 | /// 8 | 9 | procedure UseActionHTTP; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseActionHTTP; 17 | begin 18 | 19 | Ntfy.Notification(New.Notification 20 | .Topic('your-very-secret-topic') 21 | .Action(New.Action 22 | .&Type(TNotifyActionType.HTTP) 23 | .Url('https://viacep.com.br/ws/01001000/json/') 24 | .&Label('Send GET request to viacep') 25 | .Method('GET')) 26 | ); 27 | 28 | end; 29 | 30 | end. 31 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Action.Header.pas: -------------------------------------------------------------------------------- 1 | unit Example.Action.Header; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using ation headers 7 | /// 8 | 9 | procedure UseActionHeader; 10 | 11 | implementation 12 | 13 | uses 14 | Notify, 15 | Notify.Custom.Types, 16 | REST.Json.Types; 17 | 18 | procedure UseActionHeader; 19 | var 20 | Headers: TNotifyActionHeaders; 21 | begin 22 | Headers := TNotifyActionHeaders.Create; 23 | try 24 | Headers.Auth := 'Bearer 982j38sdfhj2181jcznxc81234b9as-i34'; 25 | Headers.Cmd := 'OPEN XOAUTH .\crs\main\lib'; 26 | 27 | Ntfy.Notification(New.Notification 28 | .Action(New.Action 29 | .&Type(TNotifyActionType.HTTP) 30 | .&Label('Action Headers') 31 | .Url('http://someurl.com') 32 | .Body('"message":"hello"') 33 | .Headers(Headers)) 34 | ); 35 | 36 | finally 37 | Headers.Free; 38 | end; 39 | end; 40 | 41 | end. 42 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Action.View.pas: -------------------------------------------------------------------------------- 1 | unit Example.Action.View; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using action view 7 | /// 8 | 9 | procedure UseActionView; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseActionView; 17 | begin 18 | 19 | // Opens Google Maps in a certain location 20 | Ntfy.Notification(New.Notification 21 | .Topic('your-very-secret-topic') 22 | .Action(New.Action 23 | .&Type(TNotifyActionType.VIEW) 24 | .Url('geo:40.765819,-73.975866') 25 | .&Label('Open Google Maps')) 26 | ); 27 | 28 | // Opens gmail 29 | Ntfy.Notification(New.Notification 30 | .Topic('your-very-secret-topic') 31 | .Action(New.Action 32 | .&Type(TNotifyActionType.VIEW) 33 | .Url('mailto:phil@example.com') 34 | .&Label('Send Mail')) 35 | ); 36 | 37 | end; 38 | 39 | end. 40 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Advanced.Configs.pas: -------------------------------------------------------------------------------- 1 | unit Example.Advanced.Configs; 2 | 3 | interface 4 | 5 | /// 6 | /// Example for self-hosted apis 7 | /// 8 | 9 | procedure UseAdvancedConfigs; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseAdvancedConfigs; 17 | begin 18 | 19 | // Username and password for protected topics 20 | // Be careful! Username and password are not encrypted, only encoded! Use HTTPS 21 | Ntfy.UserName('username').Password('password'); 22 | 23 | // Disabling Firebase will significantly increase the amount of time messages 24 | // are delivered in Android 25 | Ntfy.DisableFireBase(True); 26 | 27 | //Disabling cache will cause messages not to be delivered to whom is not subscribed 28 | Ntfy.Cache(False); 29 | 30 | end; 31 | 32 | end. 33 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Attachments.pas: -------------------------------------------------------------------------------- 1 | unit Example.Attachments; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using file attachments 7 | /// 8 | 9 | procedure UseAttachments; 10 | 11 | implementation 12 | 13 | uses 14 | System.IOUtils, 15 | System.SysUtils, 16 | Notify; 17 | 18 | procedure UseAttachments; 19 | var 20 | LFilePath: String; 21 | begin 22 | 23 | LFilePath := '..\..\..\..\img\delphi-notify.png'; 24 | 25 | Ntfy.Notification( 26 | New.Notification 27 | .Topic('your-very-secret-topic') 28 | .AttachFile(LFilePath) 29 | ); 30 | 31 | end; 32 | 33 | end. 34 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Basic.pas: -------------------------------------------------------------------------------- 1 | unit Example.Basic; 2 | 3 | interface 4 | 5 | /// 6 | /// Publishing a simple message 7 | /// 8 | 9 | procedure UseSimpleMessage; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseSimpleMessage; 17 | begin 18 | 19 | Ntfy.Notification( 20 | New.Notification 21 | .Topic('your-very-secret-topic') 22 | .Title('Simple message sent') 23 | ); 24 | 25 | end; 26 | 27 | end. 28 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Email.pas: -------------------------------------------------------------------------------- 1 | unit Example.Email; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using email 7 | /// 8 | 9 | procedure UseEmail; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseEmail; 17 | begin 18 | 19 | Ntfy.Notification( 20 | New.Notification 21 | .Topic('your-very-secret-topic') 22 | .Title('Email test') 23 | .MessageContent('Emails with a title and a message') 24 | .Email('someemail@gmail.com') 25 | ); 26 | 27 | end; 28 | 29 | end. 30 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Emojis.pas: -------------------------------------------------------------------------------- 1 | unit Example.Emojis; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using emojis/tags 7 | /// 8 | 9 | procedure UseTags; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseTags; 17 | begin 18 | 19 | Ntfy.Notification( 20 | New.Notification 21 | .Topic('your-very-secret-topic') 22 | .Tags(TArray.Create('partying_face', 'warning', 'rotating_light')) 23 | ); 24 | 25 | end; 26 | 27 | end. 28 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Icons.pas: -------------------------------------------------------------------------------- 1 | unit Example.Icons; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using icons url 7 | /// 8 | 9 | procedure UseIcons; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseIcons; 17 | begin 18 | 19 | Ntfy.Notification( 20 | New.Notification 21 | .Topic('your-very-secret-topic') 22 | .Icon('https://styles.redditmedia.com/t5_32uhe/styles/communityIcon_xnt6chtnr2j21.png') 23 | ); 24 | 25 | end; 26 | 27 | 28 | end. 29 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Messages.pas: -------------------------------------------------------------------------------- 1 | unit Example.Messages; 2 | 3 | interface 4 | 5 | /// 6 | /// Attaching a message body 7 | /// 8 | 9 | procedure UseMessage; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseMessage; 17 | begin 18 | 19 | Ntfy.Notification( 20 | New.Notification 21 | .Topic('your-very-secret-topic') 22 | .MessageContent('A message body...') 23 | ); 24 | 25 | end; 26 | 27 | end. 28 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Priorities.pas: -------------------------------------------------------------------------------- 1 | unit Example.Priorities; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using priorities 7 | /// 8 | 9 | procedure UsePriorities; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | 17 | procedure UsePriorities; 18 | begin 19 | 20 | Ntfy.Notification( 21 | New.Notification 22 | .Topic('your-very-secret-topic') 23 | .Priority(TNotifyPriority.HIGH) 24 | ); 25 | 26 | end; 27 | 28 | end. 29 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.Schedule.Delivery.pas: -------------------------------------------------------------------------------- 1 | unit Example.Schedule.Delivery; 2 | 3 | interface 4 | 5 | /// 6 | /// Example scheduling delivery 7 | /// 8 | 9 | procedure UseScheduleDelivery; 10 | 11 | implementation 12 | 13 | uses 14 | Notify; 15 | 16 | procedure UseScheduleDelivery; 17 | begin 18 | 19 | Ntfy.Notification( 20 | New.Notification 21 | .Topic('your-very-secret-topic') 22 | .Delay('10s') 23 | ); 24 | 25 | end; 26 | 27 | end. 28 | -------------------------------------------------------------------------------- /samples/console/publisher/src/Example.URL.Attachments.pas: -------------------------------------------------------------------------------- 1 | unit Example.URL.Attachments; 2 | 3 | interface 4 | 5 | /// 6 | /// Example using url attachments 7 | /// 8 | 9 | procedure UseURLAttachments; 10 | 11 | implementation 12 | 13 | uses 14 | Notify, System.SysUtils; 15 | 16 | const 17 | AttachURL = 18 | 'https://images.unsplash.com/photo-1670531910262-'+ 19 | '5ddb5ad666ea?crop=entropy&cs=tinysrgb&fit=crop&f'+ 20 | 'm=jpg&h=150&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx'+ 21 | '8MTY3MTE0OTI2NA&ixlib=rb-4.0.3&q=80&w=500'; 22 | 23 | procedure UseURLAttachments; 24 | begin 25 | 26 | Ntfy.Notification( 27 | New.Notification 28 | .Topic('your-very-secret-topic') 29 | .AttachURL(Trim(AttachURL)) 30 | ); 31 | 32 | end; 33 | 34 | end. 35 | -------------------------------------------------------------------------------- /samples/console/subscriber/ConsoleSubscriber.dpr: -------------------------------------------------------------------------------- 1 | program ConsoleSubscriber; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | Notify; 9 | 10 | begin 11 | 12 | ReportMemoryLeaksOnShutdown := True; 13 | 14 | Ntfy.Subscribe('notify-delphi-integration-8jh27d', 15 | procedure (AEvent: INotifyEvent) 16 | begin 17 | Writeln('You received a message:'); 18 | Writeln('Title: ' + AEvent.Title); 19 | Writeln('Message: ' + AEvent.MessageContent); 20 | end); 21 | 22 | end. 23 | -------------------------------------------------------------------------------- /samples/console/subscriber/ConsoleSubscriber.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/console/subscriber/ConsoleSubscriber.res -------------------------------------------------------------------------------- /samples/console/subscriber/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | # Subscriber Console Sample 8 | 9 |
10 | 11 | ## Subscribe to a topic 12 | 13 | Subscription is event based. 14 | 15 | ``` pascal 16 | Ntfy.Subscribe('your-very-secret-topic', 17 | procedure (AEvent: INotifyEvent); 18 | begin 19 | WriteLn('You received a message: ' + AEvent.MessageContent) 20 | end); 21 | 22 | ``` 23 | 24 | Short poll 25 | 26 | ``` pascal 27 | Ntfy.Poll(True); 28 | Ntfy.Subscribe('your-very-secret-topic', CallBack); 29 | ``` 30 | 31 | Fetching (duration, unix time or id) 32 | 33 | ``` pascal 34 | Ntfy.Since('2hs') 35 | Ntfy.Subscribe('your-very-secret-topic', CallBack); 36 | ``` 37 | 38 | 39 | Scheduled only 40 | 41 | ``` pascal 42 | Ntfy.Scheduled(True) 43 | Ntfy.Subscribe('your-very-secret-topic', CallBack); 44 | ``` 45 | 46 | ## Filtering 47 | 48 | ``` pascal 49 | Ntfy.Filter(TNotifyFilter.ID, 'xY77dh389'); 50 | Ntfy.Filter(TNotifyFilter.TITLE, 'some title'); 51 | Ntfy.Filter(TNotifyFilter.MESSAGECONTENT, 'some text'); 52 | Ntfy.Filter(TNotifyFilter.TAGS, 'some text'); 53 | Ntfy.Filter(TNotifyFilter.PRIORITY, IntToStr(Ord(TNotifyPriority.MAX))); 54 | ``` -------------------------------------------------------------------------------- /samples/openssl/README.md: -------------------------------------------------------------------------------- 1 | # SSL Libs 2 | If you are using Indy, paste these libs in the same directory where your app is running. Don't forget to add ```NTFY_HTTP_INDY``` in the conditional defines. -------------------------------------------------------------------------------- /samples/openssl/lib.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/openssl/lib.zip -------------------------------------------------------------------------------- /samples/openssl/lib32Bits.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/openssl/lib32Bits.zip -------------------------------------------------------------------------------- /samples/openssl/lib64Bits.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/openssl/lib64Bits.zip -------------------------------------------------------------------------------- /samples/vcl/publisher/Ntfy-54x.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/publisher/Ntfy-54x.ico -------------------------------------------------------------------------------- /samples/vcl/publisher/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Publisher VCL Sample 4 | 5 |
6 | 7 | Compile and explore the VCL sample. Also check this [link](https://github.com/p-samuel/delphi-notify/tree/dev-psamuel/sample/console/publisher) for other resources. 8 | 9 |
10 | 11 |
12 | 13 | ## Self-hosted server (optional for this sample) 14 | Install Docker on your machine and run these steps: 15 | 16 | ``` cmd 17 | cd delphi-notify\tests 18 | docker compose create 19 | docker cp server.yml ntfy:/etc/ntfy 20 | docker compose up 21 | ``` -------------------------------------------------------------------------------- /samples/vcl/publisher/VCLPublisher.dpr: -------------------------------------------------------------------------------- 1 | program VCLPublisher; 2 | 3 | uses 4 | Vcl.Forms, 5 | View.Main in 'src\View.Main.pas' {ViewMain}, 6 | Vcl.Themes, 7 | Vcl.Styles; 8 | 9 | {$R *.res} 10 | 11 | begin 12 | Application.Initialize; 13 | Application.MainFormOnTaskbar := True; 14 | TStyleManager.TrySetStyle('Glow'); 15 | Application.CreateForm(TViewMain, ViewMain); 16 | Application.Run; 17 | end. 18 | -------------------------------------------------------------------------------- /samples/vcl/publisher/VCLPublisher.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/publisher/VCLPublisher.res -------------------------------------------------------------------------------- /samples/vcl/publisher/VCLPublisher_Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/publisher/VCLPublisher_Icon.ico -------------------------------------------------------------------------------- /samples/vcl/publisher/img/lego-wars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/publisher/img/lego-wars.jpg -------------------------------------------------------------------------------- /samples/vcl/publisher/img/orion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/publisher/img/orion.jpg -------------------------------------------------------------------------------- /samples/vcl/publisher/img/vcl-sample.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/publisher/img/vcl-sample.PNG -------------------------------------------------------------------------------- /samples/vcl/publisher/src/Example.Notification.pas: -------------------------------------------------------------------------------- 1 | unit Example.Notification; 2 | 3 | interface 4 | 5 | procedure SendNotification; 6 | 7 | implementation 8 | 9 | uses 10 | Notify; 11 | 12 | 13 | procedure SendNotification; 14 | var 15 | LTags: TArray; 16 | LNotification: INotifyNotification; 17 | begin 18 | LNotification := New.Notification; 19 | LNotification.Topic(CbTopic.Text); 20 | LNotification.Title(lbeTitle.Text); 21 | LNotification.MessageContent(lbeMessage.Text); 22 | LNotification.Priority(TNotifyPriority(CbPriority.ItemIndex + 1)); 23 | LNotification.Icon(lbeIconAttachment.Text); 24 | LNotification.FilePath(lbeFileAttachment.Text); 25 | LNotification.Attach(lbeURLAttachment.Text); 26 | LNotification.Email(lbeEmail.Text); 27 | Ntfy.Notification(LNotification); 28 | Ntfy.Publish; 29 | end; 30 | 31 | end. 32 | -------------------------------------------------------------------------------- /samples/vcl/publisher/src/View.Main.pas: -------------------------------------------------------------------------------- 1 | unit View.Main; 2 | 3 | interface 4 | 5 | uses 6 | Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 7 | Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, 8 | Vcl.Imaging.pngimage, Notify, Vcl.CheckLst, Data.DB, Vcl.Grids, Vcl.DBGrids, 9 | FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, 10 | FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, 11 | FireDAC.Comp.DataSet, FireDAC.Comp.Client, Vcl.Mask; 12 | 13 | type 14 | TViewMain = class(TForm) 15 | gpInfo: TGroupBox; 16 | gbActions: TGroupBox; 17 | CbPriority: TComboBox; 18 | lbPriority: TLabel; 19 | Label1: TLabel; 20 | lbeFileAttachment: TLabeledEdit; 21 | btnFileAttachment: TButton; 22 | lbeURLAttachment: TLabeledEdit; 23 | lbeIconAttachment: TLabeledEdit; 24 | FileDialog: TOpenDialog; 25 | CbActionType: TComboBox; 26 | lblActionType: TLabel; 27 | CkActionClear: TCheckBox; 28 | CbActionMethod: TComboBox; 29 | lbActionMethod: TLabel; 30 | MemActionBody: TMemo; 31 | lblActionBody: TLabel; 32 | DBGrid1: TDBGrid; 33 | btnAddAction: TButton; 34 | btnDeleteAction: TButton; 35 | TableActions: TFDMemTable; 36 | TableActionsTYPE: TIntegerField; 37 | DsTableAction: TDataSource; 38 | TableActionsMETHOD: TStringField; 39 | TableActionsCLEAR: TBooleanField; 40 | TableActionsBODY: TMemoField; 41 | lbeEmail: TLabeledEdit; 42 | lblTopic: TLabel; 43 | lbeTitle: TLabeledEdit; 44 | CbTopic: TComboBox; 45 | lbeActionLabel: TLabeledEdit; 46 | lbeActionURL: TLabeledEdit; 47 | TableActionsLABEL: TStringField; 48 | TableActionsURL: TStringField; 49 | lbeDelay: TLabeledEdit; 50 | btnPublish: TButton; 51 | LbeBaseURL: TLabeledEdit; 52 | MemMessage: TMemo; 53 | LbMessage: TLabel; 54 | LbeUsername: TLabeledEdit; 55 | LbePassword: TLabeledEdit; 56 | CkDisableFirebase: TCheckBox; 57 | CkCache: TCheckBox; 58 | MemTags: TMemo; 59 | procedure FormCreate(Sender: TObject); 60 | procedure btnFileAttachmentClick(Sender: TObject); 61 | procedure CbActionTypeChange(Sender: TObject); 62 | procedure btnAddActionClick(Sender: TObject); 63 | procedure btnDeleteActionClick(Sender: TObject); 64 | published 65 | procedure btnPublishClick(Sender: TObject); 66 | private 67 | FNotification: INotifyNotification; 68 | procedure SendNotification; 69 | procedure AddActions; 70 | end; 71 | 72 | var 73 | ViewMain: TViewMain; 74 | 75 | implementation 76 | 77 | uses 78 | System.Threading; 79 | 80 | {$R *.dfm} 81 | 82 | procedure TViewMain.AddActions; 83 | begin 84 | if not TableActions.IsEmpty then 85 | begin 86 | TableActions.First; 87 | while not TableActions.Eof do 88 | begin 89 | FNotification.Action(New.Action 90 | .&Label(TableActionsLABEL.AsString) 91 | .&Url(TableActionsURL.AsString) 92 | .Method(TableActionsMETHOD.AsString) 93 | .Clear(TableActionsCLEAR.AsBoolean) 94 | .&Type(TNotifyActionType(Ord(TableActionsTYPE.AsInteger))) 95 | .Body(TableActionsBODY.GetAsString) 96 | ); 97 | TableActions.Next; 98 | end; 99 | end; 100 | end; 101 | 102 | procedure TViewMain.btnAddActionClick(Sender: TObject); 103 | begin 104 | 105 | if TableActions.RecordCount >= 3 then 106 | Exit; 107 | 108 | TableActions.Open; 109 | 110 | TableActions.AppendRecord([ 111 | Ord(TNotifyActionType(CbActionType.ItemIndex)), 112 | CbActionMethod.Text, 113 | CkActionClear.Checked, 114 | MemActionBody.Lines, 115 | lbeActionLabel.Text, 116 | lbeActionURL.Text 117 | ]) 118 | end; 119 | 120 | procedure TViewMain.btnDeleteActionClick(Sender: TObject); 121 | begin 122 | FNotification.ClearActions; 123 | if not TableActions.IsEmpty then 124 | TableActions.EmptyDataSet; 125 | end; 126 | 127 | procedure TViewMain.btnFileAttachmentClick(Sender: TObject); 128 | begin 129 | FileDialog.InitialDir := ExtractFileDir(Application.ExeName); 130 | if FileDialog.Execute then 131 | begin 132 | lbeFileAttachment.Text := FileDialog.FileName; 133 | end; 134 | end; 135 | 136 | procedure TViewMain.btnPublishClick(Sender: TObject); 137 | var 138 | LTask: ITask; 139 | begin 140 | 141 | if lbeTitle.Text = '' then 142 | Exit; 143 | 144 | LTask := TTask.Create(procedure begin 145 | try 146 | btnPublish.Enabled := False; 147 | btnPublish.Caption := '⌛ Notify'; 148 | SendNotification; 149 | finally 150 | TThread.Queue(nil, procedure begin 151 | btnPublish.Caption := '✔ Notify'; 152 | btnPublish.Enabled := True; 153 | end) 154 | end; 155 | end); 156 | 157 | LTask.Start; 158 | end; 159 | 160 | procedure TViewMain.CbActionTypeChange(Sender: TObject); 161 | begin 162 | 163 | if CbActionType.Text = 'view' then 164 | CbActionMethod.ItemIndex := 1; 165 | 166 | CbActionMethod.Enabled := (CbActionType.Text <> 'view'); 167 | MemActionBody.Enabled := (CbActionType.Text <> 'view'); 168 | lblActionBody.Enabled := (CbActionType.Text <> 'view'); 169 | 170 | end; 171 | 172 | procedure TViewMain.FormCreate(Sender: TObject); 173 | begin 174 | FNotification := New.Notification; 175 | btnAddAction.Click; 176 | end; 177 | 178 | procedure TViewMain.SendNotification; 179 | begin 180 | 181 | FNotification := New.Notification; 182 | 183 | FNotification 184 | .Topic(CbTopic.Text) 185 | .Title(lbeTitle.Text) 186 | .MessageContent(MemMessage.Lines.Text) 187 | .Priority(TNotifyPriority(CbPriority.ItemIndex + 1)) 188 | .Icon(lbeIconAttachment.Text) 189 | .AttachFile(lbeFileAttachment.Text) 190 | .AttachURL(lbeURLAttachment.Text) 191 | .Email(lbeEmail.Text) 192 | .Delay(lbeDelay.Text) 193 | .Tags(MemTags.Lines.ToStringArray); 194 | 195 | AddActions; 196 | 197 | Ntfy 198 | .UserName(LbeUsername.Text) 199 | .Password(LbePassword.Text) 200 | .BaseURL(LbeBaseURL.Text) 201 | .Topic(CbTopic.Text) 202 | .DisableFireBase(CkDisableFirebase.Checked) 203 | .Cache(CkCache.Checked) 204 | .Notification(FNotification).Publish; 205 | 206 | end; 207 | 208 | end. 209 | -------------------------------------------------------------------------------- /samples/vcl/subscriber/Ntfy-54x.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/Ntfy-54x.ico -------------------------------------------------------------------------------- /samples/vcl/subscriber/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Subscriber VCL Sample 4 | 5 |
6 | 7 | Compile and explore the VCL sample. Also check this [link](https://github.com/p-samuel/delphi-notify/tree/dev-psamuel/sample/console/publisher) for other resources. 8 | 9 |
10 | 11 |
12 | 13 | 14 | ## How to subscribe 15 | 16 | Subscription is event based. 17 | 18 | ``` pascal 19 | uses 20 | Notify; 21 | 22 | begin 23 | Ntfy.Subscribe('your-very-secret-topic', 24 | procedure (AEvent: INotifyEvent) 25 | begin 26 | ShowMessage('You received a message: ' + AEvent.Title) 27 | end); 28 | end; 29 | ``` 30 | 31 | You can define your own callback procedure and use to perform other operations every time a notification is fired. 32 | 33 | ``` pascal 34 | uses 35 | Notify; 36 | 37 | procedure YourCallBackProcedure(AEvent: INotifyEvent); 38 | begin 39 | DoSomethingWith(AEvent); 40 | end; 41 | 42 | begin 43 | Ntfy.Subscribe('your-very-secret-topic', YourCallBackProcedure); 44 | end; 45 | ``` 46 | 47 | ## ⚙ Self-hosted server (optional for this sample) 48 | Install Docker in your machine and run these commands after: 49 | 50 | ``` cmd 51 | cd delphi-notify\tests 52 | docker compose create 53 | docker cp server.yml ntfy:/etc/ntfy 54 | docker compose up 55 | ``` -------------------------------------------------------------------------------- /samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/AppIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/AppIcon.icns -------------------------------------------------------------------------------- /samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/AppIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/AppIcon.ico -------------------------------------------------------------------------------- /samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/Uwp_150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/Uwp_150.png -------------------------------------------------------------------------------- /samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/Uwp_44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/VCLSubscriber.Artwork/Windows/Uwp_44.png -------------------------------------------------------------------------------- /samples/vcl/subscriber/VCLSubscriber.dpr: -------------------------------------------------------------------------------- 1 | program VCLSubscriber; 2 | 3 | uses 4 | Vcl.Forms, 5 | View.Main in 'src\View.Main.pas' {ViewMain}, 6 | Example.Push.Notifications in 'src\Example.Push.Notifications.pas', 7 | Vcl.Themes, 8 | Vcl.Styles; 9 | 10 | {$R *.res} 11 | 12 | begin 13 | ReportMemoryLeaksOnShutdown := True; 14 | Application.Initialize; 15 | Application.MainFormOnTaskbar := True; 16 | TStyleManager.TrySetStyle('Glow'); 17 | Application.CreateForm(TViewMain, ViewMain); 18 | Application.Run; 19 | end. 20 | -------------------------------------------------------------------------------- /samples/vcl/subscriber/VCLSubscriber.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/VCLSubscriber.res -------------------------------------------------------------------------------- /samples/vcl/subscriber/img/subscriber-vcl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/samples/vcl/subscriber/img/subscriber-vcl.png -------------------------------------------------------------------------------- /samples/vcl/subscriber/src/Example.Push.Notifications.pas: -------------------------------------------------------------------------------- 1 | unit Example.Push.Notifications; 2 | 3 | interface 4 | 5 | uses 6 | System.Notification, 7 | Notify; 8 | 9 | procedure PushWindowsNotification(AEvent: INotifyEvent); 10 | 11 | implementation 12 | 13 | procedure PushWindowsNotification(AEvent: INotifyEvent); 14 | var 15 | LNotificationCenter: TNotificationCenter; 16 | LNotification: TNotification; 17 | begin 18 | LNotificationCenter := TNotificationCenter.Create(nil); 19 | try 20 | LNotification := LNotificationCenter.CreateNotification(); 21 | try 22 | LNotification.Name := AEvent.Id; 23 | LNotification.Title := AEvent.Title; 24 | LNotification.AlertBody := AEvent.MessageContent; 25 | LNotificationCenter.PresentNotification(LNotification); 26 | finally 27 | LNotification.Free; 28 | end; 29 | finally 30 | LNotificationCenter.Free; 31 | end; 32 | end; 33 | 34 | end. 35 | -------------------------------------------------------------------------------- /samples/vcl/subscriber/src/View.Main.dfm: -------------------------------------------------------------------------------- 1 | object ViewMain: TViewMain 2 | Left = 960 3 | Top = 256 4 | Caption = 'Ntfy Subscriber' 5 | ClientHeight = 584 6 | ClientWidth = 724 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | Position = poDesktopCenter 14 | OnClose = FormClose 15 | OnCreate = FormCreate 16 | DesignSize = ( 17 | 724 18 | 584) 19 | TextHeight = 13 20 | object lblTopic: TLabel 21 | Left = 445 22 | Top = 13 23 | Width = 260 24 | Height = 13 25 | Caption = 'Topics - Break a line for each. Don'#39't leave empty lines.' 26 | end 27 | object BtnSubscribe: TButton 28 | Left = 16 29 | Top = 486 30 | Width = 132 31 | Height = 25 32 | Caption = 'Subscribe' 33 | TabOrder = 0 34 | OnClick = BtnSubscribeClick 35 | end 36 | object BtnUnsubscribe: TButton 37 | Left = 16 38 | Top = 517 39 | Width = 132 40 | Height = 25 41 | Caption = 'Unsubscribe' 42 | Enabled = False 43 | TabOrder = 1 44 | OnClick = BtnUnsubscribeClick 45 | end 46 | object DBGrid1: TDBGrid 47 | Left = 154 48 | Top = 138 49 | Width = 551 50 | Height = 433 51 | Anchors = [akLeft, akTop, akRight, akBottom] 52 | DataSource = DsTable 53 | Options = [dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgConfirmDelete, dgCancelOnExit, dgTitleClick, dgTitleHotTrack] 54 | TabOrder = 2 55 | TitleFont.Charset = DEFAULT_CHARSET 56 | TitleFont.Color = clWindowText 57 | TitleFont.Height = -11 58 | TitleFont.Name = 'Tahoma' 59 | TitleFont.Style = [] 60 | Columns = < 61 | item 62 | Expanded = False 63 | FieldName = 'ID' 64 | Width = 50 65 | Visible = True 66 | end 67 | item 68 | Expanded = False 69 | FieldName = 'TIME' 70 | Width = 118 71 | Visible = True 72 | end 73 | item 74 | Expanded = False 75 | FieldName = 'PRIORITY' 76 | Visible = True 77 | end 78 | item 79 | Expanded = False 80 | FieldName = 'TITLE' 81 | Width = 146 82 | Visible = True 83 | end 84 | item 85 | Expanded = False 86 | FieldName = 'MSG' 87 | Width = 700 88 | Visible = True 89 | end 90 | item 91 | Expanded = False 92 | FieldName = 'TOPIC' 93 | Width = 300 94 | Visible = True 95 | end> 96 | end 97 | object CkPoll: TCheckBox 98 | Left = 16 99 | Top = 137 100 | Width = 41 101 | Height = 17 102 | Caption = 'Poll' 103 | TabOrder = 3 104 | end 105 | object GbSince: TRadioGroup 106 | Left = 16 107 | Top = 17 108 | Width = 89 109 | Height = 71 110 | Caption = 'Since' 111 | Enabled = False 112 | ItemIndex = 2 113 | Items.Strings = ( 114 | 'Message Id' 115 | 'Duration' 116 | 'Unix Time') 117 | TabOrder = 4 118 | OnClick = GbSinceClick 119 | end 120 | object CkScheduled: TCheckBox 121 | Left = 16 122 | Top = 155 123 | Width = 132 124 | Height = 17 125 | Caption = 'Fectch scheduled only' 126 | TabOrder = 5 127 | end 128 | object CkSince: TCheckBox 129 | Left = 16 130 | Top = 173 131 | Width = 132 132 | Height = 17 133 | Caption = 'Fetch cached messages' 134 | TabOrder = 6 135 | OnClick = CkSinceClick 136 | end 137 | object GbFilters: TGroupBox 138 | Left = 16 139 | Top = 253 140 | Width = 132 141 | Height = 225 142 | Caption = 'Filters' 143 | TabOrder = 7 144 | object lbPriority: TLabel 145 | Left = 15 146 | Top = 177 147 | Width = 34 148 | Height = 13 149 | Caption = 'Priority' 150 | end 151 | object lbeIdFilter: TLabeledEdit 152 | Left = 15 153 | Top = 31 154 | Width = 104 155 | Height = 21 156 | EditLabel.Width = 53 157 | EditLabel.Height = 13 158 | EditLabel.Caption = 'Message id' 159 | TabOrder = 0 160 | Text = '' 161 | TextHint = 'Id' 162 | end 163 | object lbeFilterTitle: TLabeledEdit 164 | Left = 15 165 | Top = 71 166 | Width = 104 167 | Height = 21 168 | EditLabel.Width = 65 169 | EditLabel.Height = 13 170 | EditLabel.Caption = 'Message Title' 171 | TabOrder = 1 172 | Text = '' 173 | TextHint = 'Title' 174 | end 175 | object lbeFilterMessage: TLabeledEdit 176 | Left = 15 177 | Top = 111 178 | Width = 104 179 | Height = 21 180 | EditLabel.Width = 67 181 | EditLabel.Height = 13 182 | EditLabel.Caption = 'Message Text' 183 | TabOrder = 2 184 | Text = '' 185 | TextHint = 'Message' 186 | end 187 | object CbFilterPriority: TComboBox 188 | Left = 15 189 | Top = 196 190 | Width = 104 191 | Height = 21 192 | Style = csDropDownList 193 | ItemIndex = 0 194 | TabOrder = 3 195 | Items.Strings = ( 196 | '' 197 | 'Min' 198 | 'Low' 199 | 'Default' 200 | 'High' 201 | 'Maximum') 202 | end 203 | object lbeFilterTags: TLabeledEdit 204 | Left = 15 205 | Top = 150 206 | Width = 104 207 | Height = 21 208 | EditLabel.Width = 68 209 | EditLabel.Height = 13 210 | EditLabel.Caption = 'Message Tags' 211 | TabOrder = 4 212 | Text = '' 213 | TextHint = 'Tags' 214 | end 215 | end 216 | object BtnClearTable: TButton 217 | Left = 680 218 | Top = 107 219 | Width = 25 220 | Height = 25 221 | Caption = #9851 222 | TabOrder = 8 223 | OnClick = BtnClearTableClick 224 | end 225 | object LbeBaseURL: TLabeledEdit 226 | Left = 16 227 | Top = 111 228 | Width = 249 229 | Height = 21 230 | EditLabel.Width = 45 231 | EditLabel.Height = 13 232 | EditLabel.Caption = 'Base URL' 233 | TabOrder = 9 234 | Text = 'https://ntfy.sh' 235 | TextHint = 'Tags' 236 | end 237 | object MemTopics: TMemo 238 | Left = 462 239 | Top = 32 240 | Width = 243 241 | Height = 52 242 | BorderStyle = bsNone 243 | Lines.Strings = ( 244 | 'your-very-secret-topic' 245 | 'notify-delphi-integration-8jh27d') 246 | TabOrder = 10 247 | end 248 | object BtnHide: TButton 249 | Left = 16 250 | Top = 548 251 | Width = 132 252 | Height = 25 253 | Caption = 'Hide' 254 | TabOrder = 11 255 | OnClick = BtnHideClick 256 | end 257 | object LbeUsername: TLabeledEdit 258 | Left = 271 259 | Top = 111 260 | Width = 132 261 | Height = 21 262 | EditLabel.Width = 48 263 | EditLabel.Height = 13 264 | EditLabel.Caption = 'Username' 265 | TabOrder = 12 266 | Text = '' 267 | TextHint = '(optional)' 268 | end 269 | object LbePassword: TLabeledEdit 270 | Left = 409 271 | Top = 111 272 | Width = 132 273 | Height = 21 274 | EditLabel.Width = 46 275 | EditLabel.Height = 13 276 | EditLabel.Caption = 'Password' 277 | PasswordChar = '*' 278 | TabOrder = 13 279 | Text = '' 280 | TextHint = '(optional)' 281 | end 282 | object DtSince: TDateTimePicker 283 | Left = 111 284 | Top = 63 285 | Width = 137 286 | Height = 21 287 | Date = 44911.000000000000000000 288 | Time = 0.858430219908768800 289 | TabOrder = 14 290 | end 291 | object EdtSince: TEdit 292 | Left = 111 293 | Top = 36 294 | Width = 137 295 | Height = 21 296 | TabOrder = 15 297 | TextHint = 'Message id or Unix time' 298 | end 299 | object CkShowNotification: TCheckBox 300 | Left = 16 301 | Top = 192 302 | Width = 132 303 | Height = 17 304 | Caption = 'Show up notifications' 305 | TabOrder = 16 306 | end 307 | object CkAutoSaveSubs: TCheckBox 308 | Left = 16 309 | Top = 211 310 | Width = 132 311 | Height = 17 312 | Caption = 'Auto Save Subs.' 313 | TabOrder = 17 314 | end 315 | object CkAutoSubscribe: TCheckBox 316 | Left = 17 317 | Top = 231 318 | Width = 132 319 | Height = 17 320 | Caption = 'Auto Subscribe' 321 | TabOrder = 18 322 | end 323 | object TableNotification: TFDMemTable 324 | FetchOptions.AssignedValues = [evMode] 325 | FetchOptions.Mode = fmAll 326 | ResourceOptions.AssignedValues = [rvSilentMode] 327 | ResourceOptions.SilentMode = True 328 | UpdateOptions.AssignedValues = [uvCheckRequired, uvAutoCommitUpdates] 329 | UpdateOptions.CheckRequired = False 330 | UpdateOptions.AutoCommitUpdates = True 331 | Left = 568 332 | Top = 336 333 | object TableNotificationID: TStringField 334 | DisplayLabel = 'Id' 335 | FieldName = 'ID' 336 | Size = 30 337 | end 338 | object TableNotificationTIME: TStringField 339 | DisplayLabel = 'Time' 340 | FieldName = 'TIME' 341 | end 342 | object TableNotificationPRIORITY: TStringField 343 | DisplayLabel = 'Priority' 344 | FieldName = 'PRIORITY' 345 | Size = 10 346 | end 347 | object TableNotificationTITLE: TStringField 348 | DisplayLabel = 'Title' 349 | FieldName = 'TITLE' 350 | Size = 50 351 | end 352 | object TableNotificationMSG: TStringField 353 | DisplayLabel = 'Message' 354 | FieldName = 'MSG' 355 | Size = 500 356 | end 357 | object TableNotificationTOPIC: TStringField 358 | DisplayLabel = 'Topic' 359 | FieldName = 'TOPIC' 360 | Size = 100 361 | end 362 | end 363 | object DsTable: TDataSource 364 | DataSet = TableNotification 365 | Left = 640 366 | Top = 336 367 | end 368 | object Tray: TTrayIcon 369 | PopupMenu = Pop 370 | Left = 392 371 | Top = 344 372 | end 373 | object Pop: TPopupMenu 374 | Left = 432 375 | Top = 344 376 | object PopShow: TMenuItem 377 | Caption = 'Show' 378 | OnClick = PopShowClick 379 | end 380 | object PopSubscribe: TMenuItem 381 | Caption = 'Subscribe' 382 | OnClick = PopSubscribeClick 383 | end 384 | object PopUnsubscribe: TMenuItem 385 | Caption = 'Unsubscribe' 386 | Enabled = False 387 | OnClick = PopUnsubscribeClick 388 | end 389 | object PopQuit: TMenuItem 390 | Caption = 'Quit' 391 | OnClick = PopQuitClick 392 | end 393 | end 394 | end 395 | -------------------------------------------------------------------------------- /samples/vcl/subscriber/src/View.Main.pas: -------------------------------------------------------------------------------- 1 | unit View.Main; 2 | 3 | interface 4 | 5 | uses 6 | Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 7 | Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Notify, 8 | System.Notification, Data.DB, FireDAC.Stan.Intf, FireDAC.Stan.Option, 9 | FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, 10 | FireDAC.DApt.Intf, FireDAC.Comp.DataSet, FireDAC.Comp.Client, Vcl.Grids, 11 | Vcl.DBGrids, Vcl.Imaging.pngimage, Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.CheckLst, 12 | Vcl.Menus, Vcl.Mask, System.IniFiles; 13 | 14 | type 15 | TViewMain = class(TForm) 16 | BtnSubscribe: TButton; 17 | BtnUnsubscribe: TButton; 18 | DBGrid1: TDBGrid; 19 | TableNotification: TFDMemTable; 20 | TableNotificationID: TStringField; 21 | TableNotificationTITLE: TStringField; 22 | TableNotificationMSG: TStringField; 23 | DsTable: TDataSource; 24 | CkPoll: TCheckBox; 25 | GbSince: TRadioGroup; 26 | CkScheduled: TCheckBox; 27 | CkSince: TCheckBox; 28 | GbFilters: TGroupBox; 29 | lbeIdFilter: TLabeledEdit; 30 | lbeFilterTitle: TLabeledEdit; 31 | lbeFilterMessage: TLabeledEdit; 32 | CbFilterPriority: TComboBox; 33 | lbPriority: TLabel; 34 | lbeFilterTags: TLabeledEdit; 35 | lblTopic: TLabel; 36 | BtnClearTable: TButton; 37 | LbeBaseURL: TLabeledEdit; 38 | MemTopics: TMemo; 39 | Tray: TTrayIcon; 40 | Pop: TPopupMenu; 41 | PopShow: TMenuItem; 42 | PopSubscribe: TMenuItem; 43 | PopUnsubscribe: TMenuItem; 44 | BtnHide: TButton; 45 | LbeUsername: TLabeledEdit; 46 | LbePassword: TLabeledEdit; 47 | PopQuit: TMenuItem; 48 | DtSince: TDateTimePicker; 49 | EdtSince: TEdit; 50 | TableNotificationTIME: TStringField; 51 | TableNotificationTOPIC: TStringField; 52 | TableNotificationPRIORITY: TStringField; 53 | CkShowNotification: TCheckBox; 54 | CkAutoSaveSubs: TCheckBox; 55 | CkAutoSubscribe: TCheckBox; 56 | procedure BtnSubscribeClick(Sender: TObject); 57 | procedure BtnUnsubscribeClick(Sender: TObject); 58 | procedure GbSinceClick(Sender: TObject); 59 | procedure CkSinceClick(Sender: TObject); 60 | procedure BtnClearTableClick(Sender: TObject); 61 | procedure BtnHideClick(Sender: TObject); 62 | procedure CkFiltersExit(Sender: TObject); 63 | procedure PopQuitClick(Sender: TObject); 64 | procedure PopShowClick(Sender: TObject); 65 | procedure PopSubscribeClick(Sender: TObject); 66 | procedure PopUnsubscribeClick(Sender: TObject); 67 | procedure FormCreate(Sender: TObject); 68 | procedure FormClose(Sender: TObject; var Action: TCloseAction); 69 | private 70 | FTopics: String; 71 | FSince: String; 72 | FIniFileName: String; 73 | procedure CheckTopics; 74 | procedure CheckSince; 75 | procedure CheckButtons; 76 | procedure CheckPops; 77 | function CheckPriority: String; 78 | procedure YourCallBackProcedure(AEvent: INotifyEvent); 79 | procedure LoadSettingsFromIni; 80 | procedure SaveSettingsToIni; 81 | procedure AutoSubscribe; 82 | end; 83 | 84 | var 85 | ViewMain: TViewMain; 86 | 87 | implementation 88 | 89 | {$R *.dfm} 90 | 91 | uses 92 | System.DateUtils, 93 | Example.Push.Notifications; 94 | 95 | procedure TViewMain.BtnClearTableClick(Sender: TObject); 96 | begin 97 | TableNotification.EmptyDataSet; 98 | end; 99 | 100 | procedure TViewMain.BtnHideClick(Sender: TObject); 101 | begin 102 | Hide; 103 | Tray.Visible := True; 104 | end; 105 | 106 | procedure TViewMain.BtnSubscribeClick(Sender: TObject); 107 | begin 108 | CheckTopics; 109 | CheckSince; 110 | CheckButtons; 111 | 112 | Ntfy 113 | .UserName(LbeUsername.Text) 114 | .Password(LbePassword.Text) 115 | .BaseURL(LbeBaseURL.Text) 116 | .Poll(CkPoll.Checked) 117 | .Since(FSince) 118 | .Scheduled(CkScheduled.Checked); 119 | 120 | Ntfy 121 | .ClearFilters 122 | .Filter(TNotifyFilter.ID, lbeIdFilter.Text) 123 | .Filter(TNotifyFilter.TITLE, lbeFilterTitle.Text) 124 | .Filter(TNotifyFilter.MESSAGECONTENT, lbeFilterMessage.Text) 125 | .Filter(TNotifyFilter.TAGS, lbeFilterTags.Text) 126 | .Filter(TNotifyFilter.PRIORITY, CheckPriority) 127 | .Subscribe(FTopics, YourCallBackProcedure); 128 | 129 | CheckPops; 130 | end; 131 | 132 | procedure TViewMain.BtnUnsubscribeClick(Sender: TObject); 133 | begin 134 | Ntfy.Unsubscribe; 135 | CheckButtons; 136 | CheckPops; 137 | end; 138 | 139 | procedure TViewMain.CheckButtons; 140 | begin 141 | if (not CkPoll.Checked) then 142 | begin 143 | BtnSubscribe.Enabled := not BtnSubscribe.Enabled; 144 | BtnUnsubscribe.Enabled := not BtnUnsubscribe.Enabled; 145 | end; 146 | end; 147 | 148 | procedure TViewMain.CheckPops; 149 | begin 150 | PopSubscribe.Enabled := not PopSubscribe.Enabled; 151 | PopUnsubscribe.Enabled := not PopUnsubscribe.Enabled; 152 | end; 153 | 154 | function TViewMain.CheckPriority: String; 155 | begin 156 | Result := ''; 157 | if CbFilterPriority.ItemIndex > 0 then 158 | Result := IntToStr(Ord(TNotifyPriority(CbFilterPriority.ItemIndex))); 159 | end; 160 | 161 | procedure TViewMain.CheckSince; 162 | begin 163 | FSince := ''; 164 | if CkSince.Checked then 165 | case GbSince.ItemIndex of 166 | 0, 1: FSince := EdtSince.Text; 167 | 2: FSince := DateTimeToUnix(DtSince.DateTime).ToString; 168 | end; 169 | end; 170 | 171 | procedure TViewMain.CheckTopics; 172 | var 173 | LTopic: String; 174 | begin 175 | FTopics := ''; 176 | for LTopic in MemTopics.Lines do 177 | if FTopics = '' then 178 | FTopics := LTopic 179 | else 180 | FTopics := Format('%s,%s', [FTopics, LTopic]); 181 | end; 182 | 183 | procedure TViewMain.CkFiltersExit(Sender: TObject); 184 | begin 185 | if CkScheduled.Checked then 186 | CkPoll.Checked := True; 187 | end; 188 | 189 | procedure TViewMain.CkSinceClick(Sender: TObject); 190 | begin 191 | GbSince.Enabled := not GbSince.Enabled; 192 | end; 193 | 194 | procedure TViewMain.FormClose(Sender: TObject; var Action: TCloseAction); 195 | begin 196 | SaveSettingsToIni; 197 | Ntfy.Unsubscribe; 198 | end; 199 | 200 | procedure TViewMain.FormCreate(Sender: TObject); 201 | begin 202 | FIniFileName := ChangeFileExt(Application.ExeName, '.ini'); 203 | LoadSettingsFromIni; 204 | 205 | if CkAutoSubscribe.Checked then 206 | AutoSubscribe; 207 | end; 208 | 209 | procedure TViewMain.LoadSettingsFromIni; 210 | var 211 | Ini: TIniFile; 212 | TopicsList: TStringList; 213 | i: Integer; 214 | begin 215 | if not FileExists(FIniFileName) then 216 | Exit; 217 | 218 | Ini := TIniFile.Create(FIniFileName); 219 | try 220 | // Load general settings 221 | LbeBaseURL.Text := Ini.ReadString('Settings', 'BaseURL', 'https://ntfy.sh'); 222 | LbeUsername.Text := Ini.ReadString('Settings', 'Username', ''); 223 | LbePassword.Text := Ini.ReadString('Settings', 'Password', ''); 224 | CkPoll.Checked := Ini.ReadBool('Settings', 'Poll', True); 225 | CkScheduled.Checked := Ini.ReadBool('Settings', 'Scheduled', False); 226 | CkShowNotification.Checked := Ini.ReadBool('Settings', 'ShowNotification', True); 227 | CkAutoSubscribe.Checked := Ini.ReadBool('Settings', 'AutoSubscribe', False); 228 | CkAutoSaveSubs.Checked := Ini.ReadBool('Settings', 'AutoSaveSubs', False); 229 | 230 | // Load filters 231 | lbeIdFilter.Text := Ini.ReadString('Filters', 'ID', ''); 232 | lbeFilterTitle.Text := Ini.ReadString('Filters', 'Title', ''); 233 | lbeFilterMessage.Text := Ini.ReadString('Filters', 'Message', ''); 234 | lbeFilterTags.Text := Ini.ReadString('Filters', 'Tags', ''); 235 | CbFilterPriority.ItemIndex := Ini.ReadInteger('Filters', 'Priority', 0); 236 | 237 | // Load Since settings 238 | CkSince.Checked := Ini.ReadBool('Since', 'Enabled', False); 239 | GbSince.ItemIndex := Ini.ReadInteger('Since', 'Type', 0); 240 | EdtSince.Text := Ini.ReadString('Since', 'Value', ''); 241 | DtSince.DateTime := StrToDateTimeDef(Ini.ReadString('Since', 'DateTime', ''), Now); 242 | 243 | // Load topics 244 | MemTopics.Clear; 245 | TopicsList := TStringList.Create; 246 | try 247 | Ini.ReadSection('Topics', TopicsList); 248 | for i := 0 to TopicsList.Count - 1 do 249 | MemTopics.Lines.Add(Ini.ReadString('Topics', TopicsList[i], '')); 250 | finally 251 | TopicsList.Free; 252 | end; 253 | 254 | // Update UI based on loaded settings 255 | GbSince.Enabled := CkSince.Checked; 256 | GbSinceClick(nil); 257 | 258 | finally 259 | Ini.Free; 260 | end; 261 | end; 262 | 263 | procedure TViewMain.SaveSettingsToIni; 264 | var 265 | Ini: TIniFile; 266 | i: Integer; 267 | begin 268 | Ini := TIniFile.Create(FIniFileName); 269 | try 270 | // Save general settings 271 | Ini.WriteString('Settings', 'BaseURL', LbeBaseURL.Text); 272 | Ini.WriteString('Settings', 'Username', LbeUsername.Text); 273 | Ini.WriteString('Settings', 'Password', LbePassword.Text); 274 | Ini.WriteBool('Settings', 'Poll', CkPoll.Checked); 275 | Ini.WriteBool('Settings', 'Scheduled', CkScheduled.Checked); 276 | Ini.WriteBool('Settings', 'ShowNotification', CkShowNotification.Checked); 277 | Ini.WriteBool('Settings', 'AutoSubscribe', CkAutoSubscribe.Checked); 278 | Ini.WriteBool('Settings', 'AutoSaveSubs', CkAutoSaveSubs.Checked); 279 | 280 | // Save filters 281 | Ini.WriteString('Filters', 'ID', lbeIdFilter.Text); 282 | Ini.WriteString('Filters', 'Title', lbeFilterTitle.Text); 283 | Ini.WriteString('Filters', 'Message', lbeFilterMessage.Text); 284 | Ini.WriteString('Filters', 'Tags', lbeFilterTags.Text); 285 | Ini.WriteInteger('Filters', 'Priority', CbFilterPriority.ItemIndex); 286 | 287 | // Save Since settings 288 | Ini.WriteBool('Since', 'Enabled', CkSince.Checked); 289 | Ini.WriteInteger('Since', 'Type', GbSince.ItemIndex); 290 | Ini.WriteString('Since', 'Value', EdtSince.Text); 291 | Ini.WriteString('Since', 'DateTime', DateTimeToStr(DtSince.DateTime)); 292 | 293 | // Save topics 294 | Ini.EraseSection('Topics'); 295 | for i := 0 to MemTopics.Lines.Count - 1 do 296 | if MemTopics.Lines[i] <> '' then 297 | Ini.WriteString('Topics', 'Topic' + IntToStr(i), MemTopics.Lines[i]); 298 | 299 | finally 300 | Ini.Free; 301 | end; 302 | end; 303 | 304 | procedure TViewMain.AutoSubscribe; 305 | begin 306 | if MemTopics.Lines.Count > 0 then 307 | BtnSubscribe.Click; 308 | end; 309 | 310 | procedure TViewMain.GbSinceClick(Sender: TObject); 311 | begin 312 | case GbSince.ItemIndex of 313 | 0, 1: begin 314 | EdtSince.Enabled := True; 315 | DtSince.Enabled := False; 316 | end; 317 | else 318 | begin 319 | EdtSince.Enabled := False; 320 | DtSince.Enabled := True; 321 | end; 322 | end; 323 | end; 324 | 325 | procedure TViewMain.PopQuitClick(Sender: TObject); 326 | begin 327 | Close; 328 | end; 329 | 330 | procedure TViewMain.PopShowClick(Sender: TObject); 331 | begin 332 | Show; 333 | end; 334 | 335 | procedure TViewMain.PopSubscribeClick(Sender: TObject); 336 | begin 337 | BtnSubscribe.Click; 338 | end; 339 | 340 | procedure TViewMain.PopUnsubscribeClick(Sender: TObject); 341 | begin 342 | BtnUnsubscribe.Click; 343 | end; 344 | 345 | procedure TViewMain.YourCallBackProcedure(AEvent: INotifyEvent); 346 | begin 347 | TableNotification.Open; 348 | TableNotification.AppendRecord([ 349 | AEvent.Id, 350 | FormatDateTime('dd/MM/yyyy hh:mm:ss', UnixToDateTime(AEvent.Time)), 351 | IntToStr(Ord(AEvent.Priority)), 352 | AEvent.Title, 353 | AEvent.MessageContent, 354 | AEvent.Topic 355 | ]); 356 | 357 | if CkShowNotification.Checked then 358 | PushWindowsNotification(AEvent); 359 | end; 360 | 361 | end. 362 | -------------------------------------------------------------------------------- /samples/win-service/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Windows Service 4 | 5 |
6 | 7 | Under construction 8 | -------------------------------------------------------------------------------- /src/Notify.Action.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Action.Contract; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Custom.Types, 8 | System.Generics.Collections; 9 | 10 | type 11 | INotifyAction = interface 12 | ['{C7C7E46E-A4BA-440A-8A48-3AD485825186}'] 13 | function &Type: TNotifyActionType; overload; 14 | function &Type(const AValue: TNotifyActionType): INotifyAction; overload; 15 | function &Label: String; overload; 16 | function &Label(const AValue: String): INotifyAction; overload; 17 | function Url: String; overload; 18 | function Url(const AValue: String): INotifyAction; overload; 19 | function Clear: Boolean; overload; 20 | function Clear(const AValue: Boolean): INotifyAction; overload; 21 | function Method: String; overload; 22 | function Method(const AValue: String): INotifyAction; overload; 23 | function Body: String; overload; 24 | function Body(const AValue: String): INotifyAction; overload; 25 | function Headers: TNotifyActionHeaders; overload; 26 | function Headers(const AValue: TNotifyActionHeaders): INotifyAction; overload; 27 | function EventHeaders(const AValue: TNotifyActionHeaders): INotifyAction; overload; 28 | function EventHeaders: TNotifyActionHeaders; overload; 29 | function Validate: INotifyAction; 30 | end; 31 | 32 | implementation 33 | 34 | end. 35 | -------------------------------------------------------------------------------- /src/Notify.Action.DTO.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Action.DTO; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Custom.Types, 7 | Notify.JSON.Parser, 8 | System.Generics.Collections, 9 | REST.Json.Types; 10 | 11 | type 12 | 13 | TNotifyActionDTO = class(TJsonDTO) 14 | private 15 | [JSONName('action')] 16 | FAction: String; 17 | [JSONName('clear')] 18 | FClear: Boolean; 19 | [JSONName('label')] 20 | FLabel: String; 21 | [JSONName('url')] 22 | FUrl: String; 23 | [JSONName('method')] 24 | FMethod: String; 25 | [JSONName('body')] 26 | FBody: String; 27 | [JSONName('headers'), JSONMarshalled] 28 | FHeaders: TNotifyActionHeaders; 29 | published 30 | property Action: String read FAction write FAction; 31 | property Clear: Boolean read FClear write FClear; 32 | property &Label: String read FLabel write FLabel; 33 | property Url: String read FUrl write FUrl; 34 | property Method: String read FMethod write FMethod; 35 | property Body: String read FBody write FBody; 36 | property Headers: TNotifyActionHeaders read FHeaders write FHeaders; 37 | end; 38 | 39 | 40 | implementation 41 | 42 | end. 43 | -------------------------------------------------------------------------------- /src/Notify.Action.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Action; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Custom.Types, 8 | Notify.Action.Contract, 9 | System.Generics.Collections; 10 | 11 | type 12 | TNofifyAction = class sealed(TInterfacedObject, INotifyAction) 13 | private 14 | FType: TNotifyActionType; 15 | FLabel: String; 16 | FUrl: String; 17 | FClear: Boolean; 18 | FMethod: String; 19 | FBody: String; 20 | FEventHeaders: TNotifyActionHeaders; 21 | FHeaders: TNotifyActionHeaders; 22 | public 23 | class function New: INotifyAction; 24 | constructor Create; 25 | destructor Destroy; override; 26 | private 27 | function &Type: TNotifyActionType; overload; 28 | function &Type(const AValue: TNotifyActionType): INotifyAction; overload; 29 | function &Label: String; overload; 30 | function &Label(const AValue: String): INotifyAction; overload; 31 | function Url: String; overload; 32 | function Url(const AValue: String): INotifyAction; overload; 33 | function Clear: Boolean; overload; 34 | function Clear(const AValue: Boolean): INotifyAction; overload; 35 | function Method: String; overload; 36 | function Method(const AValue: String): INotifyAction; overload; 37 | function Body: String; overload; 38 | function Body(const AValue: String): INotifyAction; overload; 39 | function Headers: TNotifyActionHeaders; overload; 40 | function Headers(const AValue: TNotifyActionHeaders): INotifyAction; overload; 41 | function EventHeaders: TNotifyActionHeaders; overload; 42 | function EventHeaders(const AValue: TNotifyActionHeaders): INotifyAction; overload; 43 | function Validate: INotifyAction; 44 | end; 45 | 46 | implementation 47 | 48 | uses 49 | System.SysUtils; 50 | 51 | { TNofifyAction } 52 | 53 | function TNofifyAction.&Type: TNotifyActionType; 54 | begin 55 | Result := FType; 56 | end; 57 | 58 | function TNofifyAction.&Type(const AValue: TNotifyActionType ): INotifyAction; 59 | begin 60 | Result := Self; 61 | FType := AValue; 62 | end; 63 | 64 | function TNofifyAction.Body: String; 65 | begin 66 | Result := FBody; 67 | end; 68 | 69 | function TNofifyAction.Body(const AValue: String): INotifyAction; 70 | begin 71 | Result := Self; 72 | FBody := AValue; 73 | end; 74 | 75 | function TNofifyAction.Clear(const AValue: Boolean): INotifyAction; 76 | begin 77 | Result := Self; 78 | FClear := AValue; 79 | end; 80 | 81 | constructor TNofifyAction.Create; 82 | begin 83 | FMethod := 'POST'; 84 | FType := TNotifyActionType.VIEW; 85 | end; 86 | 87 | destructor TNofifyAction.Destroy; 88 | begin 89 | FEventHeaders.Free; 90 | inherited; 91 | end; 92 | 93 | function TNofifyAction.EventHeaders(const AValue: TNotifyActionHeaders): INotifyAction; 94 | begin 95 | Result := Self; 96 | FEventHeaders := TNotifyActionHeaders(AValue); 97 | end; 98 | 99 | function TNofifyAction.EventHeaders: TNotifyActionHeaders; 100 | begin 101 | Result := FEventHeaders; 102 | end; 103 | 104 | function TNofifyAction.Headers: TNotifyActionHeaders; 105 | begin 106 | Result := FHeaders; 107 | end; 108 | 109 | function TNofifyAction.Clear: Boolean; 110 | begin 111 | Result := FClear; 112 | end; 113 | 114 | function TNofifyAction.&Label(const AValue: String): INotifyAction; 115 | begin 116 | Result := Self; 117 | FLabel := AValue; 118 | end; 119 | 120 | function TNofifyAction.Method: String; 121 | begin 122 | Result := FMethod; 123 | end; 124 | 125 | function TNofifyAction.Method(const AValue: String): INotifyAction; 126 | begin 127 | Result := Self; 128 | FMethod := AValue; 129 | end; 130 | 131 | function TNofifyAction.&Label: String; 132 | begin 133 | Result := FLabel; 134 | end; 135 | 136 | class function TNofifyAction.New: INotifyAction; 137 | begin 138 | Result := Self.Create; 139 | end; 140 | 141 | function TNofifyAction.Url: String; 142 | begin 143 | Result := FUrl; 144 | end; 145 | 146 | function TNofifyAction.Url(const AValue: String): INotifyAction; 147 | begin 148 | Result := Self; 149 | FUrl := AValue; 150 | end; 151 | 152 | function TNofifyAction.Validate: INotifyAction; 153 | begin 154 | if FUrl = '' then 155 | raise Exception.Create('URL field is missing in one of the action items'); 156 | 157 | if FLabel = '' then 158 | raise Exception.Create('Label field is missing in one of the action items'); 159 | end; 160 | 161 | function TNofifyAction.Headers(const AValue: TNotifyActionHeaders): INotifyAction; 162 | begin 163 | Result := Self; 164 | FHeaders := AValue; 165 | end; 166 | 167 | end. 168 | -------------------------------------------------------------------------------- /src/Notify.Api.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Api.Contract; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | Notify.Config.Contract, 8 | Notify.Api.Response; 9 | 10 | type 11 | INotifyApi = interface 12 | ['{4A4C86DB-6176-404E-A317-BA789ED4848B}'] 13 | function Response: TNotifyApiResponse; 14 | function Get: INotifyApi; 15 | function Post: INotifyApi; 16 | function Put: INotifyApi; 17 | function ClearHeaders: INotifyApi; 18 | function ClearBody: INotifyApi; 19 | function ClearURLParameters: INotifyApi; 20 | function ClearEndPoint: INotifyApi; 21 | function AddHeader(const PName: String; AValue: String): INotifyApi; overload; 22 | function AddHeader(const AName: String; AValues: array of String): INotifyApi; overload; 23 | function AddBody(const AValue: String): INotifyApi; overload; 24 | function AddBody(const AValue: TFileStream): INotifyApi; overload; 25 | function AddEndPoint(const AValue: String): INotifyApi; overload; 26 | function AddURLParameter(const AName: String; AValue: String): INotifyApi; 27 | function Config(const AValue: INotifyConfig): INotifyApi; 28 | function AbortStream: INotifyApi; 29 | function Disconnect: INotifyApi; 30 | end; 31 | 32 | INotifyApiFactory = interface 33 | ['{66E16F57-69CD-41CC-9457-866A4E5D396E}'] 34 | function Api: INotifyApi; 35 | end; 36 | 37 | implementation 38 | 39 | end. 40 | -------------------------------------------------------------------------------- /src/Notify.Api.Factory.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Api.Factory; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Api.Contract; 7 | 8 | type 9 | TNotifyApiFactory = class(TInterfacedObject, INotifyApiFactory) 10 | public 11 | class function New: INotifyApiFactory; 12 | function Api: INotifyApi; 13 | end; 14 | 15 | implementation 16 | 17 | uses 18 | Notify.Api.NetHTTP, 19 | Notify.Api.Indy; 20 | 21 | { TNotifyApiFactory } 22 | 23 | function TNotifyApiFactory.Api: INotifyApi; 24 | begin 25 | {$IFDEF NTFY_HTTP_INDY} 26 | Result := TNotifyApiIndy.New 27 | {$ELSE} 28 | Result := TNotifyApiNetHTTP.New; 29 | {$ENDIF} 30 | end; 31 | 32 | class function TNotifyApiFactory.New: INotifyApiFactory; 33 | begin 34 | Result := Self.Create; 35 | end; 36 | 37 | end. 38 | -------------------------------------------------------------------------------- /src/Notify.Api.Indy.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Api.Indy; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.Threading, 8 | IdBaseComponent, 9 | IdComponent, 10 | IdIOHandler, 11 | IdIOHandlerSocket, 12 | IdIOHandlerStack, 13 | IdSSL, 14 | IdSSLOpenSSL, 15 | IdTCPConnection, 16 | IdTCPClient, 17 | IdHTTP, 18 | IdStream, 19 | IdGlobal, 20 | Notify.Api.Contract, 21 | Notify.Config.Contract, 22 | Notify.Subscription.Event, 23 | NX.Horizon, 24 | Notify.Api.Response; 25 | 26 | type 27 | 28 | TSSEThread = class(TThread) 29 | private 30 | FUrl: String; 31 | FIdHttp: TIdHTTP; 32 | FIdEventStream: TMemoryStream; 33 | FResponse: TNotifyApiResponse; 34 | FConfig: INotifyConfig; 35 | const FCloseConnectionMessage = '/\/\'; 36 | procedure DoOnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 37 | public 38 | constructor Create(AUrl: String; var AIdHttp: TIdHTTP; var AResponse: TNotifyApiResponse; var AConfig: INotifyConfig); 39 | procedure Execute; override; 40 | destructor Destroy; override; 41 | procedure AbortStream; 42 | end; 43 | 44 | TNotifyApiIndy = class(TInterfacedObject, INotifyApi) 45 | strict private 46 | FIOHandlerSSL: TIdSSLIOHandlerSocketOpenSSL; 47 | FIdHTTP: TIdHTTP; 48 | FBodyStream: TMemoryStream; 49 | FNotifyConfig: INotifyConfig; 50 | FEndPoint: String; 51 | FConnectionThread: TSSEThread; 52 | FURLParametersList: TStringList; 53 | public 54 | constructor Create; 55 | destructor Destroy; override; 56 | class function New: INotifyApi; 57 | private 58 | FResponse: TNotifyApiResponse; 59 | function Config(const AValue: INotifyConfig): INotifyApi; 60 | function ClearHeaders: INotifyApi; 61 | function ClearBody: INotifyApi; 62 | function ClearURLParameters: INotifyApi; 63 | function ClearEndPoint: INotifyApi; 64 | function AddHeader(const AName: String; AValue: String): INotifyApi; overload; 65 | function AddHeader(const AName: String; AValues: array of String): INotifyApi; overload; 66 | function AddBody(const AValue: String): INotifyApi; overload; 67 | function AddBody(const AValue: TFileStream): INotifyApi; overload; 68 | function AddEndPoint(const AValue: String): INotifyApi; overload; 69 | function AddURLParameter(const AName: String; AValue: String): INotifyApi; 70 | function Get: INotifyApi; 71 | function Post: INotifyApi; 72 | function Put: INotifyApi; 73 | function AbortStream: INotifyApi; 74 | function CreateURL: String; 75 | function Response: TNotifyApiResponse; 76 | procedure LogRequest; 77 | function Disconnect: INotifyApi; 78 | end; 79 | 80 | implementation 81 | 82 | uses 83 | IdURI, 84 | Notify.SmartPointer, 85 | System.SysUtils, 86 | System.StrUtils, 87 | System.Types, 88 | Notify.Logs, 89 | Notify.Notification.DTO, 90 | Notify.Error, 91 | Notify.Response.Data; 92 | 93 | { TNotityApiIndy } 94 | 95 | function TNotifyApiIndy.AddBody(const AValue: String): INotifyApi; 96 | var 97 | LBodyStream: TSmartPointer; 98 | begin 99 | Result := Self; 100 | LBodyStream := TStringStream.Create(AValue); 101 | FBodyStream.CopyFrom(LBodyStream.Value, LBodyStream.Value.Size); 102 | end; 103 | 104 | function TNotifyApiIndy.AbortStream: INotifyApi; 105 | begin 106 | if Assigned(FConnectionThread) then 107 | begin 108 | try 109 | if not FConnectionThread.Terminated then 110 | begin 111 | FConnectionThread.AbortStream; 112 | FConnectionThread.Terminate; 113 | FConnectionThread.WaitFor; 114 | end; 115 | finally 116 | FConnectionThread.Free; 117 | FConnectionThread := nil; 118 | end; 119 | end; 120 | end; 121 | 122 | function TNotifyApiIndy.AddBody(const AValue: TFileStream): INotifyApi; 123 | begin 124 | Result := Self; 125 | FBodyStream.CopyFrom(AValue, AValue.Size); 126 | end; 127 | 128 | function TNotifyApiIndy.AddEndPoint(const AValue: String): INotifyApi; 129 | begin 130 | Result := Self; 131 | FEndPoint := AValue; 132 | end; 133 | 134 | function TNotifyApiIndy.AddHeader(const AName: String; AValues: array of String): INotifyApi; 135 | var 136 | LString, LValue: String; 137 | begin 138 | Result := Self; 139 | for LString in AValues do 140 | if LValue = '' then 141 | LValue := LString 142 | else 143 | LValue := Format('%s, %s', [LValue, LString]); 144 | if LValue <> '' then 145 | begin 146 | FIdHTTP.Request.CustomHeaders.AddValue(AName, LValue); 147 | end; 148 | end; 149 | 150 | function TNotifyApiIndy.AddURLParameter(const AName: String; AValue: String): INotifyApi; 151 | var 152 | LParam: String; 153 | begin 154 | Result := Self; 155 | LParam := Format('%s=%s', [AName, AValue]); 156 | FURLParametersList.Add(LParam); 157 | end; 158 | 159 | function TNotifyApiIndy.AddHeader(const AName: String; AValue: String): INotifyApi; 160 | begin 161 | Result := Self; 162 | FIdHTTP.Request.CustomHeaders.AddValue(AName, AValue); 163 | end; 164 | 165 | function TNotifyApiIndy.ClearBody: INotifyApi; 166 | begin 167 | Result := Self; 168 | FBodyStream.Clear; 169 | end; 170 | 171 | function TNotifyApiIndy.ClearEndPoint: INotifyApi; 172 | begin 173 | Result := Self; 174 | FEndPoint := ''; 175 | end; 176 | 177 | function TNotifyApiIndy.ClearHeaders: INotifyApi; 178 | begin 179 | Result := Self; 180 | FIdHTTP.Request.CustomHeaders.Clear; 181 | end; 182 | 183 | function TNotifyApiIndy.ClearURLParameters: INotifyApi; 184 | begin 185 | Result := Self; 186 | FURLParametersList.Clear; 187 | end; 188 | 189 | function TNotifyApiIndy.Config(const AValue: INotifyConfig): INotifyApi; 190 | begin 191 | Result := Self; 192 | FNotifyConfig := AValue; 193 | 194 | if AValue.ProxyServer <> '' then 195 | begin 196 | FIdHttp.ProxyParams.ProxyServer := AValue.ProxyServer; 197 | FIdHttp.ProxyParams.ProxyUsername := AValue.ProxyUser; 198 | FIdHttp.ProxyParams.ProxyPassword := AValue.ProxyPassword; 199 | FIdHttp.ProxyParams.ProxyPort := AValue.ProxyPort; 200 | end; 201 | 202 | end; 203 | 204 | constructor TNotifyApiIndy.Create; 205 | begin 206 | FIdHTTP := TIdHTTP.Create(nil); 207 | FIOHandlerSSL := TIdSSLIOHandlerSocketOpenSSL.Create; 208 | FBodyStream := TMemoryStream.Create; 209 | FIOHandlerSSL.SSLOptions.Method := sslvTLSv1_2; 210 | FIdHTTP.IOHandler := FIOHandlerSSL; 211 | FIdHTTP.HTTPOptions := [hoNoReadMultipartMIME]; 212 | FURLParametersList := TStringList.Create; 213 | FResponse := TNotifyApiResponse.Create; 214 | end; 215 | 216 | function TNotifyApiIndy.CreateURL: String; 217 | var 218 | I: Integer; 219 | begin 220 | 221 | if FEndPoint <> '' then 222 | Result := Format('%s/%s', [FNotifyConfig.BaseURL, FEndPoint]) 223 | else 224 | Result := FNotifyConfig.BaseURL; 225 | 226 | if FURLParametersList.Count > 0 then 227 | begin 228 | Result := Result + '?'; 229 | for I := 0 to Pred(FURLParametersList.Count) do 230 | begin 231 | if I > 0 then 232 | Result := Result + '&'; 233 | Result := Result + FURLParametersList.Strings[I]; 234 | end; 235 | end; 236 | end; 237 | 238 | destructor TNotifyApiIndy.Destroy; 239 | begin 240 | try 241 | AbortStream; 242 | finally 243 | FResponse.Free; 244 | FIdHTTP.Free; 245 | FIOHandlerSSL.Free; 246 | FURLParametersList.Free; 247 | FBodyStream.Free; 248 | end; 249 | inherited; 250 | end; 251 | 252 | function TNotifyApiIndy.Disconnect: INotifyApi; 253 | begin 254 | Result := Self; 255 | FIdHTTP.Disconnect; 256 | end; 257 | 258 | function TNotifyApiIndy.Get: INotifyApi; 259 | begin 260 | Result := Self; 261 | try 262 | if Assigned(FConnectionThread) then 263 | AbortStream; 264 | finally 265 | FConnectionThread := TSSEThread.Create(TIdURI.URLEncode(CreateURL), FIdHTTP, FResponse, FNotifyConfig); 266 | FConnectionThread.Start; 267 | end; 268 | 269 | {$IFDEF CONSOLE OR CONSOLE_TESTRUNNER} 270 | FConnectionThread.WaitFor; 271 | {$ENDIF} 272 | 273 | end; 274 | 275 | procedure TNotifyApiIndy.LogRequest; 276 | begin 277 | if FNotifyConfig.SaveLog then 278 | begin 279 | TNotifyLogs.Log(FNotifyConfig.LogPath, FIdHTTP.URL.GetFullURI()); 280 | TNotifyLogs.Log(FNotifyConfig.LogPath, FBodyStream); 281 | TNotifyLogs.Log(FNotifyConfig.LogPath, FResponse.StatusCode.ToString); 282 | TNotifyLogs.Log(FNotifyConfig.LogPath, FResponse.ResponseStream); 283 | end; 284 | end; 285 | 286 | class function TNotifyApiIndy.New: INotifyApi; 287 | begin 288 | Result := Self.Create; 289 | end; 290 | 291 | function TNotifyApiIndy.Post: INotifyApi; 292 | begin 293 | Result := Self; 294 | try 295 | FIdHTTP.Post(TIdURI.URLEncode(CreateURL), FBodyStream, FResponse.ResponseStream); 296 | finally 297 | FResponse.StatusCode := FIdHttp.ResponseCode; 298 | LogRequest; 299 | end; 300 | end; 301 | 302 | function TNotifyApiIndy.Put: INotifyApi; 303 | begin 304 | Result := Self; 305 | try 306 | FIdHTTP.Put(TIdURI.URLEncode(CreateURL), FBodyStream, FResponse.ResponseStream); 307 | finally 308 | FResponse.StatusCode := FIdHttp.ResponseCode; 309 | LogRequest; 310 | end; 311 | end; 312 | 313 | function TNotifyApiIndy.Response: TNotifyApiResponse; 314 | begin 315 | Result := FResponse; 316 | end; 317 | 318 | { TSSEThread } 319 | 320 | procedure TSSEThread.AbortStream; 321 | var 322 | Cmd: String; 323 | begin 324 | try 325 | Cmd := FCloseConnectionMessage; 326 | FIdEventStream.WriteData(ToBytes(Cmd), Length(Cmd)); 327 | FIdEventStream.Position := 0; 328 | DoOnWork(Self, wmRead, 1000); 329 | except on E: Exception do 330 | begin 331 | Exit; 332 | end; 333 | end; 334 | end; 335 | 336 | constructor TSSEThread.Create(AUrl: String; var AIdHttp: TIdHTTP; var AResponse: TNotifyApiResponse; var AConfig: INotifyConfig); 337 | begin 338 | inherited Create(True); 339 | FreeOnTerminate := False; 340 | FUrl := AUrl; 341 | FIdHttp := AIdHttp; 342 | FResponse := AResponse; 343 | FConfig := AConfig; 344 | end; 345 | 346 | destructor TSSEThread.Destroy; 347 | begin 348 | inherited; 349 | end; 350 | 351 | procedure TSSEThread.DoOnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 352 | var 353 | LEventString: UTF8String; 354 | LString: String; 355 | {$IFDEF VER310} 356 | LStrings: TStringDynArray; 357 | {$ELSE} 358 | LStrings: TArray; 359 | {$ENDIF} 360 | begin 361 | 362 | if Terminated then 363 | Exit; 364 | 365 | FIdEventStream.Position := 0; 366 | SetString(LEventString, PAnsiChar(FIdEventStream.Memory), FIdEventStream.Size); 367 | 368 | if LEventString = '' then 369 | Exit; 370 | 371 | if LEventString = FCloseConnectionMessage then 372 | FIdHttp.Socket.Close; 373 | 374 | LStrings := SplitString(UTF8ToString(LEventString), #$A); 375 | 376 | for LString in LStrings do 377 | {$IF DEFINED(ANDROID)} 378 | NxHorizon.Instance.Send(Utf8String(LString), Sync); 379 | {$ELSE} 380 | NxHorizon.Instance.Post(Utf8String(LString)); 381 | {$ENDIF} 382 | 383 | end; 384 | 385 | procedure TSSEThread.Execute; 386 | begin 387 | inherited; 388 | FIdEventStream := TMemoryStream.Create; 389 | try 390 | FIdHttp.OnWork := DoOnWork; 391 | FIdHttp.Request.CacheControl := 'no-cache'; 392 | FIdHttp.Request.Accept := 'text/event-stream'; 393 | FIdHttp.Response.KeepAlive := True; 394 | 395 | while not Terminated do 396 | begin 397 | try 398 | try 399 | FIdHttp.Get(FUrl, FIdEventStream); 400 | {$IF DEFINED(CONSOLE)} 401 | if FConfig.SaveLog then 402 | WriteLn(DateTimeToStr(Now) + ' Exiting Get HTTP Call'); 403 | {$IFEND} 404 | 405 | if not FConfig.Poll then 406 | TThread.Sleep(10000); 407 | 408 | finally 409 | if FConfig.Poll then 410 | Terminate; 411 | end; 412 | except on E: Exception do 413 | begin 414 | if FConfig.Poll then 415 | Exit; 416 | end; 417 | end; 418 | end; 419 | 420 | finally 421 | FIdEventStream.Free; 422 | end; 423 | end; 424 | 425 | end. 426 | -------------------------------------------------------------------------------- /src/Notify.Api.Response.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Api.Response; 2 | 3 | interface 4 | 5 | /// 6 | /// Response is only used for publishing 7 | /// 8 | 9 | uses 10 | Notify.Error, 11 | Notify.Response.Data, 12 | Notify.Notification.DTO, 13 | System.Classes; 14 | 15 | type 16 | 17 | {$M+} 18 | 19 | TNotifyApiResponse = class 20 | private 21 | {$IFDEF WIN32} 22 | FStatusCode: Integer; 23 | {$ELSE} 24 | FStatusCode: Int64; 25 | {$ENDIF} 26 | FResponseStream: TMemoryStream; 27 | FResponseData: TNotifyResponseDTO; 28 | FResponseErrors: TNotifyErrors; 29 | function GetErros: TNotifyErrors; 30 | function GetData: TNotifyResponseDTO; 31 | public 32 | constructor Create; 33 | destructor Destroy; override; 34 | published 35 | property Erros: TNotifyErrors read GetErros; 36 | property Data: TNotifyResponseDTO read GetData; 37 | property {$IFDEF WIN32} StatusCode: Integer {$ELSE} StatusCode: Int64 {$ENDIF} read FStatusCode write FStatusCode; 38 | property ResponseStream: TMemoryStream read FResponseStream write FResponseStream; 39 | end; 40 | 41 | implementation 42 | 43 | uses 44 | System.SysUtils; 45 | 46 | { TNotifyApiResponse } 47 | 48 | constructor TNotifyApiResponse.Create; 49 | begin 50 | FResponseData := TNotifyResponseDTO.Create; 51 | FResponseErrors := TNotifyErrors.Create; 52 | FResponseStream := TMemoryStream.Create 53 | end; 54 | 55 | destructor TNotifyApiResponse.Destroy; 56 | begin 57 | FreeAndNil(FResponseData); 58 | FreeAndNil(FResponseErrors); 59 | FreeAndNil(FResponseStream); 60 | inherited 61 | end; 62 | 63 | function TNotifyApiResponse.GetData: TNotifyResponseDTO; 64 | var 65 | LRawString: String; 66 | begin 67 | Result := FResponseData; 68 | 69 | if not Assigned(FResponseData) then 70 | Exit; 71 | 72 | FResponseStream.Position := 0; 73 | SetString(LRawString, PAnsiChar(FResponseStream.Memory), FResponseStream.Size); 74 | FResponseData.AsJson := LRawString; 75 | end; 76 | 77 | function TNotifyApiResponse.GetErros: TNotifyErrors; 78 | var 79 | LRawString: String; 80 | begin 81 | Result := FResponseErrors; 82 | 83 | if not Assigned(FResponseData) then 84 | Exit; 85 | 86 | FResponseStream.Position := 0; 87 | SetString(LRawString, PAnsiChar(FResponseStream.Memory), FResponseStream.Size); 88 | FResponseErrors.AsJson := LRawString; 89 | end; 90 | 91 | end. 92 | -------------------------------------------------------------------------------- /src/Notify.Attachment.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Attachment.Contract; 2 | 3 | interface 4 | 5 | type 6 | INotifyAttachment = interface 7 | ['{2F96DFC0-08A4-4096-BF48-578E4B6EB327}'] 8 | function Name: String; overload; 9 | function Name(const AValue: String): INotifyAttachment; overload; 10 | function Url: String; overload; 11 | function Url(const AValue:String): INotifyAttachment; overload; 12 | function MimeType: String; overload; 13 | function MimeType(const AValue: String): INotifyAttachment; overload; 14 | function Size: Integer; overload; 15 | function Size(const AValue: Integer): INotifyAttachment; overload; 16 | function Expires: Integer; overload; 17 | function Expires(const AValue: Integer): INotifyAttachment; overload; 18 | end; 19 | 20 | implementation 21 | 22 | end. 23 | -------------------------------------------------------------------------------- /src/Notify.Attachment.DTO.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Attachment.DTO; 2 | 3 | interface 4 | 5 | uses 6 | Rest.Json.Types, 7 | Notify.JSON.Parser; 8 | 9 | type 10 | TNotifyAttachmentDTO = class(TJsonDTO) 11 | private 12 | [JSONName('name')] 13 | FName: String; 14 | [JSONName('url')] 15 | FUrl: String; 16 | [JSONName('type')] 17 | FMimeType: String; 18 | [JSONName('size')] 19 | FSize: Integer; 20 | [JSONName('expires')] 21 | FExpires: Integer; 22 | published 23 | property Name: String read FName write FName; 24 | property Url: String read FUrl write FUrl; 25 | property MimeType: String read FMimeType write FMimeType; 26 | property Size: Integer read FSize write FSize; 27 | property Expires: Integer read FExpires write FExpires; 28 | end; 29 | 30 | 31 | implementation 32 | 33 | end. 34 | -------------------------------------------------------------------------------- /src/Notify.Attachment.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Attachment; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Attachment.Contract; 7 | 8 | type 9 | TNotifyAttachment = class(TInterfacedObject, INotifyAttachment) 10 | strict private 11 | FName: String; 12 | FUrl: String; 13 | FMimeType: String; 14 | FSize: Integer; 15 | FExpires: Integer; 16 | public 17 | class function New: INotifyAttachment; 18 | private 19 | function Name: String; overload; 20 | function Name(const AValue: String): INotifyAttachment; overload; 21 | function Url: String; overload; 22 | function Url(const AValue:String): INotifyAttachment; overload; 23 | function MimeType: String; overload; 24 | function MimeType(const AValue: String): INotifyAttachment; overload; 25 | function Size: Integer; overload; 26 | function Size(const AValue: Integer): INotifyAttachment; overload; 27 | function Expires: Integer; overload; 28 | function Expires(const AValue: Integer): INotifyAttachment; overload; 29 | end; 30 | 31 | implementation 32 | 33 | { TNotifyAttachment } 34 | 35 | function TNotifyAttachment.Expires: Integer; 36 | begin 37 | Result := FExpires; 38 | end; 39 | 40 | function TNotifyAttachment.Expires(const AValue: Integer): INotifyAttachment; 41 | begin 42 | Result := Self; 43 | FExpires := AValue; 44 | end; 45 | 46 | function TNotifyAttachment.MimeType: String; 47 | begin 48 | Result := FMimeType; 49 | end; 50 | 51 | function TNotifyAttachment.MimeType(const AValue: String): INotifyAttachment; 52 | begin 53 | Result := Self; 54 | FMimeType := AValue; 55 | end; 56 | 57 | function TNotifyAttachment.Name(const AValue: String): INotifyAttachment; 58 | begin 59 | Result := Self; 60 | FName := AValue; 61 | end; 62 | 63 | function TNotifyAttachment.Name: String; 64 | begin 65 | Result := FName; 66 | end; 67 | 68 | class function TNotifyAttachment.New: INotifyAttachment; 69 | begin 70 | Result := Self.Create; 71 | end; 72 | 73 | function TNotifyAttachment.Size: Integer; 74 | begin 75 | Result := FSize; 76 | end; 77 | 78 | function TNotifyAttachment.Size(const AValue: Integer): INotifyAttachment; 79 | begin 80 | Result := Self; 81 | FSize := AValue; 82 | end; 83 | 84 | function TNotifyAttachment.Url: String; 85 | begin 86 | Result := FUrl; 87 | end; 88 | 89 | function TNotifyAttachment.Url(const AValue: String): INotifyAttachment; 90 | begin 91 | Result := Self; 92 | FUrl := AValue; 93 | end; 94 | 95 | end. 96 | -------------------------------------------------------------------------------- /src/Notify.Config.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Config.Contract; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types; 7 | 8 | type 9 | INotifyConfig = interface 10 | ['{8CFCA0D0-3637-4367-9F56-B420D5441659}'] 11 | function BaseURL: String; overload; 12 | function BaseURL(const AValue: String): INotifyConfig; overload; 13 | function UserName: String; overload; 14 | function UserName(const AValue: String): INotifyConfig; overload; 15 | function Password: String; overload; 16 | function Password(const AValue: String): INotifyConfig; overload; 17 | function Cache: Boolean; overload; 18 | function Cache(const AValue: Boolean): INotifyConfig; overload; 19 | function DisableFireBase: Boolean; overload; 20 | function DisableFireBase(const AValue: Boolean): INotifyConfig; overload; 21 | function SaveLog: Boolean; overload; 22 | function SaveLog(const AValue: Boolean): INotifyConfig; overload; 23 | function LogPath: String; overload; 24 | function LogPath(const AValue: String): INotifyConfig; overload; 25 | function SubscriptionType: TNotifySubscriptionType; overload; 26 | function SubscriptionType(const AValue: TNotifySubscriptionType): INotifyConfig; overload; 27 | function ProxyServer: string; overload; 28 | function ProxyUser: string; overload; 29 | function ProxyPassword: string; overload; 30 | function ProxyPort: integer; overload; 31 | function Proxy(const aProxyServer, aProxyUser, aProxyPassword: string; const aProxyPort: integer): INotifyConfig; overload; 32 | function Poll: Boolean; overload; 33 | function Poll(const AValue: Boolean): INotifyConfig; overload; 34 | function Since: String; overload; 35 | function Since(const AValue: String): INotifyConfig; overload; 36 | function Scheduled: Boolean; overload; 37 | function Scheduled(const AValue: Boolean): INotifyConfig; overload; 38 | end; 39 | 40 | implementation 41 | 42 | end. 43 | -------------------------------------------------------------------------------- /src/Notify.Config.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Config; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Config.Contract; 8 | 9 | type 10 | TNotifyConfig = class sealed(TInterfacedObject, INotifyConfig) 11 | strict private 12 | FBaseURL: String; 13 | FUserName: String; 14 | FPassword: String; 15 | FCache: Boolean; 16 | FDisableFirebaseFCM: Boolean; 17 | FSaveLog: Boolean; 18 | FLogPath: String; 19 | FSubscriptionType: TNotifySubscriptionType; 20 | FProxyServer, FProxyUser, FProxyPassword: string; 21 | FProxyPort: integer; 22 | FPoll: Boolean; 23 | FSince: String; 24 | FScheduled: Boolean; 25 | public 26 | class function New: INotifyConfig; 27 | private 28 | constructor Create; 29 | function BaseURL: String; overload; 30 | function BaseURL(const AValue: String): INotifyConfig; overload; 31 | function UserName: String; overload; 32 | function UserName(const AValue: String): INotifyConfig; overload; 33 | function Password: String; overload; 34 | function Password(const AValue: String): INotifyConfig; overload; 35 | function Cache: Boolean; overload; 36 | function Cache(const AValue: Boolean): INotifyConfig; overload; 37 | function DisableFireBase: Boolean; overload; 38 | function DisableFireBase(const AValue: Boolean): INotifyConfig; overload; 39 | function SaveLog: Boolean; overload; 40 | function SaveLog(const AValue: Boolean): INotifyConfig; overload; 41 | function LogPath: String; overload; 42 | function LogPath(const AValue: String): INotifyConfig; overload; 43 | function SubscriptionType: TNotifySubscriptionType; overload; 44 | function SubscriptionType(const AValue: TNotifySubscriptionType): INotifyConfig; overload; 45 | function ProxyServer: string; overload; 46 | function ProxyUser: string; overload; 47 | function ProxyPassword: string; overload; 48 | function ProxyPort: integer; overload; 49 | function Proxy(const aProxyServer, aProxyUser, aProxyPassword: string; const aProxyPort: integer): INotifyConfig; overload; 50 | function Poll: Boolean; overload; 51 | function Poll(const AValue: Boolean): INotifyConfig; overload; 52 | function Since: String; overload; 53 | function Since(const AValue: String): INotifyConfig; overload; 54 | function Scheduled: Boolean; overload; 55 | function Scheduled(const AValue: Boolean): INotifyConfig; overload; 56 | end; 57 | 58 | implementation 59 | 60 | uses 61 | System.SysUtils; 62 | 63 | { TNotifyConfig } 64 | 65 | function TNotifyConfig.BaseURL(const AValue: String): INotifyConfig; 66 | begin 67 | Result := Self; 68 | FBaseURL := AValue; 69 | end; 70 | 71 | function TNotifyConfig.Cache(const AValue: Boolean): INotifyConfig; 72 | begin 73 | Result := Self; 74 | FCache := AValue; 75 | end; 76 | 77 | constructor TNotifyConfig.Create; 78 | begin 79 | FBaseURL := 'https://ntfy.sh'; 80 | FCache := True; 81 | FLogPath := ExtractFilePath(ParamStr(0)); 82 | FSubscriptionType := TNotifySubscriptionType.JSON; 83 | end; 84 | 85 | function TNotifyConfig.DisableFireBase(const AValue: Boolean): INotifyConfig; 86 | begin 87 | Result := Self; 88 | FDisableFirebaseFCM := AValue; 89 | end; 90 | 91 | function TNotifyConfig.LogPath(const AValue: String): INotifyConfig; 92 | begin 93 | Result := Self; 94 | FLogPath := AValue; 95 | end; 96 | 97 | function TNotifyConfig.LogPath: String; 98 | begin 99 | Result := FLogPath; 100 | end; 101 | 102 | function TNotifyConfig.DisableFireBase: Boolean; 103 | begin 104 | Result := FDisableFirebaseFCM; 105 | end; 106 | 107 | function TNotifyConfig.Cache: Boolean; 108 | begin 109 | Result := FCache; 110 | end; 111 | 112 | function TNotifyConfig.BaseURL: String; 113 | begin 114 | Result := FBaseURL; 115 | end; 116 | 117 | class function TNotifyConfig.New: INotifyConfig; 118 | begin 119 | Result := Self.Create; 120 | end; 121 | 122 | function TNotifyConfig.Password: String; 123 | begin 124 | Result := FPassword; 125 | end; 126 | 127 | function TNotifyConfig.Password(const AValue: String): INotifyConfig; 128 | begin 129 | Result := Self; 130 | FPassword := AValue; 131 | end; 132 | 133 | function TNotifyConfig.Poll: Boolean; 134 | begin 135 | Result := FPoll; 136 | end; 137 | 138 | function TNotifyConfig.Poll(const AValue: Boolean): INotifyConfig; 139 | begin 140 | Result := Self; 141 | FPoll := AValue; 142 | end; 143 | 144 | function TNotifyConfig.Proxy(const AProxyServer, AProxyUser, AProxyPassword: string; const aProxyPort: integer): INotifyConfig; 145 | begin 146 | FProxyServer := AProxyServer; 147 | FProxyUser := AProxyUser; 148 | FProxyPassword := AProxyPassword; 149 | FProxyPort := AProxyPort; 150 | end; 151 | 152 | function TNotifyConfig.ProxyPassword: string; 153 | begin 154 | Result := FProxyPassword; 155 | end; 156 | 157 | function TNotifyConfig.ProxyPort: integer; 158 | begin 159 | Result := FProxyPort; 160 | end; 161 | 162 | function TNotifyConfig.ProxyServer: string; 163 | begin 164 | Result := FProxyServer; 165 | end; 166 | 167 | function TNotifyConfig.ProxyUser: string; 168 | begin 169 | Result := FProxyUser; 170 | end; 171 | 172 | function TNotifyConfig.SaveLog(const AValue: Boolean): INotifyConfig; 173 | begin 174 | Result := Self; 175 | FSaveLog := AValue; 176 | end; 177 | 178 | function TNotifyConfig.Scheduled(const AValue: Boolean): INotifyConfig; 179 | begin 180 | Result := Self; 181 | FScheduled := AValue; 182 | end; 183 | 184 | function TNotifyConfig.Scheduled: Boolean; 185 | begin 186 | Result := FScheduled; 187 | end; 188 | 189 | function TNotifyConfig.Since: String; 190 | begin 191 | Result := FSince; 192 | end; 193 | 194 | function TNotifyConfig.Since(const AValue: String): INotifyConfig; 195 | begin 196 | Result := Self; 197 | FSince := AValue; 198 | end; 199 | 200 | function TNotifyConfig.SubscriptionType(const AValue: TNotifySubscriptionType): INotifyConfig; 201 | begin 202 | Result := Self; 203 | FSubscriptionType := AValue; 204 | 205 | if AValue in [TNotifySubscriptionType.WEB_SOCKET] then 206 | FBaseURL := StringReplace(FBaseURL, 'https', 'wss', [rfReplaceAll]); 207 | end; 208 | 209 | function TNotifyConfig.SubscriptionType: TNotifySubscriptionType; 210 | begin 211 | Result := FSubscriptionType; 212 | end; 213 | 214 | function TNotifyConfig.SaveLog: Boolean; 215 | begin 216 | Result := FSaveLog; 217 | end; 218 | 219 | function TNotifyConfig.UserName: String; 220 | begin 221 | Result := FUserName; 222 | end; 223 | 224 | function TNotifyConfig.UserName(const AValue: String): INotifyConfig; 225 | begin 226 | Result := Self; 227 | FUserName := AValue; 228 | end; 229 | 230 | end. 231 | -------------------------------------------------------------------------------- /src/Notify.Core.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Core.Contract; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | Notify.Types, 8 | Notify.Api.Contract, 9 | Notify.Notification.Contract, 10 | Notify.Config.Contract, 11 | Notify.Action.Contract, 12 | Notify.Event.Contract, 13 | Notify.Attachment.Contract, 14 | Notify.Api.Response, 15 | System.Generics.Collections; 16 | 17 | type 18 | INotifyCore = interface 19 | ['{AEDB3C31-D45F-4469-9427-9CEA5427A4E3}'] 20 | function Cache(const AValue: Boolean): INotifyCore; 21 | function UserName(const AValue: String): INotifyCore; 22 | function Password(const AValue: String): INotifyCore; 23 | function SaveLog(const AValue: Boolean): INotifyCore; 24 | function LogPath(const AValue: String): INotifyCore; 25 | function BaseURL(const AValue: String): INotifyCore; 26 | function SubscriptionType(const AValue: TNotifySubscriptionType): INotifyCore; 27 | function Topic(const AValue: String): INotifyCore; 28 | function DisableFireBase(const AValue: Boolean): INotifyCore; 29 | function Publish: INotifyCore; 30 | function Unsubscribe: INotifyCore; 31 | function Notification(const ANotification: INotifyNotification): INotifyCore; overload; 32 | procedure Subscribe(const ATopic: String; const ACallBack: TNotifyEventProc); overload; 33 | function Filter(const AFilterType: TNotifyFilter; const AValue: String): INotifyCore; 34 | function ClearFilters: INotifyCore; 35 | function Poll(const AValue: Boolean): INotifyCore; overload; 36 | function Since(const AValue: String): INotifyCore; overload; 37 | function Scheduled(const AValue: Boolean): INotifyCore; overload; 38 | function Response: TNotifyApiResponse; 39 | function Disconnect: INotifyCore; 40 | function Proxy(const AProxyServer, AProxyUser, AProxyPassword: String; const AProxyPort: Integer): INotifyCore; overload; 41 | end; 42 | 43 | INotifyCoreFacade = interface 44 | ['{47D19A0C-9C4C-4389-B2CD-CE4515BB4F35}'] 45 | function Api: INotifyApi; 46 | function Notification: INotifyNotification; 47 | function Notify: INotifyCore; 48 | function Action: INotifyAction; 49 | function Config: INotifyConfig; 50 | function Event: INotifyEvent; 51 | function Attachment: INotifyAttachment; 52 | end; 53 | 54 | INotifyParametersFilters = TDictionary; 55 | 56 | implementation 57 | 58 | end. 59 | -------------------------------------------------------------------------------- /src/Notify.Custom.Types.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Custom.Types; 2 | 3 | interface 4 | 5 | uses 6 | REST.Json.Types; 7 | 8 | /// Custom types defined by the user. You can modify the classes members 9 | /// on this unit adjusting it for your needs. Do not rename the class 10 | /// declarations because it's being used underneath the library. You only need 11 | /// to rename fields/properties. 12 | 13 | type 14 | 15 | {$M+} 16 | 17 | TNotifyActionHeaders = class 18 | private 19 | [JSONName('cmd')] 20 | FCmd: String; 21 | [JSONName('parameter')] 22 | FParameter: String; 23 | [JSONName('systemdate')] 24 | FSystemDate: String; 25 | [JSONName('auth')] 26 | FAuth: String; 27 | published 28 | property Cmd: String read FCmd write FCmd; 29 | property Parameter: String read FParameter write FParameter; 30 | property SystemDate: String read FSystemDate write FSystemDate; 31 | property Auth: String read FAuth write FAuth; 32 | end; 33 | 34 | implementation 35 | 36 | end. 37 | -------------------------------------------------------------------------------- /src/Notify.Error.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Error; 2 | 3 | interface 4 | 5 | uses 6 | Notify.JSON.Parser, REST.Json.Types; 7 | 8 | type 9 | TNotifyErrors = class(TJsonDTO) 10 | private 11 | [JSONName('code')] 12 | FCode: Integer; 13 | [JSONName('http')] 14 | FHttp: Integer; 15 | [JSONName('error')] 16 | FError: String; 17 | published 18 | property Code: Integer read FCode write FCode; 19 | property Http: Integer read FHttp write FHttp; 20 | property Error: String read FError write FError; 21 | end; 22 | 23 | implementation 24 | 25 | end. 26 | -------------------------------------------------------------------------------- /src/Notify.Event.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Event.Contract; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | Notify.Types, 8 | Notify.Action.Contract, 9 | Notify.Attachment.Contract, 10 | System.Generics.Collections; 11 | 12 | type 13 | 14 | INotifyEventActions = TDictionary; 15 | 16 | INotifyEvent = interface 17 | ['{0342B585-BB88-4A63-9C41-8848B90B6042}'] 18 | function Id: String; overload; 19 | function Id(const AValue: String): INotifyEvent; overload; 20 | function Time: Integer; overload; 21 | function Time(const AValue: Integer): INotifyEvent; overload; 22 | function Event: String; overload; 23 | function Event(const AValue: String): INotifyEvent; overload; 24 | function Topic: String; overload; 25 | function Topic(const AValue: String): INotifyEvent; overload; 26 | function Tags: TArray; overload; 27 | function Tags(const AValue: TArray): INotifyEvent; overload; 28 | function Click: String; overload; 29 | function Click(const AValue: String): INotifyEvent; overload; 30 | function Title: String; overload; 31 | function Title(const AValue: String): INotifyEvent; overload; 32 | function MessageContent: String; overload; 33 | function MessageContent(const AValue: String): INotifyEvent; overload; 34 | function Priority: TNotifyPriority; overload; 35 | function Priority(const AValue: TNotifyPriority): INotifyEvent; overload; 36 | function Action: INotifyAction; overload; 37 | function Action(const AValue: INotifyAction): INotifyEvent; overload; 38 | function Actions: INotifyEventActions; 39 | function Attachment: INotifyAttachment; overload; 40 | function Attachment(const AValue: INotifyAttachment): INotifyEvent; overload; 41 | function Icon: String; overload; 42 | function Icon(const AValue: String): INotifyEvent; overload; 43 | end; 44 | 45 | TNotifyEventProc = TProc; 46 | 47 | implementation 48 | 49 | end. 50 | -------------------------------------------------------------------------------- /src/Notify.Event.DTO.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Event.DTO; 2 | 3 | interface 4 | 5 | uses 6 | Rest.Json.Types, 7 | Notify.JSON.Parser, 8 | Notify.Action.DTO, 9 | Notify.Attachment.DTO, 10 | System.Generics.Collections; 11 | 12 | type 13 | TNotifyEventDTO = class(TJsonDTO) 14 | private 15 | [JSONName('id')] 16 | FId: String; 17 | [JSONName('time')] 18 | FTime: Integer; 19 | [JSONName('event')] 20 | FEvent: String; 21 | [JSONName('topic')] 22 | FTopic: String; 23 | [JSONName('priority')] 24 | FPriority: Integer; 25 | [JSONName('click')] 26 | FClick: String; 27 | [JSONName('title')] 28 | FTitle: String; 29 | [JSONName('message')] 30 | FMessage: String; 31 | [JSONName('icon')] 32 | FIcon: String; 33 | [JSONName('tags')] 34 | FTagsArray: TArray; 35 | [JSONMarshalled(False)] 36 | FTags: TList; 37 | [GenericListReflect] 38 | FActions: TObjectList; 39 | [JSONName('actions'), JSONMarshalled(False)] 40 | FActionsArray: TArray; 41 | [JSONName('attachment')] 42 | FAttachment: TNotifyAttachmentDTO; 43 | function GetTags: TList; 44 | function GetActions: TObjectList; 45 | protected 46 | function GetAsJson: string; override; 47 | published 48 | property Id: String read FId write FId; 49 | property Time: Integer read FTime write FTime; 50 | property Event: String read FEvent write FEvent; 51 | property Topic: String read FTopic write FTopic; 52 | property Priority: Integer read FPriority write FPriority; 53 | property Click: String read FClick write FClick; 54 | property Title: String read FTitle write FTitle; 55 | property Message: String read FMessage write FMessage; 56 | property Tags: TList read GetTags; 57 | property Actions: TObjectList read GetActions; 58 | property Attachment: TNotifyAttachmentDTO read FAttachment write FAttachment; 59 | property Icon: String read FIcon write FIcon; 60 | public 61 | constructor Create; override; 62 | destructor Destroy; override; 63 | end; 64 | 65 | 66 | implementation 67 | 68 | { TNotifyEventDTO } 69 | 70 | constructor TNotifyEventDTO.Create; 71 | begin 72 | inherited; 73 | end; 74 | 75 | destructor TNotifyEventDTO.Destroy; 76 | begin 77 | GetTags.Free; 78 | GetActions.Free; 79 | inherited; 80 | end; 81 | 82 | function TNotifyEventDTO.GetActions: TObjectList; 83 | begin 84 | Result := ObjectList(FActions, FActionsArray); 85 | end; 86 | 87 | function TNotifyEventDTO.GetAsJson: string; 88 | begin 89 | RefreshArray(FTags, FTagsArray); 90 | RefreshArray(FActions, FActionsArray); 91 | Result := inherited; 92 | end; 93 | 94 | function TNotifyEventDTO.GetTags: TList; 95 | begin 96 | Result := List(FTags, FTagsArray); 97 | end; 98 | 99 | end. 100 | -------------------------------------------------------------------------------- /src/Notify.Event.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Event; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Event.Contract, 8 | Notify.Action.Contract, 9 | Notify.Attachment.Contract; 10 | 11 | type 12 | TNotifyEvent = class(TInterfacedObject, INotifyEvent) 13 | private 14 | FId: String; 15 | FTime: Integer; 16 | FEvent: String; 17 | FTopic: String; 18 | FTags: TArray; 19 | FClick: String; 20 | FTitle: String; 21 | FMessage: String; 22 | FPriority: TNotifyPriority; 23 | FAction: INotifyAction; 24 | FActions: INotifyEventActions; 25 | FAttachment: INotifyAttachment; 26 | FIcon: String; 27 | public 28 | class function New: INotifyEvent; 29 | constructor Create; 30 | destructor Destroy; override; 31 | function Id: String; overload; 32 | function Id(const AValue: String): INotifyEvent; overload; 33 | function Time: Integer; overload; 34 | function Time(const AValue: Integer): INotifyEvent; overload; 35 | function Event: String; overload; 36 | function Event(const AValue: String): INotifyEvent; overload; 37 | function Topic: String; overload; 38 | function Topic(const AValue: String): INotifyEvent; overload; 39 | function Tags: TArray; overload; 40 | function Tags(const AValue: TArray): INotifyEvent; overload; 41 | function Click: String; overload; 42 | function Click(const AValue: String): INotifyEvent; overload; 43 | function Title: String; overload; 44 | function Title(const AValue: String): INotifyEvent; overload; 45 | function MessageContent: String; overload; 46 | function MessageContent(const AValue: String): INotifyEvent; overload; 47 | function Priority: TNotifyPriority; overload; 48 | function Priority(const AValue: TNotifyPriority): INotifyEvent; overload; 49 | function Action: INotifyAction; overload; 50 | function Action(const AValue: INotifyAction): INotifyEvent; overload; 51 | function Actions: INotifyEventActions; 52 | function Attachment: INotifyAttachment; overload; 53 | function Attachment(const AValue: INotifyAttachment): INotifyEvent; overload; 54 | function Icon: String; overload; 55 | function Icon(const AValue: String): INotifyEvent; overload; 56 | end; 57 | 58 | implementation 59 | 60 | uses 61 | Notify.Facade, 62 | System.Generics.Collections; 63 | 64 | { TNotifySubscription } 65 | 66 | function TNotifyEvent.Action(const AValue: INotifyAction): INotifyEvent; 67 | begin 68 | Result := Self; 69 | FAction := AValue; 70 | 71 | if FActions.ContainsKey(AValue.&Label) then 72 | FActions.Remove(AValue.&Label); 73 | 74 | if FActions.Count >= 3 then 75 | Exit; 76 | 77 | FActions.Add(AValue.&Label, AValue); 78 | end; 79 | 80 | function TNotifyEvent.Actions: INotifyEventActions; 81 | begin 82 | Result := FActions; 83 | end; 84 | 85 | function TNotifyEvent.Attachment(const AValue: INotifyAttachment): INotifyEvent; 86 | begin 87 | Result := Self; 88 | FAttachment := AValue; 89 | end; 90 | 91 | function TNotifyEvent.Attachment: INotifyAttachment; 92 | begin 93 | Result := FAttachment; 94 | end; 95 | 96 | function TNotifyEvent.Action: INotifyAction; 97 | begin 98 | Result := FAction; 99 | end; 100 | 101 | function TNotifyEvent.Click(const AValue: String): INotifyEvent; 102 | begin 103 | Result := Self; 104 | FClick := AValue; 105 | end; 106 | 107 | constructor TNotifyEvent.Create; 108 | begin 109 | FActions := TDictionary.Create(); 110 | FAttachment := TNotifyCoreFacade.New.Attachment; 111 | end; 112 | 113 | destructor TNotifyEvent.Destroy; 114 | begin 115 | FActions.Free; 116 | inherited; 117 | end; 118 | 119 | function TNotifyEvent.Click: String; 120 | begin 121 | Result := FClick; 122 | end; 123 | 124 | function TNotifyEvent.Event: String; 125 | begin 126 | Result := FEvent; 127 | end; 128 | 129 | function TNotifyEvent.Event(const AValue: String): INotifyEvent; 130 | begin 131 | Result := Self; 132 | FEvent := AValue; 133 | end; 134 | 135 | function TNotifyEvent.Icon(const AValue: String): INotifyEvent; 136 | begin 137 | Result := Self; 138 | FIcon := AValue; 139 | end; 140 | 141 | function TNotifyEvent.Icon: String; 142 | begin 143 | Result := FIcon; 144 | end; 145 | 146 | function TNotifyEvent.Id(const AValue: String): INotifyEvent; 147 | begin 148 | Result := Self; 149 | FId := AValue; 150 | end; 151 | 152 | function TNotifyEvent.Id: String; 153 | begin 154 | Result := FId; 155 | end; 156 | 157 | function TNotifyEvent.MessageContent: String; 158 | begin 159 | Result := FMessage; 160 | end; 161 | 162 | function TNotifyEvent.MessageContent(const AValue: String): INotifyEvent; 163 | begin 164 | Result := Self; 165 | FMessage := AValue; 166 | end; 167 | 168 | class function TNotifyEvent.New: INotifyEvent; 169 | begin 170 | Result := Self.Create; 171 | end; 172 | 173 | function TNotifyEvent.Priority(const AValue: TNotifyPriority): INotifyEvent; 174 | begin 175 | Result := Self; 176 | FPriority := AValue; 177 | end; 178 | 179 | function TNotifyEvent.Priority: TNotifyPriority; 180 | begin 181 | Result := FPriority; 182 | end; 183 | 184 | function TNotifyEvent.Tags(const AValue: TArray): INotifyEvent; 185 | begin 186 | Result := Self; 187 | FTags := AValue; 188 | end; 189 | 190 | function TNotifyEvent.Tags: TArray; 191 | begin 192 | Result := FTags; 193 | end; 194 | 195 | function TNotifyEvent.Time: Integer; 196 | begin 197 | Result := FTime; 198 | end; 199 | 200 | function TNotifyEvent.Time(const AValue: Integer): INotifyEvent; 201 | begin 202 | Result := Self; 203 | FTime := AValue; 204 | end; 205 | 206 | function TNotifyEvent.Title(const AValue: String): INotifyEvent; 207 | begin 208 | Result := Self; 209 | FTitle := AValue; 210 | end; 211 | 212 | function TNotifyEvent.Title: String; 213 | begin 214 | Result := FTitle; 215 | end; 216 | 217 | function TNotifyEvent.Topic(const AValue: String): INotifyEvent; 218 | begin 219 | Result := Self; 220 | FTopic := AValue; 221 | end; 222 | 223 | function TNotifyEvent.Topic: String; 224 | begin 225 | Result := FTopic; 226 | end; 227 | 228 | end. 229 | -------------------------------------------------------------------------------- /src/Notify.Facade.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Facade; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Core.Contract, 7 | Notify.Api.Contract, 8 | Notify.Notification.Contract, 9 | Notify.Action.Contract, 10 | Notify.Config.Contract, 11 | Notify.Event.Contract, 12 | Notify.Attachment.Contract; 13 | 14 | type 15 | TNotifyCoreFacade = class sealed(TInterfacedObject, INotifyCoreFacade) 16 | private 17 | FApiFactory: INotifyApiFactory; 18 | public 19 | constructor Create; 20 | class function New: INotifyCoreFacade; 21 | function Api: INotifyApi; 22 | function Notification: INotifyNotification; 23 | function Notify: INotifyCore; 24 | function Action: INotifyAction; 25 | function Config: INotifyConfig; 26 | function Event: INotifyEvent; 27 | function Attachment: INotifyAttachment; 28 | end; 29 | 30 | implementation 31 | 32 | uses 33 | Notify.Api.Factory, 34 | Notify.Notification, 35 | Notify.Core, 36 | Notify.Action, 37 | Notify.Config, 38 | Notify.Attachment, 39 | Notify.Event; 40 | 41 | { TNotifyCoreFacade } 42 | 43 | function TNotifyCoreFacade.Notify: INotifyCore; 44 | begin 45 | Result := TNotifyCore.New; 46 | end; 47 | 48 | function TNotifyCoreFacade.Event: INotifyEvent; 49 | begin 50 | Result := TNotifyEvent.New; 51 | end; 52 | 53 | function TNotifyCoreFacade.Action: INotifyAction; 54 | begin 55 | Result := TNofifyAction.New; 56 | end; 57 | 58 | function TNotifyCoreFacade.Config: INotifyConfig; 59 | begin 60 | Result := TNotifyConfig.New 61 | end; 62 | 63 | constructor TNotifyCoreFacade.Create; 64 | begin 65 | FApiFactory := TNotifyApiFactory.New; 66 | end; 67 | 68 | class function TNotifyCoreFacade.New: INotifyCoreFacade; 69 | begin 70 | Result := Self.Create; 71 | end; 72 | 73 | function TNotifyCoreFacade.Api: INotifyApi; 74 | begin 75 | Result := FApiFactory.Api; 76 | end; 77 | 78 | function TNotifyCoreFacade.Attachment: INotifyAttachment; 79 | begin 80 | Result := TNotifyAttachment.New; 81 | end; 82 | 83 | function TNotifyCoreFacade.Notification: INotifyNotification; 84 | begin 85 | Result := TNotifyNotification.New; 86 | end; 87 | 88 | end. 89 | -------------------------------------------------------------------------------- /src/Notify.JSON.Parser.pas: -------------------------------------------------------------------------------- 1 | unit Notify.JSON.Parser; 2 | 3 | interface 4 | 5 | uses System.Classes, System.Json, Rest.Json, System.Generics.Collections, Rest.JsonReflect; 6 | 7 | {$M+} 8 | 9 | type 10 | TArrayMapper = class 11 | protected 12 | procedure RefreshArray(aSource: TList; var aDestination: TArray); 13 | function List(var aList: TList; aSource: TArray): TList; 14 | function ObjectList(var aList: TObjectList; aSource: TArray): TObjectList; 15 | public 16 | constructor Create; virtual; 17 | end; 18 | 19 | TJsonDTO = class(TArrayMapper) 20 | private 21 | FOptions: TJsonOptions; 22 | class procedure PrettyPrintPair(aJSONValue: TJSONPair; aOutputStrings: TStrings; Last: Boolean; Indent: Integer); 23 | class procedure PrettyPrintJSON(aJSONValue: TJsonValue; aOutputStrings: TStrings; Indent: Integer = 0); overload; 24 | class procedure PrettyPrintArray(aJSONValue: TJSONArray; aOutputStrings: TStrings; Last: Boolean; Indent: Integer); 25 | protected 26 | function GetAsJson: string; virtual; 27 | procedure SetAsJson(aValue: string); virtual; 28 | public 29 | constructor Create; override; 30 | class function PrettyPrintJSON(aJson: string): string; overload; 31 | function ToString: string; override; 32 | property AsJson: string read GetAsJson write SetAsJson; 33 | end; 34 | 35 | GenericListReflectAttribute = class(JsonReflectAttribute) 36 | public 37 | constructor Create; 38 | end; 39 | 40 | SuppressZeroAttribute = class(JsonReflectAttribute) 41 | public 42 | constructor Create; 43 | end; 44 | 45 | implementation 46 | 47 | uses System.Sysutils, System.JSONConsts, System.Rtti, System.DateUtils; 48 | 49 | { TJsonDTO } 50 | 51 | constructor TJsonDTO.Create; 52 | begin 53 | inherited; 54 | FOptions := [joDateIsUTC, joDateFormatISO8601, joIgnoreEmptyStrings, joIgnoreEmptyArrays]; 55 | end; 56 | 57 | function TJsonDTO.GetAsJson: string; 58 | begin 59 | Result := TJson.ObjectToJsonString(Self, FOptions); 60 | end; 61 | 62 | const 63 | INDENT_SIZE = 2; 64 | 65 | class procedure TJsonDTO.PrettyPrintJSON(aJSONValue: TJsonValue; aOutputStrings: TStrings; Indent: Integer); 66 | var 67 | i: Integer; 68 | Ident: Integer; 69 | begin 70 | Ident := Indent + INDENT_SIZE; 71 | i := 0; 72 | 73 | if aJSONValue is TJSONObject then 74 | begin 75 | aOutputStrings.Add(StringOfChar(' ', Ident) + '{'); 76 | for i := 0 to TJSONObject(aJSONValue).Count - 1 do 77 | PrettyPrintPair(TJSONObject(aJSONValue).Pairs[i], aOutputStrings, i = TJSONObject(aJSONValue).Count - 1, Ident); 78 | 79 | aOutputStrings.Add(StringOfChar(' ', Ident) + '}'); 80 | end 81 | else if aJSONValue is TJSONArray then 82 | PrettyPrintArray(TJSONArray(aJSONValue), aOutputStrings, i = TJSONObject(aJSONValue).Count - 1, Ident) 83 | else 84 | aOutputStrings.Add(StringOfChar(' ', Ident) + aJSONValue.ToString); 85 | end; 86 | 87 | class procedure TJsonDTO.PrettyPrintArray(aJSONValue: TJSONArray; aOutputStrings: TStrings; Last: Boolean; Indent: Integer); 88 | var 89 | i: Integer; 90 | begin 91 | aOutputStrings.Add(StringOfChar(' ', Indent + INDENT_SIZE) + '['); 92 | 93 | for i := 0 to aJSONValue.Count - 1 do 94 | begin 95 | PrettyPrintJSON(aJSONValue.Items[i], aOutputStrings, Indent); 96 | if i < aJSONValue.Count - 1 then 97 | aOutputStrings[aOutputStrings.Count - 1] := aOutputStrings[aOutputStrings.Count - 1] + ','; 98 | end; 99 | 100 | aOutputStrings.Add(StringOfChar(' ', Indent + INDENT_SIZE - 2) + ']'); 101 | end; 102 | 103 | class function TJsonDTO.PrettyPrintJSON(aJson: string): string; 104 | var 105 | StringList: TStringlist; 106 | JSONValue: TJsonValue; 107 | begin 108 | StringList := TStringlist.Create; 109 | try 110 | JSONValue := TJSONObject.ParseJSONValue(aJson); 111 | try 112 | if JSONValue <> nil then 113 | PrettyPrintJSON(JSONValue, StringList); 114 | finally 115 | JSONValue.Free; 116 | end; 117 | 118 | Result := StringList.Text; 119 | finally 120 | StringList.Free; 121 | end; 122 | end; 123 | 124 | class procedure TJsonDTO.PrettyPrintPair(aJSONValue: TJSONPair; aOutputStrings: TStrings; Last: Boolean; Indent: Integer); 125 | const 126 | TEMPLATE = '%s:%s'; 127 | var 128 | Line: string; 129 | NewList: TStringlist; 130 | begin 131 | NewList := TStringlist.Create; 132 | try 133 | PrettyPrintJSON(aJSONValue.JSONValue, NewList, Indent); 134 | Line := Format(TEMPLATE, [aJSONValue.JsonString.ToString, Trim(NewList.Text)]); 135 | finally 136 | NewList.Free; 137 | end; 138 | 139 | Line := StringOfChar(' ', Indent + INDENT_SIZE) + Line; 140 | if not Last then 141 | Line := Line + ','; 142 | aOutputStrings.Add(Line); 143 | end; 144 | 145 | procedure TJsonDTO.SetAsJson(aValue: string); 146 | var 147 | JSONValue: TJsonValue; 148 | JSONObject: TJSONObject; 149 | begin 150 | JSONValue := TJSONObject.ParseJSONValue(aValue); 151 | try 152 | if not Assigned(JSONValue) then 153 | Exit; 154 | 155 | if (JSONValue is TJSONArray) then 156 | begin 157 | with TJSONUnMarshal.Create do 158 | try 159 | SetFieldArray(Self, 'Items', (JSONValue as TJSONArray)); 160 | finally 161 | Free; 162 | end; 163 | 164 | Exit; 165 | end; 166 | 167 | if (JSONValue is TJSONObject) then 168 | JSONObject := JSONValue as TJSONObject 169 | else 170 | begin 171 | aValue := aValue.Trim; 172 | if (aValue = '') and not Assigned(JSONValue) or (aValue <> '') and Assigned(JSONValue) and JSONValue.Null then 173 | Exit 174 | else 175 | raise EConversionError.Create(SCannotCreateObject); 176 | end; 177 | 178 | TJson.JsonToObject(Self, JSONObject, FOptions); 179 | finally 180 | JSONValue.Free; 181 | end; 182 | end; 183 | 184 | function TJsonDTO.ToString: string; 185 | begin 186 | Result := AsJson; 187 | end; 188 | 189 | { TArrayMapper } 190 | 191 | constructor TArrayMapper.Create; 192 | begin 193 | inherited; 194 | end; 195 | 196 | function TArrayMapper.List(var aList: TList; aSource: TArray): TList; 197 | begin 198 | if aList = nil then 199 | begin 200 | aList := TList.Create; 201 | aList.AddRange(aSource); 202 | end; 203 | 204 | Exit(aList); 205 | end; 206 | 207 | function TArrayMapper.ObjectList(var aList: TObjectList; aSource: TArray): TObjectList; 208 | var 209 | Element: T; 210 | begin 211 | if aList = nil then 212 | begin 213 | aList := TObjectList.Create; 214 | for Element in aSource do 215 | aList.Add(Element); 216 | end; 217 | 218 | Exit(aList); 219 | end; 220 | 221 | procedure TArrayMapper.RefreshArray(aSource: TList; var aDestination: TArray); 222 | begin 223 | if aSource <> nil then 224 | aDestination := aSource.ToArray; 225 | end; 226 | 227 | type 228 | TGenericListFieldInterceptor = class(TJSONInterceptor) 229 | public 230 | function ObjectsConverter(Data: TObject; Field: string): TListOfObjects; override; 231 | end; 232 | 233 | { TListFieldInterceptor } 234 | 235 | function TGenericListFieldInterceptor.ObjectsConverter(Data: TObject; Field: string): TListOfObjects; 236 | var 237 | ctx: TRttiContext; 238 | List: TList; 239 | RttiProperty: TRttiProperty; 240 | begin 241 | RttiProperty := ctx.GetType(Data.ClassInfo).GetProperty(Copy(Field, 2, MAXINT)); 242 | List := TList(RttiProperty.GetValue(Data).AsObject); 243 | Result := TListOfObjects(List.List); 244 | SetLength(Result, List.Count); 245 | end; 246 | 247 | constructor GenericListReflectAttribute.Create; 248 | begin 249 | inherited Create(ctObjects, rtObjects, TGenericListFieldInterceptor, nil, false); 250 | end; 251 | 252 | type 253 | TSuppressZeroDateInterceptor = class(TJSONInterceptor) 254 | public 255 | function StringConverter(Data: TObject; Field: string): string; override; 256 | procedure StringReverter(Data: TObject; Field: string; Arg: string); override; 257 | end; 258 | 259 | function TSuppressZeroDateInterceptor.StringConverter(Data: TObject; Field: string): string; 260 | var 261 | RttiContext: TRttiContext; 262 | Date: TDateTime; 263 | begin 264 | Date := RttiContext.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType; 265 | if Date = 0 then 266 | Result := string.Empty 267 | else 268 | Result := DateToISO8601(Date, True); 269 | end; 270 | 271 | procedure TSuppressZeroDateInterceptor.StringReverter(Data: TObject; Field, Arg: string); 272 | var 273 | RttiContext: TRttiContext; 274 | Date: TDateTime; 275 | begin 276 | if Arg.IsEmpty then 277 | Date := 0 278 | else 279 | Date := ISO8601ToDate(Arg, True); 280 | 281 | RttiContext.GetType(Data.ClassType).GetField(Field).SetValue(Data, Date); 282 | end; 283 | 284 | { SuppressZeroAttribute } 285 | 286 | constructor SuppressZeroAttribute.Create; 287 | begin 288 | inherited Create(ctString, rtString, TSuppressZeroDateInterceptor); 289 | end; 290 | 291 | end. 292 | -------------------------------------------------------------------------------- /src/Notify.Logs.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Logs; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes; 7 | 8 | type 9 | TNotifyLogs = class sealed 10 | public 11 | class procedure Log(const APath: String; AValue: String = ''); overload; 12 | class procedure Log(const APath: String; AValue: TMemoryStream); overload; 13 | end; 14 | 15 | implementation 16 | 17 | uses 18 | System.SysUtils, System.DateUtils; 19 | 20 | { TNotifyLogs } 21 | 22 | class procedure TNotifyLogs.Log(const APath: String; AValue: String); 23 | var 24 | LStrings: TStringList; 25 | LLogFileName: String; 26 | begin 27 | LStrings := TStringList.Create; 28 | try 29 | LLogFileName := 'ntfy-' + FormatDateTime('dd.MM.yyyy', Now) + '.txt'; 30 | 31 | if FileExists(LLogFileName) then 32 | LStrings.LoadFromFile(LLogFileName); 33 | 34 | LStrings.Add(Format('%s : %s', [DateTimeToStr(Now), AValue]) ); 35 | LStrings.SaveToFile(LLogFileName); 36 | finally 37 | LStrings.Free; 38 | end; 39 | 40 | end; 41 | 42 | class procedure TNotifyLogs.Log(const APath: String; AValue: TMemoryStream); 43 | var 44 | LStrings: TStringList; 45 | LLogFileName: String; 46 | LDataString: String; 47 | begin 48 | 49 | if AValue = nil then 50 | Exit; 51 | 52 | LStrings := TStringList.Create; 53 | try 54 | LLogFileName := 'ntfy-' + FormatDateTime('dd.MM.yyyy', Now) + '.txt'; 55 | 56 | if FileExists(LLogFileName) then 57 | LStrings.LoadFromFile(LLogFileName); 58 | 59 | SetString(LDataString, PAnsiChar(AValue.Memory), AValue.Size); 60 | LStrings.Add(Format('%s : %s', [DateTimeToStr(Now), LDataString]) ); 61 | LStrings.SaveToFile(LLogFileName); 62 | finally 63 | LStrings.Free; 64 | end; 65 | 66 | end; 67 | 68 | end. 69 | -------------------------------------------------------------------------------- /src/Notify.Notification.Contract.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Notification.Contract; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Action.Contract; 8 | 9 | type 10 | INotifyTags = TArray; 11 | 12 | INotifyNotification = interface 13 | ['{BE2E83B7-C39E-4985-93F9-4468976B6AC5}'] 14 | function Topic: String; overload; 15 | function Topic(const AValue: String): INotifyNotification; overload; 16 | function MessageContent: String; overload; 17 | function MessageContent(const AValue: String): INotifyNotification; overload; 18 | function Title: String; overload; 19 | function Title(const AValue: String): INotifyNotification; overload; 20 | function Tags: INotifyTags; overload; 21 | function Tags(const AValue: INotifyTags): INotifyNotification; overload; 22 | function Priority: TNotifyPriority; overload; 23 | function Priority(const AValue: TNotifyPriority): INotifyNotification; overload; 24 | function AttachURL(const AValue: String): INotifyNotification; overload; 25 | function FilePath: String; overload; 26 | function AttachFile(const AValue: String): INotifyNotification; overload; 27 | function FileName: String; overload; 28 | function Click: String overload; 29 | function Click(const AValue: String): INotifyNotification; overload; 30 | function Action: INotifyAction; overload; 31 | function Action(const AValue: INotifyAction): INotifyNotification; overload; 32 | function ClearActions: INotifyNotification; overload; 33 | function AsJSONString: String; 34 | function Email: String; overload; 35 | function Email(const AValue: String): INotifyNotification; overload; 36 | function Icon: String; overload; 37 | function Icon(const AValue: String): INotifyNotification; overload; 38 | function Delay: String; overload; 39 | function Delay(const AValue: String): INotifyNotification; overload; 40 | end; 41 | 42 | implementation 43 | 44 | end. 45 | -------------------------------------------------------------------------------- /src/Notify.Notification.DTO.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Notification.DTO; 2 | 3 | interface 4 | 5 | uses 6 | Notify.JSON.Parser, 7 | System.Generics.Collections, 8 | REST.Json.Types, 9 | Notify.Action.DTO; 10 | 11 | type 12 | TNotifyNotificationDTO = class(TJsonDTO) 13 | private 14 | [JSONName('attach')] 15 | FAttach: string; 16 | [JSONName('click')] 17 | FClick: string; 18 | [JSONName('filename')] 19 | FFilename: string; 20 | [JSONName('message')] 21 | FMessage: string; 22 | [JSONName('priority')] 23 | FPriority: Integer; 24 | [JSONName('tags')] 25 | FTagsArray: TArray; 26 | [JSONMarshalled(False)] 27 | FTags: TList; 28 | [JSONName('title')] 29 | FTitle: string; 30 | [JSONName('topic')] 31 | FTopic: string; 32 | [JSONName('delay')] 33 | FDelay: String; 34 | [JSONName('email')] 35 | FEmail: String; 36 | [GenericListReflect] 37 | FActions: TObjectList; 38 | [JSONName('actions'), JSONMarshalled(False)] 39 | FActionsArray: TArray; 40 | function GetTags: TList; 41 | function GetActions: TObjectList; 42 | protected 43 | function GetAsJson: string; override; 44 | published 45 | property Attach: string read FAttach write FAttach; 46 | property Click: string read FClick write FClick; 47 | property Filename: string read FFilename write FFilename; 48 | property MessageContent: string read FMessage write FMessage; 49 | property Priority: Integer read FPriority write FPriority; 50 | property Tags: TList read GetTags; 51 | property Title: string read FTitle write FTitle; 52 | property Topic: string read FTopic write FTopic; 53 | property Actions: TObjectList read GetActions; 54 | property Delay: String read FDelay write FDelay; 55 | property Email: String read FEmail write FEmail; 56 | public 57 | destructor Destroy; override; 58 | end; 59 | 60 | implementation 61 | 62 | { TNotifyNotificationDTO } 63 | 64 | destructor TNotifyNotificationDTO.Destroy; 65 | begin 66 | GetTags.Free; 67 | GetActions.Free; 68 | inherited; 69 | end; 70 | 71 | function TNotifyNotificationDTO.GetTags: TList; 72 | begin 73 | Result := List(FTags, FTagsArray); 74 | end; 75 | 76 | function TNotifyNotificationDTO.GetActions: TObjectList; 77 | begin 78 | Result := ObjectList(FActions, FActionsArray); 79 | end; 80 | 81 | function TNotifyNotificationDTO.GetAsJson: string; 82 | begin 83 | RefreshArray(FTags, FTagsArray); 84 | RefreshArray(FActions, FActionsArray); 85 | Result := inherited; 86 | end; 87 | 88 | end. 89 | -------------------------------------------------------------------------------- /src/Notify.Notification.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Notification; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Action.Contract, 8 | Notify.Api.Contract, 9 | Notify.Notification.Contract, 10 | System.Generics.Collections; 11 | 12 | type 13 | TNotifyNotification = class sealed(TInterfacedObject, INotifyNotification) 14 | strict private 15 | FTopic: String; 16 | FMessageContent: String; 17 | FTitle: String; 18 | FTags: INotifyTags; 19 | FPriority: TNotifyPriority; 20 | FClick: String; 21 | FAction: INotifyAction; 22 | FActions: TDictionary; 23 | FFileName: String; 24 | FFilePath: String; 25 | FAttachment: String; 26 | FEmail: String; 27 | FIcon: String; 28 | FDelay: String; 29 | public 30 | class function New: INotifyNotification; 31 | constructor Create; 32 | destructor Destroy; override; 33 | private 34 | function Topic: String; overload; 35 | function Topic(const AValue: String): INotifyNotification; overload; 36 | function MessageContent: String; overload; 37 | function MessageContent(const AValue: String): INotifyNotification; overload; 38 | function Title: String; overload; 39 | function Title(const AValue: String): INotifyNotification; overload; 40 | function Tags: INotifyTags; overload; 41 | function Tags(const AValue: INotifyTags): INotifyNotification; overload; 42 | function Priority: TNotifyPriority; overload; 43 | function Priority(const AValue: TNotifyPriority): INotifyNotification; overload; 44 | function AttachURL(const AValue: String): INotifyNotification; overload; 45 | function FileName: String; overload; 46 | function FilePath: String; overload; 47 | function AttachFile(const AValue: String): INotifyNotification; overload; 48 | function Click: String overload; 49 | function Click(const AValue: String): INotifyNotification; overload; 50 | function Action: INotifyAction; overload; 51 | function Action(const AValue: INotifyAction): INotifyNotification; overload; 52 | function ClearActions: INotifyNotification; overload; 53 | function Email: String; overload; 54 | function Email(const AValue: String): INotifyNotification; overload; 55 | function Icon: String; overload; 56 | function Icon(const AValue: String): INotifyNotification; overload; 57 | function Delay: String; overload; 58 | function Delay(const AValue: String): INotifyNotification; overload; 59 | function AsJSONString: String; 60 | end; 61 | 62 | implementation 63 | 64 | uses 65 | System.SysUtils, 66 | Notify.SmartPointer, 67 | Notify.Action.DTO, 68 | Notify.Notification.DTO, 69 | System.Classes, 70 | Notify.Config; 71 | 72 | { TNotifyNotification } 73 | 74 | function TNotifyNotification.Action: INotifyAction; 75 | begin 76 | Result := FAction; 77 | end; 78 | 79 | function TNotifyNotification.Action(const AValue: INotifyAction): INotifyNotification; 80 | begin 81 | Result := Self; 82 | FAction := AValue; 83 | 84 | if FActions.ContainsKey(AValue.&Label) then 85 | FActions.Remove(AValue.&Label); 86 | 87 | if FActions.Count >= 3 then 88 | Exit; 89 | 90 | FActions.Add(AValue.&Label, AValue); 91 | 92 | end; 93 | 94 | function TNotifyNotification.AsJSONString: String; 95 | var 96 | LNotificationDTO: TSmartPointer; 97 | LActionDTO: TNotifyActionDTO; 98 | LAction: INotifyAction; 99 | begin 100 | LNotificationDTO.Value.Topic := FTopic; 101 | LNotificationDTO.Value.MessageContent := FMessageContent; 102 | LNotificationDTO.Value.Priority := Ord(FPriority); 103 | LNotificationDTO.Value.Title := FTitle; 104 | LNotificationDTO.Value.Tags.AddRange(FTags); 105 | LNotificationDTO.Value.Filename := FFileName; 106 | LNotificationDTO.Value.Attach := FAttachment; 107 | LNotificationDTO.Value.Delay := FDelay; 108 | LNotificationDTO.Value.Email := FEmail; 109 | 110 | for LAction in FActions.Values do 111 | begin 112 | LActionDTO := TNotifyActionDTO.Create; 113 | LActionDTO.Action := NotifyActionTypesArray[LAction.&Type]; 114 | LActionDTO.&Label := LAction.&Label; 115 | LActionDTO.Clear := LAction.Clear; 116 | LActionDTO.Url := LAction.Url; 117 | LActionDTO.Method := LAction.Method; 118 | LActionDTO.Body := LAction.Body; 119 | LActionDTO.Method := LAction.Method; 120 | LActionDTO.Headers := LAction.Headers; 121 | LAction.Validate; 122 | LNotificationDTO.Value.Actions.Add(LActionDTO); 123 | end; 124 | 125 | Result := LNotificationDTO.Value.AsJson; 126 | end; 127 | 128 | function TNotifyNotification.AttachURL(const AValue: String): INotifyNotification; 129 | begin 130 | Result := Self; 131 | FAttachment := AValue; 132 | end; 133 | 134 | function TNotifyNotification.ClearActions: INotifyNotification; 135 | begin 136 | Result := Self; 137 | FActions.Clear; 138 | end; 139 | 140 | function TNotifyNotification.Click(const AValue: String): INotifyNotification; 141 | begin 142 | Result := Self; 143 | FClick := AValue; 144 | end; 145 | 146 | constructor TNotifyNotification.Create; 147 | begin 148 | FPriority := TNotifyPriority.DEFAULT; 149 | FActions := TDictionary.Create(); 150 | end; 151 | 152 | function TNotifyNotification.Delay(const AValue: String): INotifyNotification; 153 | begin 154 | Result := Self; 155 | FDelay := AValue; 156 | end; 157 | 158 | function TNotifyNotification.Delay: String; 159 | begin 160 | Result := FDelay; 161 | end; 162 | 163 | destructor TNotifyNotification.Destroy; 164 | begin 165 | FActions.Free; 166 | inherited; 167 | end; 168 | 169 | function TNotifyNotification.Email(const AValue: String): INotifyNotification; 170 | begin 171 | //"Email" doesn't works together with "Delay" 172 | Result := Self; 173 | FEmail := AValue; 174 | end; 175 | 176 | function TNotifyNotification.Email: String; 177 | begin 178 | Result := FEmail; 179 | end; 180 | 181 | function TNotifyNotification.FilePath: String; 182 | begin 183 | Result := FFilePath; 184 | end; 185 | 186 | function TNotifyNotification.Click: String; 187 | begin 188 | Result := FClick; 189 | end; 190 | 191 | function TNotifyNotification.FileName: String; 192 | begin 193 | Result := FFileName; 194 | end; 195 | 196 | function TNotifyNotification.AttachFile(const AValue: String): INotifyNotification; 197 | begin 198 | Result := Self; 199 | FFileName := ExtractFileName(AValue); 200 | FFilePath := AValue; 201 | end; 202 | 203 | function TNotifyNotification.Icon(const AValue: String): INotifyNotification; 204 | begin 205 | Result := Self; 206 | FIcon := AValue; 207 | end; 208 | 209 | function TNotifyNotification.Icon: String; 210 | begin 211 | Result := FIcon; 212 | end; 213 | 214 | function TNotifyNotification.MessageContent(const AValue: String): INotifyNotification; 215 | begin 216 | Result := Self; 217 | FMessageContent := AValue; 218 | end; 219 | 220 | class function TNotifyNotification.New: INotifyNotification; 221 | begin 222 | Result := Self.Create; 223 | end; 224 | 225 | function TNotifyNotification.MessageContent: String; 226 | begin 227 | Result := FMessageContent; 228 | end; 229 | 230 | function TNotifyNotification.Priority(const AValue: TNotifyPriority): INotifyNotification; 231 | begin 232 | Result := Self; 233 | FPriority := AValue; 234 | end; 235 | 236 | function TNotifyNotification.Priority: TNotifyPriority; 237 | begin 238 | Result := FPriority; 239 | end; 240 | 241 | function TNotifyNotification.Tags: INotifyTags; 242 | begin 243 | Result := FTags; 244 | end; 245 | 246 | function TNotifyNotification.Tags(const AValue: INotifyTags): INotifyNotification; 247 | begin 248 | Result := Self; 249 | FTags := AValue; 250 | end; 251 | 252 | function TNotifyNotification.Title(const AValue: String): INotifyNotification; 253 | begin 254 | Result := Self; 255 | FTitle := AValue; 256 | end; 257 | 258 | function TNotifyNotification.Title: String; 259 | begin 260 | Result := FTitle; 261 | end; 262 | 263 | function TNotifyNotification.Topic(const AValue: String): INotifyNotification; 264 | begin 265 | Result := Self; 266 | FTopic := AValue; 267 | end; 268 | 269 | function TNotifyNotification.Topic: String; 270 | begin 271 | Result := FTopic; 272 | end; 273 | 274 | end. 275 | -------------------------------------------------------------------------------- /src/Notify.Response.Data.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Response.Data; 2 | 3 | interface 4 | 5 | uses 6 | REST.Json.Types, Notify.JSON.Parser; 7 | 8 | type 9 | TNotifyResponseDTO = class(TJsonDTO) 10 | private 11 | [JSONName('id')] 12 | FId: String; 13 | [JSONName('time')] 14 | FTime: Integer; 15 | published 16 | property Id: String read FId write FId; 17 | property Time: Integer read FTime write FTime; 18 | end; 19 | 20 | implementation 21 | 22 | end. 23 | -------------------------------------------------------------------------------- /src/Notify.SmartPointer.pas: -------------------------------------------------------------------------------- 1 | unit Notify.SmartPointer; 2 | 3 | interface 4 | 5 | uses 6 | Generics.Defaults; 7 | 8 | type 9 | TSmartPointer = record 10 | strict private 11 | FValue: T; 12 | FFreeTheValue: IInterface; 13 | function GetValue: T; 14 | private 15 | type 16 | TFreeTheValue = class (TInterfacedObject) 17 | private 18 | FObjectToFree: TObject; 19 | public 20 | constructor Create(anObjectToFree: TObject); 21 | destructor Destroy; override; 22 | end; 23 | public 24 | constructor Create(AValue: T); overload; 25 | procedure Create; overload; 26 | class operator Implicit(AValue: T): TSmartPointer; 27 | class operator Implicit(smart: TSmartPointer ): T; 28 | property Value: T read GetValue; 29 | end; 30 | 31 | implementation 32 | 33 | 34 | { TSmartPointer } 35 | 36 | constructor TSmartPointer.Create(AValue: T); 37 | begin 38 | FValue := AValue; 39 | FFreeTheValue := TFreeTheValue.Create(FValue); 40 | end; 41 | 42 | procedure TSmartPointer.Create; 43 | begin 44 | TSmartPointer.Create (T.Create); 45 | end; 46 | 47 | function TSmartPointer.GetValue: T; 48 | begin 49 | if not Assigned(FFreeTheValue) then 50 | Self := TSmartPointer.Create (T.Create); 51 | Result := FValue; 52 | end; 53 | 54 | class operator TSmartPointer.Implicit(smart: TSmartPointer): T; 55 | begin 56 | Result := Smart.Value; 57 | end; 58 | 59 | class operator TSmartPointer.Implicit(AValue: T): TSmartPointer; 60 | begin 61 | Result := TSmartPointer.Create(AValue); 62 | end; 63 | 64 | { TSmartPointer.TFreeTheValue } 65 | 66 | constructor TSmartPointer.TFreeTheValue.Create(anObjectToFree: TObject); 67 | begin 68 | FObjectToFree := anObjectToFree; 69 | end; 70 | 71 | destructor TSmartPointer.TFreeTheValue.Destroy; 72 | begin 73 | FObjectToFree.Free; 74 | inherited; 75 | end; 76 | 77 | end. 78 | -------------------------------------------------------------------------------- /src/Notify.Subscription.Event.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Subscription.Event; 2 | 3 | interface 4 | 5 | type 6 | TNotifySubscriptionEvent = type UTF8String; 7 | 8 | implementation 9 | 10 | end. 11 | -------------------------------------------------------------------------------- /src/Notify.Types.pas: -------------------------------------------------------------------------------- 1 | unit Notify.Types; 2 | 3 | interface 4 | 5 | uses 6 | Notify.JSON.Parser, System.Generics.Collections; 7 | 8 | type 9 | {$SCOPEDENUMS ON} 10 | 11 | TNotifyActionType = (VIEW, BROADCAST, HTTP); 12 | TNotifySubscriptionType = (JSON, SSE, RAW, WEB_SOCKET); 13 | 14 | TNotifyPriority = ( 15 | MAX = 5, 16 | HIGH = 4, 17 | DEFAULT = 3, 18 | LOW = 2, 19 | MIN = 1 20 | ); 21 | 22 | TNotifyMessageEvent = ( 23 | OPEN, 24 | KEEPALIVE, 25 | MSG, 26 | POLL_REQUEST 27 | ); 28 | 29 | TNotifyFilter = ( 30 | ID, 31 | TITLE, 32 | MESSAGECONTENT, 33 | PRIORITY, 34 | TAGS 35 | ); 36 | 37 | {$SCOPEDENUMS OFF} 38 | 39 | TJsonDTO = Notify.JSON.Parser.TJsonDTO; 40 | 41 | const 42 | NotifyActionTypesArray: array [TNotifyActionType] of String = ( 43 | 'view', 44 | 'broadcast', 45 | 'http' 46 | ); 47 | 48 | NotifyMessageEventArray: array[TNotifyMessageEvent] of String = ( 49 | 'open', 50 | 'keepalive', 51 | 'message', 52 | 'poll_request' 53 | ); 54 | 55 | NotifyFilterTypeDescription: array[TNotifyFilter] of String = ( 56 | 'id', 57 | 'title', 58 | 'message', 59 | 'priority', 60 | 'tags' 61 | ); 62 | 63 | implementation 64 | 65 | 66 | end. 67 | -------------------------------------------------------------------------------- /src/Notify.pas: -------------------------------------------------------------------------------- 1 | unit Notify; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types, 7 | Notify.Action.Contract, 8 | Notify.Notification.Contract, 9 | Notify.Event.Contract, 10 | Notify.Core.Contract, 11 | Notify.Api.Response; 12 | 13 | type 14 | INotify = Notify.Core.Contract.INotifyCore; 15 | INotifyNotification = Notify.Notification.Contract.INotifyNotification; 16 | INotifyAction = Notify.Action.Contract.INotifyAction; 17 | INotifyEvent = Notify.Event.Contract.INotifyEvent; 18 | TNotifyEventProc = Notify.Event.Contract.TNotifyEventProc; 19 | TNotifyPriority = Notify.Types.TNotifyPriority; 20 | TNotifyActionType = Notify.Types.TNotifyActionType; 21 | TNotifySubscriptionType = Notify.Types.TNotifySubscriptionType; 22 | TJsonDTO = Notify.Types.TJsonDTO; 23 | TNotifyFilter = Notify.Types.TNotifyFilter; 24 | TNotifyApiResponse = Notify.Api.Response.TNotifyApiResponse; 25 | 26 | var 27 | New: INotifyCoreFacade; 28 | Ntfy: INotify; 29 | 30 | implementation 31 | 32 | uses 33 | Notify.Facade; 34 | 35 | initialization 36 | New := nil; 37 | Ntfy := nil; 38 | New := TNotifyCoreFacade.New; 39 | Ntfy := New.Notify; 40 | 41 | 42 | end. 43 | -------------------------------------------------------------------------------- /tests/NtfyForDelphiTests.dpr: -------------------------------------------------------------------------------- 1 | program NtfyForDelphiTests; 2 | { 3 | 4 | Delphi DUnit Test Project 5 | ------------------------- 6 | This project contains the DUnit test framework and the GUI/Console test runners. 7 | Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options 8 | to use the console test runner. Otherwise the GUI test runner will be used by 9 | default. 10 | 11 | } 12 | 13 | {$IFDEF CONSOLE_TESTRUNNER} 14 | {$APPTYPE CONSOLE} 15 | {$ENDIF} 16 | 17 | uses 18 | DUnitTestRunner, 19 | Notify in '..\src\Notify.pas', 20 | Notify.Action.Contract in '..\src\Notify.Action.Contract.pas', 21 | Notify.Action.DTO in '..\src\Notify.Action.DTO.pas', 22 | Notify.Action in '..\src\Notify.Action.pas', 23 | Notify.Api.Contract in '..\src\Notify.Api.Contract.pas', 24 | Notify.Api.Indy in '..\src\Notify.Api.Indy.pas', 25 | Notify.Attachment.Contract in '..\src\Notify.Attachment.Contract.pas', 26 | Notify.Attachment.DTO in '..\src\Notify.Attachment.DTO.pas', 27 | Notify.Attachment in '..\src\Notify.Attachment.pas', 28 | Notify.Config.Contract in '..\src\Notify.Config.Contract.pas', 29 | Notify.Config in '..\src\Notify.Config.pas', 30 | Notify.Core.Contract in '..\src\Notify.Core.Contract.pas', 31 | Notify.Core in '..\src\Notify.Core.pas', 32 | Notify.Event.Contract in '..\src\Notify.Event.Contract.pas', 33 | Notify.Event.DTO in '..\src\Notify.Event.DTO.pas', 34 | Notify.Event in '..\src\Notify.Event.pas', 35 | Notify.Facade in '..\src\Notify.Facade.pas', 36 | Notify.JSON.Parser in '..\src\Notify.JSON.Parser.pas', 37 | Notify.Logs in '..\src\Notify.Logs.pas', 38 | Notify.Notification.Contract in '..\src\Notify.Notification.Contract.pas', 39 | Notify.Notification.DTO in '..\src\Notify.Notification.DTO.pas', 40 | Notify.Notification in '..\src\Notify.Notification.pas', 41 | Notify.SmartPointer in '..\src\Notify.SmartPointer.pas', 42 | Notify.Subscription.Event in '..\src\Notify.Subscription.Event.pas', 43 | Notify.Types in '..\src\Notify.Types.pas', 44 | Notify.Error in '..\src\Notify.Error.pas', 45 | Notify.Api.Response in '..\src\Notify.Api.Response.pas', 46 | Notify.Response.Data in '..\src\Notify.Response.Data.pas', 47 | Notify.Custom.Types in '..\src\Notify.Custom.Types.pas', 48 | Notify.Api.NetHTTP in '..\src\Notify.Api.NetHTTP.pas', 49 | Notify.Api.Factory in '..\src\Notify.Api.Factory.pas', 50 | NX.Horizon in '..\src\NX.Horizon.pas', 51 | Test.Simple.Message in 'src\Test.Simple.Message.pas', 52 | Test.Action.Header in 'src\Test.Action.Header.pas', 53 | Test.Constants in 'src\Test.Constants.pas', 54 | Test.Action.HTTP in 'src\Test.Action.HTTP.pas', 55 | Test.Action.View in 'src\Test.Action.View.pas', 56 | Test.Attachments in 'src\Test.Attachments.pas', 57 | Test.Email in 'src\Test.Email.pas', 58 | Test.Icons in 'src\Test.Icons.pas', 59 | Test.Emojis in 'src\Test.Emojis.pas', 60 | Test.URL.Attachments in 'src\Test.URL.Attachments.pas', 61 | Index in 'src\Index.pas', 62 | Test.Subscription in 'src\Test.Subscription.pas'; 63 | 64 | begin 65 | DUnitTestRunner.RunRegisteredTests; 66 | Readln; 67 | end. 68 | 69 | -------------------------------------------------------------------------------- /tests/NtfyForDelphiTests.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazzelnuts/ntfy-for-delphi/fd0d51efafd6954bfd1aaaae98fc07531149bb6a/tests/NtfyForDelphiTests.res -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | ## Test cases 6 | 7 | DUnit tests for basic methods. You can deploy a self-hosted server via Docker images. Here you find a demo docker compose and server yml file. 8 | 9 | ## Environment 10 | Run these steps in your enviroment: 11 | 12 | ``` cmd 13 | docker compose create 14 | docker cp server.yml ntfy:/etc/ntfy 15 | docker compose up 16 | ``` 17 | 18 | ## Installation 19 | * Open ```sample/Examples.grouppoj``` and build ```NtfyForDelphiTests``` project. 20 | * (Optional) Paste the SSL libraries into the executable's folder ```bin``` if you are using ```NTFY_HTTP_INDY```. 21 | 22 | ## DUnit Tests 23 | * Access http://localhost:80 in your browser and subscribe to ```notify-delphi-integration-8jh27d```. 24 | * Run ```bin\NtfyForDelphiTests.exe```. You should receive notifications sent from the test runner. 25 | 26 | ``` pascal 27 | RegisterTest('Send simple message', TTestSimpleMessage.Suite); 28 | RegisterTest('Send action view', TTestActionView.Suite); 29 | RegisterTest('Send action http', TTestActionHTTP.Suite); 30 | RegisterTest('Send file attachments', TTestAttachments.Suite); 31 | RegisterTest('Send action headers', TTestActionHeader.Suite); 32 | RegisterTest('Send emails', TTestEmail.Suite); 33 | RegisterTest('Send tags', TTestEmojis.Suite); 34 | RegisterTest('Send icons', TTestIcons.Suite); 35 | RegisterTest('Send url attachments', TTestURLAttachments.Suite); 36 | ``` 37 |
-------------------------------------------------------------------------------- /tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | ntfy: 5 | image: binwiederhier/ntfy 6 | container_name: ntfy 7 | command: 8 | - serve 9 | volumes: 10 | - /var/cache/ntfy:/var/cache/ntfy 11 | - /etc/ntfy:/etc/ntfy 12 | ports: 13 | - 80:80 14 | restart: unless-stopped 15 | -------------------------------------------------------------------------------- /tests/server.yml: -------------------------------------------------------------------------------- 1 | base-url: "http://localhost:80" 2 | attachment-cache-dir: "/var/cache/ntfy/attachments" 3 | attachment-total-size-limit: "5G" 4 | attachment-file-size-limit: "15M" 5 | attachment-expiry-duration: "3h" 6 | visitor-attachment-total-size-limit: "100M" 7 | visitor-attachment-daily-bandwidth-limit: "1000M" 8 | smtp-sender-addr: "email-smtp.us-east-2.amazonaws.com:587" 9 | smtp-sender-user: "AKIDEADBEEFAFFE12345" 10 | smtp-sender-pass: "Abd13Kf+sfAk2DzifjafldkThisIsNotARealKeyOMG." 11 | smtp-sender-from: "ntfy@ntfy.sh" -------------------------------------------------------------------------------- /tests/src/Index.pas: -------------------------------------------------------------------------------- 1 | unit Index; 2 | 3 | interface 4 | 5 | uses 6 | Test.Simple.Message, 7 | Test.Action.Header, 8 | Test.Action.HTTP, 9 | Test.Action.View, 10 | Test.Attachments, 11 | Test.Email, 12 | Test.Emojis, 13 | Test.Icons, 14 | Test.URL.Attachments, 15 | Test.Subscription; 16 | 17 | implementation 18 | 19 | uses 20 | TestFramework; 21 | 22 | initialization 23 | RegisterTest('Send simple message', TTestSimpleMessage.Suite); 24 | RegisterTest('Send action view', TTestActionView.Suite); 25 | RegisterTest('Send action http', TTestActionHTTP.Suite); 26 | RegisterTest('Send file attachments', TTestAttachments.Suite); 27 | RegisterTest('Send action headers', TTestActionHeader.Suite); 28 | RegisterTest('Send emails', TTestEmail.Suite); 29 | RegisterTest('Send tags', TTestEmojis.Suite); 30 | RegisterTest('Send icons', TTestIcons.Suite); 31 | RegisterTest('Send url attachments', TTestURLAttachments.Suite); 32 | //RegisterTest('Subscription', TTestSubscription.Suite); 33 | 34 | 35 | end. 36 | -------------------------------------------------------------------------------- /tests/src/Test.Action.HTTP.pas: -------------------------------------------------------------------------------- 1 | unit Test.Action.HTTP; 2 | 3 | interface 4 | 5 | uses 6 | TestFramework, Notify; 7 | 8 | type 9 | TTestActionHTTP = class(TTestCase) 10 | private 11 | FActionType: TNotifyActionType; 12 | FLabel: String; 13 | FUrl: String; 14 | procedure CallBack(AEvent: INotifyEvent); 15 | public 16 | procedure SetUp; override; 17 | published 18 | procedure Publish; 19 | procedure Subscribe; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | Test.Constants, System.SysUtils; 26 | 27 | var 28 | Id, Time: String; 29 | 30 | { TTestActionHTTP } 31 | 32 | procedure TTestActionHTTP.CallBack(AEvent: INotifyEvent); 33 | begin 34 | FLabel := AEvent.Actions.Items[ACTION_LABEL].&Label; 35 | FUrl := AEvent.Actions.Items[ACTION_LABEL].Url; 36 | FActionType := AEvent.Actions.Items[ACTION_LABEL].&Type; 37 | end; 38 | 39 | procedure TTestActionHTTP.Publish; 40 | begin 41 | Ntfy := New.Notify; 42 | Ntfy.Notification( 43 | New.Notification 44 | .Topic(TOPIC) 45 | .Action(New.Action 46 | .&Type(TNotifyActionType.HTTP) 47 | .&Label(ACTION_LABEL) 48 | .Url(ACTION_URL) 49 | )); 50 | 51 | try 52 | try 53 | WriteLn('Action http test: Publish'); 54 | Sleep(TIME_DELAY); 55 | Ntfy.ClearFilters; 56 | Ntfy.BaseURL('http://localhost:80'); 57 | Ntfy.Publish; 58 | Id := Ntfy.Response.Data.Id; 59 | Time := Ntfy.Response.Data.Time.ToString; 60 | except on E: Exception do 61 | WriteLn(E.Message); 62 | end; 63 | finally 64 | CheckEquals( 65 | Ord(TStatusCode.OK), 66 | Ntfy.Response.StatusCode, 67 | MSG_REQUEST_FAILED 68 | ); 69 | end; 70 | end; 71 | 72 | procedure TTestActionHTTP.SetUp; 73 | begin 74 | 75 | end; 76 | 77 | procedure TTestActionHTTP.Subscribe; 78 | begin 79 | try 80 | try 81 | WriteLn('Action http test: Subscribe'); 82 | Sleep(TIME_DELAY); 83 | Ntfy := New.Notify; 84 | Ntfy.Poll(True); 85 | Ntfy.Since(Time); 86 | Ntfy.BaseURL('http://localhost:80'); 87 | Ntfy.Subscribe(TOPIC, CallBack); 88 | finally 89 | Ntfy.Unsubscribe; 90 | end; 91 | finally 92 | CheckEquals(Ord(FActionType), Ord(TNotifyActionType.HTTP), MSG_WRONG_HEADERS); 93 | CheckEquals(FLabel, ACTION_LABEL, MSG_WRONG_LABELS); 94 | CheckEquals(FUrl, ACTION_URL, MSG_WRONG_URLS); 95 | end; 96 | end; 97 | 98 | end. 99 | -------------------------------------------------------------------------------- /tests/src/Test.Action.Header.pas: -------------------------------------------------------------------------------- 1 | unit Test.Action.Header; 2 | 3 | interface 4 | 5 | uses 6 | TestFrameWork, Notify.Custom.Types, Notify; 7 | 8 | type 9 | 10 | TTestActionHeader = class(TTestCase) 11 | private 12 | FHeaders: TNotifyActionHeaders; 13 | FEventHeaders: TNotifyActionHeaders; 14 | procedure CallBack(AEvent: INotifyEvent); 15 | public 16 | procedure SetUp; override; 17 | procedure TearDown; override; 18 | published 19 | procedure Publish; 20 | procedure Subscribe; 21 | end; 22 | 23 | implementation 24 | 25 | uses 26 | Test.Constants, System.SysUtils; 27 | 28 | var 29 | Id, Time: String; 30 | 31 | { TestActionHeader } 32 | 33 | procedure TTestActionHeader.CallBack(AEvent: INotifyEvent); 34 | begin 35 | FEventHeaders := AEvent.Actions.Items[ACTION_LABEL].EventHeaders; 36 | end; 37 | 38 | procedure TTestActionHeader.Publish; 39 | begin 40 | 41 | Ntfy := New.Notify; 42 | Ntfy.Notification( 43 | New.Notification 44 | .Topic(TOPIC) 45 | .Action(New.Action 46 | .&Type(TNotifyActionType.VIEW) 47 | .&Label(ACTION_LABEL) 48 | .Url(ACTION_URL) 49 | .Headers(FHeaders) 50 | )); 51 | 52 | try 53 | try 54 | WriteLn('Action headers test: Publish'); 55 | Sleep(TIME_DELAY); 56 | Ntfy.BaseURL('http://localhost:80'); 57 | Ntfy.ClearFilters; 58 | Ntfy.Publish; 59 | Id := Ntfy.Response.Data.Id; 60 | Time := Ntfy.Response.Data.Time.ToString; 61 | except on E: Exception do 62 | WriteLn(E.Message); 63 | end; 64 | finally 65 | CheckEquals( 66 | Ord(TStatusCode.OK), 67 | Ntfy.Response.StatusCode, 68 | MSG_REQUEST_FAILED 69 | ); 70 | end; 71 | 72 | end; 73 | 74 | procedure TTestActionHeader.SetUp; 75 | begin 76 | inherited; 77 | FHeaders := TNotifyActionHeaders.Create; 78 | FHeaders.Cmd := 'systeminfo'; 79 | FHeaders.Parameter := '/FO LIST'; 80 | FHeaders.SystemDate := 'date'; 81 | end; 82 | 83 | procedure TTestActionHeader.Subscribe; 84 | begin 85 | try 86 | try 87 | WriteLn('Action headers test: Subscribe'); 88 | Sleep(TIME_DELAY); 89 | Ntfy := New.Notify; 90 | Ntfy.BaseURL('http://localhost:80'); 91 | Ntfy.Poll(True); 92 | Ntfy.Since(Time); 93 | Ntfy.Subscribe(TOPIC, CallBack); 94 | finally 95 | Ntfy.Unsubscribe; 96 | end; 97 | finally 98 | CheckEquals(FHeaders.Cmd, FEventHeaders.Cmd, MSG_WRONG_HEADERS); 99 | CheckEquals(FHeaders.Parameter, FEventHeaders.Parameter, MSG_WRONG_HEADERS); 100 | CheckEquals(FHeaders.SystemDate, FEventHeaders.SystemDate, MSG_WRONG_HEADERS); 101 | end; 102 | end; 103 | 104 | procedure TTestActionHeader.TearDown; 105 | begin 106 | inherited; 107 | FHeaders.Free; 108 | end; 109 | 110 | end. 111 | -------------------------------------------------------------------------------- /tests/src/Test.Action.View.pas: -------------------------------------------------------------------------------- 1 | unit Test.Action.View; 2 | 3 | interface 4 | 5 | uses 6 | TestFrameWork, Notify; 7 | 8 | type 9 | TTestActionView = class(TTestCase) 10 | private 11 | FActionType: TNotifyActionType; 12 | FLabel: String; 13 | FUrl: String; 14 | procedure CallBack(AEvent: INotifyEvent); 15 | public 16 | procedure SetUp; override; 17 | published 18 | procedure Publish; 19 | procedure Subscribe; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | System.SysUtils, Test.Constants; 26 | 27 | var 28 | Id, Time: String; 29 | 30 | { TTestActionView } 31 | 32 | procedure TTestActionView.CallBack(AEvent: INotifyEvent); 33 | begin 34 | FLabel := AEvent.Actions.Items[ACTION_LABEL].&Label; 35 | FUrl := AEvent.Actions.Items[ACTION_LABEL].Url; 36 | FActionType := AEvent.Actions.Items[ACTION_LABEL].&Type; 37 | end; 38 | 39 | procedure TTestActionView.Publish; 40 | begin 41 | Ntfy := New.Notify; 42 | Ntfy.Notification( 43 | New.Notification 44 | .Topic(TOPIC) 45 | .Action(New.Action 46 | .&Type(TNotifyActionType.VIEW) 47 | .&Label(ACTION_LABEL) 48 | .Url(ACTION_URL) 49 | )); 50 | 51 | try 52 | try 53 | WriteLn('Send action view: Publish'); 54 | Sleep(TIME_DELAY); 55 | Ntfy.ClearFilters; 56 | Ntfy.BaseURL('http://localhost:80'); 57 | Ntfy.Publish; 58 | Id := Ntfy.Response.Data.Id; 59 | Time := Ntfy.Response.Data.Time.ToString; 60 | except on E: Exception do 61 | WriteLn(E.Message); 62 | end; 63 | finally 64 | CheckEquals( 65 | Ord(TStatusCode.OK), 66 | Ntfy.Response.StatusCode, 67 | MSG_REQUEST_FAILED 68 | ); 69 | end; 70 | end; 71 | 72 | procedure TTestActionView.SetUp; 73 | begin 74 | inherited; 75 | end; 76 | 77 | procedure TTestActionView.Subscribe; 78 | begin 79 | try 80 | try 81 | WriteLn('Send action view: Subscribe'); 82 | Sleep(TIME_DELAY); 83 | Ntfy := New.Notify; 84 | Ntfy.BaseURL('http://localhost:80'); 85 | Ntfy.Poll(True); 86 | Ntfy.Since(Time); 87 | Ntfy.Subscribe(TOPIC, CallBack); 88 | finally 89 | Ntfy.Unsubscribe; 90 | end; 91 | finally 92 | CheckEquals(Ord(FActionType), Ord(TNotifyActionType.VIEW), MSG_WRONG_HEADERS); 93 | CheckEquals(FLabel, ACTION_LABEL, MSG_WRONG_LABELS); 94 | CheckEquals(FUrl, ACTION_URL, MSG_WRONG_URLS); 95 | end; 96 | end; 97 | 98 | end. 99 | -------------------------------------------------------------------------------- /tests/src/Test.Attachments.pas: -------------------------------------------------------------------------------- 1 | unit Test.Attachments; 2 | 3 | interface 4 | 5 | uses 6 | TestFramework, Notify; 7 | 8 | type 9 | TTestAttachments = class(TTestCase) 10 | private 11 | FFileName: String; 12 | FFilePath: String; 13 | procedure CallBack(AEvent: INotifyEvent); 14 | public 15 | procedure SetUp; override; 16 | published 17 | procedure Publish; 18 | procedure Subscribe; 19 | end; 20 | 21 | implementation 22 | 23 | uses 24 | Test.Constants, System.SysUtils; 25 | 26 | var 27 | Id, Time: String; 28 | 29 | { TTestAttachments } 30 | 31 | procedure TTestAttachments.CallBack(AEvent: INotifyEvent); 32 | begin 33 | CheckEquals(FFileName, AEvent.Attachment.Name, MSG_WRONG_FILENAME); 34 | end; 35 | 36 | procedure TTestAttachments.Publish; 37 | begin 38 | WriteLn('Send Attachment: Publish'); 39 | Ntfy := New.Notify; 40 | Ntfy.Notification( 41 | New.Notification 42 | .Topic(TOPIC) 43 | .AttachFile(FFilePath) 44 | ); 45 | 46 | try 47 | try 48 | Sleep(TIME_DELAY); 49 | Ntfy.BaseURL('http://localhost:80'); 50 | Ntfy.ClearFilters; 51 | Ntfy.Publish; 52 | Id := Ntfy.Response.Data.Id; 53 | Time := Ntfy.Response.Data.Time.ToString; 54 | except on E: Exception do 55 | WriteLn(E.Message); 56 | end; 57 | finally 58 | CheckEquals( 59 | Ord(TStatusCode.OK), 60 | Ntfy.Response.StatusCode, 61 | MSG_REQUEST_FAILED 62 | ); 63 | end; 64 | end; 65 | 66 | procedure TTestAttachments.SetUp; 67 | begin 68 | inherited; 69 | FFilePath := '..\..\img\delphi-notify.png'; 70 | FFileName := ExtractFileName(FFilePath); 71 | end; 72 | 73 | procedure TTestAttachments.Subscribe; 74 | begin 75 | WriteLn('Send Attachment: Subscribe'); 76 | try 77 | Sleep(TIME_DELAY); 78 | Ntfy := New.Notify; 79 | Ntfy.BaseURL('http://localhost:80'); 80 | Ntfy.Poll(True); 81 | Ntfy.Since(Time); 82 | Ntfy.Subscribe(TOPIC, CallBack); 83 | finally 84 | Ntfy.Unsubscribe; 85 | end; 86 | end; 87 | 88 | end. 89 | -------------------------------------------------------------------------------- /tests/src/Test.Constants.pas: -------------------------------------------------------------------------------- 1 | unit Test.Constants; 2 | 3 | interface 4 | 5 | uses 6 | Notify.Types; 7 | 8 | const TITLE = '⚡ Ntfy for Delphi'; 9 | const MESSAGECONTENT = 'A friendly Delphi library to ntfy.sh'; 10 | const TOPIC = 'notify-delphi-integration-8jh27d'; 11 | const TAGS: array [0..1] of String = ('construction', 'smiley'); 12 | const PRIORITY = Ord(TNotifyPriority.MAX); 13 | const ACTION_LABEL = 'Action Label'; 14 | const ACTION_URL = 'https://ntfy.sh'; 15 | const TIME_DELAY = 2153; //I finished the major test cases in 2022, Dec 31th at 21:53hs 16 | const MSG_WRONG_PRIORITY = 'Sent priority is different from the one received'; 17 | const MSG_WRONG_MESSAGE = 'Sent message is different from the one received'; 18 | const MSG_WRONG_TITLE = 'Sent title is different from the one received'; 19 | const MSG_REQUEST_FAILED = 'Response status code not expected'; 20 | const MSG_WRONG_HEADERS = 'Sent headers is different from the one received'; 21 | const MSG_WRONG_LABELS = 'Sent label is different from the one received'; 22 | const MSG_WRONG_URLS = 'Sent url is different from the one received'; 23 | const MSG_WRONG_FILENAME = 'Sent file name is different from the one received'; 24 | const MSG_WRONG_EMOJI = 'Sent emojis are different from the ones received'; 25 | const MSG_WRONG_ICON = 'Sent icon is different fron the one received'; 26 | const MSG_WRONG_URL_ATTACHMENT = 'Sent url attachment is different from the one received'; 27 | 28 | {$SCOPEDENUMS ON} 29 | type TStatusCode = ( 30 | OK = 200, 31 | SERVER_ERROR = 500, 32 | BAD_REQUEST = 400 33 | ); 34 | {$SCOPEDENUMS OFF} 35 | 36 | implementation 37 | 38 | end. 39 | -------------------------------------------------------------------------------- /tests/src/Test.Email.pas: -------------------------------------------------------------------------------- 1 | unit Test.Email; 2 | 3 | interface 4 | 5 | uses 6 | TestFramework, Notify; 7 | 8 | type 9 | TTestEmail = class(TTestCase) 10 | private 11 | FPriority: TNotifyPriority; 12 | FTitle: String; 13 | FMessageContent: String; 14 | procedure CallBack(AEvent: INotifyEvent); 15 | public 16 | procedure SetUp; override; 17 | published 18 | procedure Publish; 19 | procedure Subscribe; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | System.SysUtils, Test.Constants; 26 | 27 | var 28 | Id, Time: String; 29 | 30 | { TTestEmail } 31 | 32 | procedure TTestEmail.CallBack(AEvent: INotifyEvent); 33 | begin 34 | FTitle := AEvent.Title; 35 | FMessageContent := AEvent.MessageContent; 36 | FPriority := AEvent.Priority; 37 | end; 38 | 39 | procedure TTestEmail.Publish; 40 | begin 41 | try 42 | Ntfy := New.Notify; 43 | Ntfy.Notification( 44 | New.Notification 45 | .Topic(TOPIC) 46 | .Title(TITLE) 47 | .MessageContent(MESSAGECONTENT) 48 | .Priority(TNotifyPriority.HIGH) 49 | .Email('hazzelnuts.contact@gmail.com') 50 | ); 51 | 52 | try 53 | WriteLn('Send email: Publish'); 54 | Sleep(TIME_DELAY); 55 | Ntfy.BaseURL('http://localhost:80'); 56 | Ntfy.ClearFilters; 57 | Ntfy.Publish; 58 | Id := Ntfy.Response.Data.Id; 59 | Time := Ntfy.Response.Data.Time.ToString; 60 | except on E: Exception do 61 | Writeln(E.Message) 62 | end; 63 | finally 64 | CheckEquals( 65 | Ord(TStatusCode.OK), 66 | Ntfy.Response.StatusCode, 67 | MSG_REQUEST_FAILED 68 | ); 69 | end; 70 | end; 71 | 72 | procedure TTestEmail.SetUp; 73 | begin 74 | inherited; 75 | 76 | end; 77 | 78 | procedure TTestEmail.Subscribe; 79 | begin 80 | try 81 | try 82 | WriteLn('Send email: Subscribe'); 83 | Sleep(TIME_DELAY); 84 | Ntfy := New.Notify; 85 | Ntfy.BaseURL('http://localhost:80'); 86 | Ntfy.Poll(True); 87 | Ntfy.Since(Time); 88 | Ntfy.Subscribe(Topic, CallBack); 89 | finally 90 | Ntfy.Unsubscribe; 91 | end; 92 | finally 93 | CheckEquals(TITLE, FTitle, MSG_WRONG_TITLE); 94 | CheckEquals(MESSAGECONTENT, FMessageContent, MSG_WRONG_MESSAGE); 95 | CheckEquals(Ord(TNotifyPriority.HIGH), Ord(FPriority), MSG_WRONG_PRIORITY); 96 | end; 97 | end; 98 | 99 | end. 100 | -------------------------------------------------------------------------------- /tests/src/Test.Emojis.pas: -------------------------------------------------------------------------------- 1 | unit Test.Emojis; 2 | 3 | interface 4 | 5 | uses 6 | TestFrameWork, Notify; 7 | 8 | type 9 | TTestEmojis = class(TTestCase) 10 | private 11 | FEmojis: TArray; 12 | procedure CallBack(AEvent: INotifyEvent); 13 | public 14 | procedure SetUp; override; 15 | published 16 | procedure Publish; 17 | procedure Subscribe; 18 | end; 19 | 20 | implementation 21 | 22 | uses 23 | System.SysUtils, Test.Constants; 24 | 25 | var 26 | Id, Time: String; 27 | 28 | { TTestEmojis } 29 | 30 | procedure TTestEmojis.CallBack(AEvent: INotifyEvent); 31 | var 32 | Idx: Integer; 33 | begin 34 | for Idx := 0 to Length(AEvent.Tags) - 1 do 35 | CheckEquals(FEmojis[Idx], AEvent.Tags[Idx], MSG_WRONG_EMOJI); 36 | end; 37 | 38 | procedure TTestEmojis.Publish; 39 | begin 40 | Ntfy := New.Notify; 41 | Ntfy.Notification( 42 | New.Notification 43 | .Topic(TOPIC) 44 | .Tags(FEmojis) 45 | ); 46 | 47 | try 48 | try 49 | WriteLn('Send tags: Publish'); 50 | Sleep(TIME_DELAY); 51 | Ntfy.BaseURL('http://localhost:80'); 52 | Ntfy.ClearFilters; 53 | Ntfy.Publish; 54 | Id := Ntfy.Response.Data.Id; 55 | Time := Ntfy.Response.Data.Time.ToString; 56 | except on E: Exception do 57 | Writeln(E.Message) 58 | end; 59 | finally 60 | CheckEquals( 61 | Ord(TStatusCode.OK), 62 | Ntfy.Response.StatusCode, 63 | MSG_REQUEST_FAILED 64 | ); 65 | end; 66 | end; 67 | 68 | procedure TTestEmojis.SetUp; 69 | begin 70 | inherited; 71 | FEmojis := TArray.Create('warning', 'cd', 'skull'); 72 | end; 73 | 74 | procedure TTestEmojis.Subscribe; 75 | begin 76 | try 77 | WriteLn('Send tags: Subscribe'); 78 | Sleep(TIME_DELAY); 79 | Ntfy := New.Notify; 80 | Ntfy.BaseURL('http://localhost:80'); 81 | Ntfy.Poll(True); 82 | Ntfy.Since(Time); 83 | Ntfy.Subscribe(Topic, CallBack); 84 | finally 85 | Ntfy.Unsubscribe; 86 | end; 87 | end; 88 | 89 | end. 90 | -------------------------------------------------------------------------------- /tests/src/Test.Icons.pas: -------------------------------------------------------------------------------- 1 | unit Test.Icons; 2 | 3 | interface 4 | 5 | uses 6 | TestFramework, Notify; 7 | 8 | type 9 | TTestIcons = class(TTestCase) 10 | private 11 | FIconURL: String; 12 | const IconURL = 'https://styles.redditmedia.com/t5_32uhe/styles/communityIcon_xnt6chtnr2j21.png'; 13 | procedure CallBack(AEvent: INotifyEvent); 14 | public 15 | procedure SetUp; override; 16 | published 17 | procedure Publish; 18 | procedure Subscribe; 19 | end; 20 | 21 | implementation 22 | 23 | uses 24 | System.SysUtils, Test.Constants; 25 | 26 | var 27 | Id, Time: String; 28 | 29 | { TTestIcons } 30 | 31 | procedure TTestIcons.CallBack(AEvent: INotifyEvent); 32 | begin 33 | FIconURL := AEvent.Icon; 34 | end; 35 | 36 | procedure TTestIcons.Publish; 37 | begin 38 | 39 | Ntfy := New.Notify; 40 | Ntfy.Notification( 41 | New.Notification 42 | .Topic(TOPIC) 43 | .Icon(IconURL) 44 | ); 45 | 46 | try 47 | try 48 | WriteLn('Send icons: Publish'); 49 | Sleep(TIME_DELAY); 50 | Ntfy.BaseURL('http://localhost:80'); 51 | Ntfy.ClearFilters; 52 | Ntfy.Publish; 53 | Id := Ntfy.Response.Data.Id; 54 | Time := Ntfy.Response.Data.Time.ToString; 55 | except on E: Exception do 56 | Writeln(E.Message) 57 | end; 58 | finally 59 | CheckEquals( 60 | Ord(TStatusCode.OK), 61 | Ntfy.Response.StatusCode, 62 | MSG_REQUEST_FAILED 63 | ); 64 | end; 65 | end; 66 | 67 | procedure TTestIcons.SetUp; 68 | begin 69 | inherited; 70 | end; 71 | 72 | procedure TTestIcons.Subscribe; 73 | begin 74 | try 75 | try 76 | WriteLn('Send icons: Subscribe'); 77 | Sleep(TIME_DELAY); 78 | Ntfy := New.Notify; 79 | Ntfy.BaseURL('http://localhost:80'); 80 | Ntfy.Poll(True); 81 | Ntfy.Since(Time); 82 | Ntfy.Subscribe(Topic, CallBack); 83 | finally 84 | Ntfy.Unsubscribe; 85 | end; 86 | finally 87 | CheckEquals(FIconURL, IconURL, MSG_WRONG_ICON); 88 | end; 89 | end; 90 | 91 | end. 92 | -------------------------------------------------------------------------------- /tests/src/Test.Simple.Message.pas: -------------------------------------------------------------------------------- 1 | unit Test.Simple.Message; 2 | 3 | interface 4 | 5 | uses 6 | Notify, TestFramework; 7 | 8 | type 9 | TTestSimpleMessage = class(TTestCase) 10 | private 11 | FTitle: String; 12 | FMessage: String; 13 | FPriority: TNotifyPriority; 14 | procedure CallBack(AEvent: INotifyEvent); 15 | public 16 | procedure SetUp; override; 17 | published 18 | procedure Publish; 19 | procedure Subscribe; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | Test.Constants, Winapi.Windows, System.SysUtils; 26 | 27 | var 28 | Id, Time: String; 29 | 30 | { TTestSimpleMessage } 31 | 32 | procedure TTestSimpleMessage.CallBack(AEvent: INotifyEvent); 33 | begin 34 | FTitle := AEvent.Title; 35 | FMessage := AEvent.MessageContent; 36 | FPriority := AEvent.Priority; 37 | end; 38 | 39 | procedure TTestSimpleMessage.Publish; 40 | begin 41 | 42 | Ntfy := New.Notify; 43 | Ntfy.Notification( 44 | New.Notification 45 | .Topic(TOPIC) 46 | .Title(TITLE) 47 | .MessageContent(MESSAGECONTENT) 48 | .Priority(TNotifyPriority.MAX) 49 | ); 50 | 51 | try 52 | try 53 | WriteLn('Simple message test: Publish'); 54 | Sleep(TIME_DELAY); 55 | Ntfy.BaseURL('http://localhost:80'); 56 | Ntfy.ClearFilters; 57 | Ntfy.Publish; 58 | Id := Ntfy.Response.Data.Id; 59 | Time := Ntfy.Response.Data.Time.ToString; 60 | except on E: Exception do 61 | Writeln(E.Message) 62 | end; 63 | finally 64 | CheckEquals( 65 | Ord(TStatusCode.OK), 66 | Ntfy.Response.StatusCode, 67 | MSG_REQUEST_FAILED 68 | ); 69 | end; 70 | end; 71 | 72 | procedure TTestSimpleMessage.SetUp; 73 | begin 74 | inherited; 75 | 76 | end; 77 | 78 | procedure TTestSimpleMessage.Subscribe; 79 | begin 80 | try 81 | try 82 | WriteLn('Simple message test: Subscribe'); 83 | Sleep(TIME_DELAY); 84 | Ntfy := New.Notify; 85 | Ntfy.BaseURL('http://localhost:80'); 86 | Ntfy.Poll(True); 87 | Ntfy.Since(Time); 88 | Ntfy.Subscribe(Topic, CallBack); 89 | finally 90 | Ntfy.Unsubscribe; 91 | end; 92 | finally 93 | CheckEquals(TITLE, FTitle, MSG_WRONG_TITLE); 94 | CheckEquals(MESSAGECONTENT, FMessage, MSG_WRONG_MESSAGE); 95 | CheckEquals(Ord(PRIORITY), Ord(FPriority), MSG_WRONG_PRIORITY); 96 | end; 97 | end; 98 | 99 | end. 100 | -------------------------------------------------------------------------------- /tests/src/Test.Subscription.pas: -------------------------------------------------------------------------------- 1 | unit Test.Subscription; 2 | 3 | interface 4 | 5 | uses 6 | TestFramework, Notify, System.SyncObjs; 7 | 8 | type 9 | 10 | TMessageStatus = record 11 | received: Boolean; 12 | msg: string; 13 | constructor Create(AReceived: Boolean; AMsg: String); 14 | end; 15 | 16 | TMsgs = class 17 | public 18 | class var 19 | msg1m: TMessageStatus; 20 | msg2m: TMessageStatus; 21 | msg5m: TMessageStatus; 22 | msg10m: TMessageStatus; 23 | msg15m: TMessageStatus; 24 | msg30m: TMessageStatus; 25 | msg1h: TMessageStatus; 26 | msg3h: TMessageStatus; 27 | msg6h: TMessageStatus; 28 | msg12h: TMessageStatus; 29 | msg24h: TMessageStatus; 30 | class procedure Initialize; static; 31 | end; 32 | 33 | TTestSubscription = class(TTestCase) 34 | private 35 | Event: TEvent; 36 | FMsg: TMsgs; 37 | procedure CallBack(AEvent: INotifyEvent); 38 | published 39 | procedure Publish; 40 | procedure Subscribe; 41 | public 42 | constructor Create; 43 | destructor Destroy; 44 | end; 45 | 46 | implementation 47 | 48 | uses 49 | System.SysUtils, Test.Constants, System.Classes; 50 | 51 | { TTestSubscription } 52 | 53 | var 54 | Id, Time: String; 55 | 56 | 57 | procedure TTestSubscription.CallBack(AEvent: INotifyEvent); 58 | begin 59 | WriteLn(AEvent.Title); 60 | end; 61 | 62 | constructor TTestSubscription.Create; 63 | begin 64 | FMsg := TMsgs.Create; 65 | end; 66 | 67 | destructor TTestSubscription.Destroy; 68 | begin 69 | FMsg.Free; 70 | end; 71 | 72 | procedure TTestSubscription.Publish; 73 | var 74 | Id: String; 75 | Time: String; 76 | begin 77 | 78 | try 79 | try 80 | 81 | WriteLn('Long subscription, publishing messages for 1m, 2m, 5m, 10m, 15m, 30m, 1h, 3h, 6h, 12h and 24h'); 82 | Sleep(TIME_DELAY); 83 | 84 | Ntfy := New.Notify; 85 | Ntfy.BaseURL('http://localhost:80'); 86 | FMsg.Initialize; 87 | 88 | Ntfy.ClearFilters.Notification( 89 | New.Notification 90 | .Topic(TOPIC) 91 | .Delay(FMsg.msg1m.msg) 92 | .Title(FMsg.msg1m.msg) 93 | ).Publish; 94 | 95 | Ntfy.ClearFilters.Notification( 96 | New.Notification 97 | .Topic(TOPIC) 98 | .Delay(FMsg.msg2m.msg) 99 | .Title(FMsg.msg2m.msg) 100 | ).Publish; 101 | 102 | Ntfy.ClearFilters.Notification( 103 | New.Notification 104 | .Topic(TOPIC) 105 | .Delay(FMsg.msg5m.msg) 106 | .Title(FMsg.msg5m.msg) 107 | ).Publish; 108 | 109 | Ntfy.ClearFilters.Notification( 110 | New.Notification 111 | .Topic(TOPIC) 112 | .Delay(FMsg.msg10m.msg) 113 | .Title(FMsg.msg10m.msg) 114 | ).Publish; 115 | 116 | Ntfy.ClearFilters.Notification( 117 | New.Notification 118 | .Topic(TOPIC) 119 | .Delay(FMsg.msg15m.msg) 120 | .Title(FMsg.msg15m.msg) 121 | ).Publish; 122 | 123 | Ntfy.ClearFilters.Notification( 124 | New.Notification 125 | .Topic(TOPIC) 126 | .Delay(FMsg.msg30m.msg) 127 | .Title(FMsg.msg30m.msg) 128 | ).Publish; 129 | 130 | Ntfy.ClearFilters.Notification( 131 | New.Notification 132 | .Topic(TOPIC) 133 | .Delay(FMsg.msg1h.msg) 134 | .Title(FMsg.msg1h.msg) 135 | ).Publish; 136 | 137 | Ntfy.ClearFilters.Notification( 138 | New.Notification 139 | .Topic(TOPIC) 140 | .Delay(FMsg.msg3h.msg) 141 | .Title(FMsg.msg3h.msg) 142 | ).Publish; 143 | 144 | Ntfy.ClearFilters.Notification( 145 | New.Notification 146 | .Topic(TOPIC) 147 | .Delay(FMsg.msg6h.msg) 148 | .Title(FMsg.msg6h.msg) 149 | ).Publish; 150 | 151 | Ntfy.ClearFilters.Notification( 152 | New.Notification 153 | .Topic(TOPIC) 154 | .Delay(FMsg.msg12h.msg) 155 | .Title(FMsg.msg12h.msg) 156 | ).Publish; 157 | 158 | Ntfy.ClearFilters.Notification( 159 | New.Notification 160 | .Topic(TOPIC) 161 | .Delay(FMsg.msg24h.msg) 162 | .Title(FMsg.msg24h.msg) 163 | ).Publish; 164 | 165 | 166 | except on E: Exception do 167 | Writeln(E.Message) 168 | end; 169 | finally 170 | CheckEquals( 171 | Ord(TStatusCode.OK), 172 | Ntfy.Response.StatusCode, 173 | MSG_REQUEST_FAILED 174 | ); 175 | end; 176 | end; 177 | 178 | procedure TTestSubscription.Subscribe; 179 | begin 180 | try 181 | WriteLn('Long subscription, waiting for scheduled notifications...'); 182 | Sleep(TIME_DELAY); 183 | Ntfy := New.Notify; 184 | Ntfy.BaseURL('http://localhost:80').Subscribe(Topic, CallBack); 185 | finally 186 | Ntfy.Unsubscribe; 187 | end; 188 | end; 189 | 190 | { TMsgs } 191 | 192 | class procedure TMsgs.Initialize; 193 | begin 194 | msg1m := TMessageStatus.Create(False, '1m'); 195 | msg2m := TMessageStatus.Create(False, '2m'); 196 | msg5m := TMessageStatus.Create(False, '5m'); 197 | msg10m := TMessageStatus.Create(False, '10m'); 198 | msg15m := TMessageStatus.Create(False, '15m'); 199 | msg30m := TMessageStatus.Create(False, '30m'); 200 | msg1h := TMessageStatus.Create(False, '1h'); 201 | msg3h := TMessageStatus.Create(False, '3h'); 202 | msg6h := TMessageStatus.Create(False, '6h'); 203 | msg12h := TMessageStatus.Create(False, '12h'); 204 | msg24h := TMessageStatus.Create(False, '24h'); 205 | end; 206 | 207 | { TMessageStatus } 208 | 209 | constructor TMessageStatus.Create(AReceived: Boolean; AMsg: String); 210 | begin 211 | msg := AMsg; 212 | received := AReceived; 213 | end; 214 | 215 | end. 216 | -------------------------------------------------------------------------------- /tests/src/Test.URL.Attachments.pas: -------------------------------------------------------------------------------- 1 | unit Test.URL.Attachments; 2 | 3 | interface 4 | 5 | uses 6 | TestFramework, Notify; 7 | 8 | type 9 | TTestURLAttachments = class(TTestCase) 10 | private 11 | FURLAttachment: String; 12 | const URLAttachment = 13 | 'https://images.unsplash.com/photo-1670531910262-'+ 14 | '5ddb5ad666ea?crop=entropy&cs=tinysrgb&fit=crop&f'+ 15 | 'm=jpg&h=150&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx'+ 16 | '8MTY3MTE0OTI2NA&ixlib=rb-4.0.3&q=80&w=500'; 17 | procedure CallBack(AEvent: INotifyEvent); 18 | published 19 | procedure Publish; 20 | procedure Subscribe; 21 | end; 22 | 23 | 24 | implementation 25 | 26 | uses 27 | System.SysUtils, Test.Constants; 28 | 29 | var 30 | Id, Time: String; 31 | 32 | { TTestURLAttachments } 33 | 34 | procedure TTestURLAttachments.CallBack(AEvent: INotifyEvent); 35 | begin 36 | FURLAttachment := AEvent.Attachment.Url; 37 | end; 38 | 39 | procedure TTestURLAttachments.Publish; 40 | begin 41 | Ntfy := New.Notify; 42 | Ntfy.Notification( 43 | New.Notification 44 | .Topic(TOPIC) 45 | .AttachURL(URLAttachment) 46 | ); 47 | 48 | try 49 | try 50 | WriteLn('Send url attachments: Publish'); 51 | Sleep(TIME_DELAY); 52 | Ntfy.BaseURL('http://localhost:80'); 53 | Ntfy.ClearFilters; 54 | Ntfy.Publish; 55 | Id := Ntfy.Response.Data.Id; 56 | Time := Ntfy.Response.Data.Time.ToString; 57 | except on E: Exception do 58 | Writeln(E.Message) 59 | end; 60 | finally 61 | CheckEquals( 62 | Ord(TStatusCode.OK), 63 | Ntfy.Response.StatusCode, 64 | MSG_REQUEST_FAILED 65 | ); 66 | end; 67 | end; 68 | 69 | procedure TTestURLAttachments.Subscribe; 70 | begin 71 | try 72 | try 73 | WriteLn('Send url attachments: Subscribe'); 74 | Sleep(TIME_DELAY); 75 | Ntfy := New.Notify; 76 | Ntfy.BaseURL('http://localhost:80'); 77 | Ntfy.Poll(True); 78 | Ntfy.Since(Time); 79 | Ntfy.Subscribe(Topic, CallBack); 80 | finally 81 | Ntfy.Unsubscribe; 82 | end; 83 | finally 84 | CheckEquals(FURLAttachment, URLAttachment, MSG_WRONG_URL_ATTACHMENT); 85 | end; 86 | end; 87 | 88 | end. 89 | --------------------------------------------------------------------------------