├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── code_quality.yml │ ├── dotnet.yml │ └── publish.yml ├── .gitignore ├── .idea └── .idea.iGotifyNotificationAssist │ └── .idea │ ├── .gitignore │ ├── .name │ ├── encodings.xml │ ├── indexLayout.xml │ └── vcs.xml ├── CONTRIBUTING.md ├── Controller ├── DeviceController.cs └── VersionController.cs ├── Dockerfile ├── LICENSE ├── Models ├── DeviceModel.cs ├── GotifyMessage.cs ├── ServerVersion.cs └── Users.cs ├── Program.cs ├── Properties └── launchSettings.json ├── README.md ├── Services ├── DatabaseService.cs ├── GotifySocketService.cs ├── StartUpBuilder.cs ├── Tool.cs └── WebSockClient.cs ├── appsettings.Development.json ├── appsettings.json ├── docker-compose.yaml ├── iGotify Notification Assist.csproj ├── iGotifyNotificationAssist.sln ├── iGotifyNotificationAssist.sln.DotSettings.user ├── login_screen_1.png ├── login_screen_2.png ├── paypal-donate-icon-7_20.png └── qodana.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [androidseb25] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | #polar: # Replace with a single Polar username 13 | #buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | #thanks_dev: # Replace with a single thanks.dev username 15 | custom: ["https://www.paypal.com/donate/?hosted_button_id=VFSL9ZECRD6D6"] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: androidseb25 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | Language of description allowed: English/German 13 | 14 | **Expected behavior** 15 | A clear and concise description of what you expected to happen. 16 | 17 | **Screenshots** 18 | If applicable, add screenshots to help explain your problem. 19 | 20 | **Smartphone (please complete the following information):** 21 | - Device: [e.g. iPhone6] 22 | - OS: [e.g. iOS8.1] 23 | - Version of Container [e.g. 1.0.0.7] 24 | - Version of iGotify [e.g. 1.0.5] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE REQUEST]" 5 | labels: Feature Request 6 | assignees: androidseb25 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe. ** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | Language of description allowed: English/German 13 | 14 | **Describe the solution you'd like** 15 | A clear and concise description of what you want to happen. 16 | 17 | **Describe alternatives you've considered** 18 | A clear and concise description of any alternative solutions or features you've considered. 19 | 20 | **Additional context** 21 | Add any other context or screenshots about the feature request here. 22 | -------------------------------------------------------------------------------- /.github/workflows/code_quality.yml: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: # Specify your branches here 7 | #- main # The 'main' branch 8 | - 'releases/*' # The release branches 9 | 10 | jobs: 11 | qodana: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | pull-requests: write 16 | checks: write 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit 21 | fetch-depth: 0 # a full history is required for pull request analysis 22 | - name: 'Qodana Scan' 23 | uses: JetBrains/qodana-action@v2023.3 24 | env: 25 | QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release dotNet 2 | 3 | #on: [push] 4 | on: 5 | push: 6 | paths-ignore: 7 | - '*.md' 8 | - '.github/**' 9 | - '*.yaml' 10 | - '*.png' 11 | - 'LICENSE' 12 | 13 | jobs: 14 | release-project: 15 | name: Release application to GitHub 16 | strategy: 17 | matrix: 18 | kind: [ 'amd64', 'arm64', 'armv7' ] 19 | include: 20 | - kind: amd64 21 | target: linux-amd64 22 | release_name: "iGotify-Notification-Service-amd64" 23 | - kind: arm64 24 | target: linux-arm64 25 | release_name: "iGotify-Notification-Service-arm64" 26 | - kind: armv7 27 | target: linux-arm 28 | release_name: "iGotify-Notification-Service-arm" 29 | runs-on: ubuntu-latest 30 | needs: build-project 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | - name: Download Binaries 37 | uses: actions/download-artifact@v4 38 | with: 39 | name: ${{ matrix.target }} 40 | path: ${{ matrix.release_name }} 41 | - name: Archive binaries 42 | if: startsWith(github.ref, 'refs/tags/v') 43 | uses: thedoctor0/zip-release@master 44 | with: 45 | filename: ${{ matrix.release_name }}.zip 46 | path: ${{ matrix.release_name }} 47 | - name: Get latest Tag 48 | uses: actions-ecosystem/action-get-latest-tag@v1 49 | id: get-latest-tag 50 | - name: Get version informations 51 | id: infos 52 | run: | 53 | versionGit=${{ steps.get-latest-tag.outputs.tag }} 54 | versionProject=v$(grep '' 'iGotify Notification Assist.csproj' | cut -d '>' -f2 | cut -d '<' -f1 | xargs) 55 | if [ "$versionGit" != "$versionProject" -a $(git tag | grep -c "$versionProject") -eq 0 ]; then 56 | echo "version=$versionProject" >> $GITHUB_OUTPUT 57 | echo "createtag=true" >> $GITHUB_OUTPUT 58 | exit 0 59 | fi 60 | echo "version=$versionGit" >> $GITHUB_OUTPUT 61 | echo "createtag=false" >> $GITHUB_OUTPUT 62 | - name: Create GitHub Release 63 | if: steps.infos.outputs.createtag == 'true' 64 | id: create-new-release 65 | uses: actions/create-release@v1 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GH_RELEASE }} 68 | with: 69 | tag_name: ${{ steps.infos.outputs.version }} 70 | release_name: Release ${{ steps.infos.outputs.version }} 71 | - name: Get Release URL 72 | if: startsWith(github.ref, 'refs/tags/v') 73 | id: get-release-url 74 | uses: jossef/action-latest-release-info@v1.2.1 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GH_RELEASE }} 77 | - name: Upload asset to GitHub Release 78 | if: startsWith(github.ref, 'refs/tags/v') 79 | uses: actions/upload-release-asset@v1 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.GH_RELEASE }} 82 | with: 83 | upload_url: ${{ steps.get-release-url.outputs.upload_url }} 84 | asset_path: ./${{ matrix.release_name }}.zip 85 | asset_name: ${{ matrix.release_name }}-${{ steps.infos.outputs.version }}.zip 86 | asset_content_type: application/zip 87 | - name: Gotify Notification SUCCESS 88 | if: success() 89 | uses: eikendev/gotify-action@master 90 | with: 91 | gotify_api_base: ${{ secrets.API_BASE }} 92 | gotify_app_token: ${{ secrets.API_TOKEN }} 93 | notification_title: '[RELEASE] Create & Upload' 94 | notification_message: 'Release ${{ steps.infos.outputs.version }} successfully created and binaries uploaded.' 95 | notification_priority: 0 96 | - name: Gotify Notification FAILED 97 | if: failure() 98 | uses: eikendev/gotify-action@master 99 | with: 100 | gotify_api_base: ${{ secrets.API_BASE }} 101 | gotify_app_token: ${{ secrets.API_TOKEN }} 102 | notification_title: '[RELEASE] Create & Upload' 103 | notification_message: 'Release ${{ steps.infos.outputs.version }} failed and binaries not uploaded.' 104 | notification_priority: 9 105 | - name: Gotify Notification CANCELLED 106 | if: cancelled() 107 | uses: eikendev/gotify-action@master 108 | with: 109 | gotify_api_base: ${{ secrets.API_BASE }} 110 | gotify_app_token: ${{ secrets.API_TOKEN }} 111 | notification_title: '[RELEASE] Create & Upload' 112 | notification_message: 'Release ${{ steps.infos.outputs.version }} cancelled by user and binaries not uploaded.' 113 | notification_priority: 5 114 | 115 | 116 | build-project: 117 | #if: github.event.pull_request.merged == true 118 | name: Build dotNet 119 | strategy: 120 | matrix: 121 | dotnet-version: [ '9.x' ] 122 | kind: [ 'amd64', 'arm64', 'armv7' ] 123 | include: 124 | - kind: amd64 125 | target: linux-amd64 126 | release_name: "iGotify-Notification-Service-amd64" 127 | - kind: arm64 128 | target: linux-arm64 129 | release_name: "iGotify-Notification-Service-arm64" 130 | - kind: armv7 131 | target: linux-arm 132 | release_name: "iGotify-Notification-Service-arm" 133 | runs-on: ubuntu-latest 134 | steps: 135 | - name: Checkout code 136 | uses: actions/checkout@v4 137 | 138 | - name: Setup dotnet ${{ matrix.dotnet-version }} 139 | uses: actions/setup-dotnet@v3 140 | with: 141 | dotnet-version: ${{ matrix.dotnet-version }} 142 | 143 | - name: Display dotnet version 144 | run: dotnet --version 145 | 146 | - name: dotNet Restore 147 | run: dotnet restore "./iGotify Notification Assist.csproj" --runtime ${{ matrix.target }} 148 | 149 | - name: dotNet Build 150 | run: dotnet build "./iGotify Notification Assist.csproj" --runtime ${{ matrix.target }} -o "./${{ matrix.release_name }}/build" 151 | 152 | - name: dotNet Publish 153 | run: dotnet publish "./iGotify Notification Assist.csproj" -c Release --runtime ${{ matrix.target }} -o "./${{ matrix.release_name }}/publish" 154 | 155 | # - name: Upload coverage reports to Codecov 156 | # uses: codecov/codecov-action@v4.0.1 157 | # with: 158 | # token: ${{ secrets.CODECOV_TOKEN }} 159 | # slug: androidseb25/iGotify-Notification-Assistent 160 | 161 | - name: Upload Binaries ${{ matrix.target }} 162 | uses: actions/upload-artifact@v4 163 | with: 164 | name: ${{ matrix.target }} 165 | path: "./${{ matrix.release_name }}/publish" 166 | - name: Gotify Notification SUCCESS 167 | if: success() 168 | uses: eikendev/gotify-action@master 169 | with: 170 | gotify_api_base: ${{ secrets.API_BASE }} 171 | gotify_app_token: ${{ secrets.API_TOKEN }} 172 | notification_title: '[DOTNET] Build & Upload Artifacts' 173 | notification_message: 'DotNet build successfully on target: ${{ matrix.target }}.\nArtifact uploaded.' 174 | notification_priority: 0 175 | - name: Gotify Notification FAILED 176 | if: failure() 177 | uses: eikendev/gotify-action@master 178 | with: 179 | gotify_api_base: ${{ secrets.API_BASE }} 180 | gotify_app_token: ${{ secrets.API_TOKEN }} 181 | notification_title: '[DOTNET] Build & Upload Artifacts' 182 | notification_message: 'DotNet build failed on target: ${{ matrix.target }}.\nArtifact not uploaded.' 183 | notification_priority: 9 184 | - name: Gotify Notification CANCELLED 185 | if: cancelled() 186 | uses: eikendev/gotify-action@master 187 | with: 188 | gotify_api_base: ${{ secrets.API_BASE }} 189 | gotify_app_token: ${{ secrets.API_TOKEN }} 190 | notification_title: '[DOTNET] Build & Upload Artifacts' 191 | notification_message: 'DotNet build cancelled by user on target: ${{ matrix.target }}.\nArtifact not uploaded.' 192 | notification_priority: 5 193 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | #on: [ push ] 4 | on: 5 | push: 6 | paths-ignore: 7 | - '*.md' 8 | - '.github/**' 9 | - '*.yaml' 10 | - '*.png' 11 | - 'LICENSE' 12 | 13 | jobs: 14 | build_arm64_images: 15 | #if: startsWith(github.ref, 'refs/tags/v') 16 | #if: github.event.pull_request.merged == true 17 | name: "Build Multi Arch Images" 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout Code 21 | uses: actions/checkout@v2 22 | - name: Extract branch name 23 | shell: bash 24 | run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT 25 | id: extract_branch 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v3 28 | with: 29 | version: latest 30 | - name: Login to GitHub Container Registry 31 | uses: docker/login-action@v1 32 | with: 33 | registry: ghcr.io 34 | username: ${{ github.actor }} 35 | password: ${{ secrets.DOCKER_GIT_SEC }} 36 | - name: Run Buildx & Push Multi Arch for dev 37 | if: steps.extract_branch.outputs.branch == 'dev' 38 | run: | 39 | docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t ghcr.io/androidseb25/igotify-notification-assist-dev:latest -f ./Dockerfile --provenance=false --sbom=false --output type=image,push=true . 40 | - name: Run Buildx & Push Multi Arch for public 41 | if: steps.extract_branch.outputs.branch != 'dev' 42 | run: | 43 | docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t ghcr.io/androidseb25/igotify-notification-assist:latest -f ./Dockerfile --provenance=false --sbom=false --output type=image,push=true . 44 | - name: Gotify Notification SUCCESS 45 | if: success() 46 | uses: eikendev/gotify-action@master 47 | with: 48 | gotify_api_base: ${{ secrets.API_BASE }} 49 | gotify_app_token: ${{ secrets.API_TOKEN }} 50 | notification_title: '[DOCKER] iGotify Notification Assist' 51 | notification_message: 'Build success. DEV-Build: ${{ steps.extract_branch.outputs.branch }}' 52 | notification_priority: 0 53 | - name: Gotify Notification FAILED 54 | if: failure() 55 | uses: eikendev/gotify-action@master 56 | with: 57 | gotify_api_base: ${{ secrets.API_BASE }} 58 | gotify_app_token: ${{ secrets.API_TOKEN }} 59 | notification_title: '[DOCKER] iGotify Notification Assist' 60 | notification_message: 'Build failed. DEV-Build: ${{ steps.extract_branch.outputs.branch }}' 61 | notification_priority: 9 62 | - name: Gotify Notification CANCELLED 63 | if: cancelled() 64 | uses: eikendev/gotify-action@master 65 | with: 66 | gotify_api_base: ${{ secrets.API_BASE }} 67 | gotify_app_token: ${{ secrets.API_TOKEN }} 68 | notification_title: '[DOCKER] iGotify Notification Assist' 69 | notification_message: 'Build cancelled. DEV-Build: ${{ steps.extract_branch.outputs.branch }}' 70 | notification_priority: 5 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.idea/.idea.iGotifyNotificationAssist/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /.idea.iGotifyNotificationAssist.iml 7 | /projectSettingsUpdater.xml 8 | /contentModel.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.iGotifyNotificationAssist/.idea/.name: -------------------------------------------------------------------------------- 1 | iGotifyNotificationAssist -------------------------------------------------------------------------------- /.idea/.idea.iGotifyNotificationAssist/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.iGotifyNotificationAssist/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.iGotifyNotificationAssist/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to iGotify-Notification-Assistent 2 | 3 | Thank you for considering contributing to the iGotify-Notification-Assistent open source project. Your support is greatly appreciated and will help us make this project even better. There are many ways you can help, from reporting bugs and improving the documentation to contributing code changes. 4 | 5 | ## Reporting Bugs 6 | 7 | Before you submit a bug report, please search the [existing issues](https://github.com/androidseb25/iGotify-Notification-Assistent/issues) to see if the problem has already been reported. If it has, please add any additional information you have to the existing issue. 8 | 9 | If you can't find an existing issue for your problem, please open a new issue and follow the checkpoints in the template. 10 | 11 | ## Contributing Code 12 | 13 | We welcome code contributions to the project. If you are interested in contributing, please follow these steps: 14 | 15 | 1. Fork this repository to your own GitHub account 16 | 2. Clone the repository to your local machine 17 | 3. Create a new branch for your changes (e.g. `this-amazing-feature` or `this-needed-bugfix`) 18 | 4. Make your changes and commit them to your branch 19 | 5. Push your branch to your fork on GitHub 20 | 6. Open a pull request from your branch to the `dev` branch of this repository 21 | 22 | Please make sure your pull request includes the following: 23 | 24 | - A clear and descriptive title 25 | - A description of the changes you have made 26 | - Any relevant issue numbers (e.g. "Fixes #123") 27 | - A list of any dependencies your changes require 28 | 29 | We will review your pull request and provide feedback as soon as possible. Thank you for your contribution! 30 | 31 | ### Project setup 32 | 1. Use one of you're favorite C# IDE's e.g. VS Code, Visual Studio or Jetbrain's Rider 33 | 2. Please make sure you have running an reachable instance of gotify 34 | 3. Add needed informations in all `#DEBUG` cases 35 | 4. Run the api in http or https local on you're machine and show if it will connect to you're gotify instance 36 | 37 | By sharing ideas and code with the community, either through GitHub or via DM under Discord, you agree that these contributions become the property of the community and may be implemented into the open source code. 38 | -------------------------------------------------------------------------------- /Controller/DeviceController.cs: -------------------------------------------------------------------------------- 1 | using iGotify_Notification_Assist.Models; 2 | using iGotify_Notification_Assist.Services; 3 | using Microsoft.AspNetCore.Mvc; 4 | using SecNtfyNuGet; 5 | using Environments = iGotify_Notification_Assist.Services.Environments; 6 | 7 | namespace iGotify_Notification_Assist.Controller; 8 | 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class DeviceController : ControllerBase 12 | { 13 | /// 14 | /// Add device token to the TXT for sending the push notificcation 15 | /// 16 | /// 17 | /// 18 | [HttpPost] 19 | public async Task PostDeviceModel(DeviceModel deviceModel) 20 | { 21 | string result; 22 | bool resultBool; 23 | 24 | Console.WriteLine($"ClientToken: {deviceModel.ClientToken}"); 25 | Console.WriteLine($"DeviceToken: {deviceModel.DeviceToken}"); 26 | Console.WriteLine($"GotifyUrl: {deviceModel.GotifyUrl}"); 27 | 28 | if ( 29 | deviceModel.ClientToken.Length == 0 || deviceModel.ClientToken == "string" || 30 | deviceModel.DeviceToken.Length == 0 || deviceModel.DeviceToken.Length < 60 || deviceModel.DeviceToken == "string" || 31 | deviceModel.GotifyUrl.Length == 0 || deviceModel.GotifyUrl == "string" 32 | ) 33 | { 34 | result = "Fehler beim hinzugefügen des Gerätes!"; 35 | resultBool = false; 36 | return Ok(new { Message = result, Successful = resultBool }); 37 | } 38 | 39 | if (await deviceModel.Insert()) 40 | { 41 | GotifySocketService.getInstance(); 42 | GotifySocketService.StartWsThread(deviceModel.GotifyUrl, deviceModel.ClientToken); 43 | result = "Gerät erfolgreich hinzugefügt"; 44 | resultBool = true; 45 | } else { 46 | result = "Fehler beim hinzugefügen des Gerätes!"; 47 | resultBool = false; 48 | } 49 | 50 | return Ok(new { Message = result, Successful = resultBool }); 51 | } 52 | 53 | /// 54 | /// Delete device from TXT when loggin out from iGotify 55 | /// 56 | /// 57 | /// 58 | [HttpDelete] 59 | public async Task DeleteDevcice(string token) 60 | { 61 | string result; 62 | bool resultBool; 63 | 64 | Console.WriteLine($"Delete Token: {token}"); 65 | if (token.Length == 0 || token == "string") 66 | { 67 | result = "Fehler beim löschen des Gerätes!"; 68 | resultBool = false; 69 | return Ok(new { Message = result, Successful = resultBool }); 70 | } 71 | 72 | var deviceModel = new DeviceModel { ClientToken = token }; 73 | var usr = await DatabaseService.GetUser(token); 74 | if (await deviceModel.Delete()) 75 | { 76 | if (usr.Uid > 0) 77 | { 78 | GotifySocketService.getInstance(); 79 | GotifySocketService.KillWsThread(usr.ClientToken); 80 | } 81 | 82 | result = "Gerät erfolgreich gelöscht"; 83 | resultBool = true; 84 | } else { 85 | result = "Fehler beim löschen des Gerätes!"; 86 | resultBool = false; 87 | } 88 | 89 | return Ok(new { Message = result, Successful = resultBool }); 90 | } 91 | 92 | [HttpGet("Test/{deviceToken}")] 93 | public async Task Test(string deviceToken) 94 | { 95 | var ntfy = new SecNtfy(Environments.secNtfyUrl); 96 | if (deviceToken.Length > 0) 97 | _ = await ntfy.SendNotification(deviceToken, "Test", "Test Nachricht"); 98 | if (Environments.isLogEnabled) 99 | Console.WriteLine(ntfy.encTitle); 100 | 101 | return Ok(); 102 | } 103 | } -------------------------------------------------------------------------------- /Controller/VersionController.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using iGotify_Notification_Assist.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace iGotify_Notification_Assist.Controller; 6 | 7 | [ApiController] 8 | [Route("[controller]")] 9 | public class VersionController : ControllerBase 10 | { 11 | /// 12 | /// Get the current version of the container 13 | /// 14 | /// 15 | [HttpGet] 16 | public IActionResult GetVersion() 17 | { 18 | var sv = new ServerVersion { version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "" }; 19 | var buildDate = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime; 20 | sv.buildDate = buildDate.ToString("yyyy-MM-dd'T'HH:mm:ss"); 21 | sv.commit = Programms.StartUpCommit; 22 | return Ok(sv); 23 | } 24 | } 25 | 26 | public static class Programms 27 | { 28 | public static readonly string? StartUpCommit = Guid.NewGuid().ToString().Replace("-", ""); 29 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # See https://devblogs.microsoft.com/dotnet/improving-multiplatform-container-support/ 2 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base 3 | WORKDIR /app 4 | EXPOSE 5047 5 | EXPOSE 7221 6 | 7 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build 8 | ARG TARGETARCH 9 | WORKDIR /src 10 | 11 | # copy csproj and restore as distinct layers 12 | COPY ["iGotify Notification Assist.csproj", "."] 13 | RUN dotnet restore "./iGotify Notification Assist.csproj" -a $TARGETARCH 14 | 15 | # copy everything else and build app 16 | COPY . . 17 | WORKDIR "/src/." 18 | RUN dotnet build "./iGotify Notification Assist.csproj" -c Release -a $TARGETARCH -o /app/build 19 | 20 | FROM build AS publish 21 | RUN dotnet publish "./iGotify Notification Assist.csproj" -c Release -a $TARGETARCH -o /app/publish 22 | 23 | # final stage/image 24 | FROM base AS final 25 | WORKDIR /app 26 | COPY --from=publish /app/publish . 27 | # USER $APP_UID 28 | ENTRYPOINT ["dotnet", "iGotify Notification Assist.dll"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 androidseb25 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 | -------------------------------------------------------------------------------- /Models/DeviceModel.cs: -------------------------------------------------------------------------------- 1 | using iGotify_Notification_Assist.Services; 2 | using SecNtfyNuGet; 3 | using Websocket.Client; 4 | using Environments = iGotify_Notification_Assist.Services.Environments; 5 | 6 | namespace iGotify_Notification_Assist.Models; 7 | 8 | public class DeviceModel 9 | { 10 | public string ClientToken { get; set; } = ""; 11 | public string DeviceToken { get; set; } = ""; 12 | public string GotifyUrl { get; set; } = ""; 13 | 14 | /// 15 | /// Add device token to txt file 16 | /// 17 | /// 18 | public async Task Insert() 19 | { 20 | if (!await DatabaseService.CheckIfUserExists(this)) 21 | return await DatabaseService.InsertUser(this); 22 | else 23 | return false; 24 | } 25 | 26 | /// 27 | /// delete device token 28 | /// 29 | /// 30 | public async Task Delete() 31 | { 32 | return await DatabaseService.DeleteUser(ClientToken); 33 | } 34 | 35 | /// 36 | /// Send the passed notification from the gotify instance that was passed via WebSocket 37 | /// 38 | /// 39 | /// 40 | public async Task SendNotifications(GotifyMessage iGotifyMessage, WebsocketClient webSock) 41 | { 42 | var title = iGotifyMessage.title; 43 | var msg = iGotifyMessage.message; 44 | 45 | var protocol = webSock.Url.ToString().Contains("ws://") ? "http://" : "https://"; 46 | var gotifyServerUrl = webSock.Url.ToString().Replace("ws://", "").Replace("wss://", "").Replace("\"", "").Split("/stream"); 47 | var imageUrl = gotifyServerUrl.Length > 0 ? $"{protocol}{gotifyServerUrl[0]}$$${iGotifyMessage.appid}$$${webSock.Name}" : ""; 48 | 49 | var usr = await DatabaseService.GetUser(webSock.Name!); 50 | 51 | if (usr.Uid == 0) 52 | { 53 | Console.WriteLine("THERE'S SOMETHING WRONG HERE? NO USER FOUND"); 54 | } 55 | 56 | var ntfy = new SecNtfy(Environments.secNtfyUrl); 57 | _ = ntfy.SendNotification(usr.DeviceToken, title, msg, iGotifyMessage.priority == 10, imageUrl, iGotifyMessage.priority); 58 | } 59 | } -------------------------------------------------------------------------------- /Models/GotifyMessage.cs: -------------------------------------------------------------------------------- 1 | namespace iGotify_Notification_Assist.Models; 2 | 3 | public class GotifyMessage 4 | { 5 | public int id { get; set; } 6 | public int appid { get; set; } 7 | public string? message { get; set; } 8 | public string? title { get; set; } 9 | public int priority { get; set; } 10 | public GotifyExtras? extras { get; set; } 11 | public string? date { get; set; } 12 | } 13 | 14 | public class GotifyExtras 15 | { 16 | public ClientDisplay? clientdisplay { get; set; } 17 | public ClientNotification? clientnotification { get; set; } 18 | public ClientAndroidAction? androidaction { get; set; } 19 | } 20 | 21 | public class ClientDisplay 22 | { 23 | public string? contentType { get; set; } 24 | } 25 | 26 | public class ClientNotification 27 | { 28 | public string? bigImageUrl { get; set; } 29 | public ClientNotificationClick? click { get; set; } 30 | } 31 | 32 | public class ClientNotificationClick 33 | { 34 | public string? url { get; set; } 35 | } 36 | 37 | public class ClientAndroidAction 38 | { 39 | public ClientAndroidActionOnReceive? onReceive { get; set; } 40 | } 41 | 42 | public class ClientAndroidActionOnReceive 43 | { 44 | public string? intentUrl { get; set; } 45 | } -------------------------------------------------------------------------------- /Models/ServerVersion.cs: -------------------------------------------------------------------------------- 1 | namespace iGotify_Notification_Assist.Models; 2 | 3 | public class ServerVersion 4 | { 5 | public string? version { get; set; } = ""; 6 | public string? commit { get; set; } = ""; 7 | public string? buildDate { get; set; } = ""; 8 | } -------------------------------------------------------------------------------- /Models/Users.cs: -------------------------------------------------------------------------------- 1 | namespace iGotify_Notification_Assist.Models; 2 | 3 | public class Users 4 | { 5 | public int Uid { get; init; } 6 | public string ClientToken { get; init; } = ""; 7 | public string DeviceToken { get; init; } = ""; 8 | public string GotifyUrl { get; init; } = ""; 9 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using iGotify_Notification_Assist.Services; 2 | using Microsoft.AspNetCore.Http.Json; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.OpenApi.Models; 5 | using Scalar.AspNetCore; 6 | using Environments = iGotify_Notification_Assist.Services.Environments; 7 | 8 | var builder = WebApplication.CreateBuilder(args); 9 | 10 | // Add services to the container. 11 | builder.Services.AddCors(); 12 | 13 | builder.Services.AddControllers(opt => 14 | { 15 | opt.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true; 16 | }) 17 | .AddJsonOptions(opt => 18 | { 19 | opt.JsonSerializerOptions.PropertyNamingPolicy = null; 20 | opt.JsonSerializerOptions.PropertyNameCaseInsensitive = true; 21 | }); 22 | builder.Services.Configure(options => 23 | { 24 | options.SerializerOptions.PropertyNameCaseInsensitive = true; // Enable case-insensitivity 25 | options.SerializerOptions.PropertyNamingPolicy = null; // Preserve exact casing 26 | }); 27 | 28 | builder.Services.AddSingleton(builder.Configuration); 29 | builder.Services.AddOpenApi(); 30 | builder.Services.AddTransient(); 31 | 32 | var app = builder.Build(); 33 | app.UsePathBase("/api"); 34 | 35 | app.UseCors(x => x 36 | .WithOrigins("http://localhost:4200") 37 | .AllowAnyOrigin() 38 | .AllowAnyMethod() 39 | .AllowAnyHeader() 40 | ); 41 | 42 | app.UseHttpsRedirection(); 43 | 44 | app.UseRouting(); 45 | 46 | if (Environments.enableScalarUi) 47 | { 48 | app.MapOpenApi(); 49 | app.MapScalarApiReference(options => 50 | { 51 | options.WithTitle("iGotify Notification Assist API") 52 | .WithModels(false) 53 | .WithLayout(ScalarLayout.Classic) 54 | .WithTheme(ScalarTheme.Moon) 55 | .WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient); 56 | }); 57 | } 58 | 59 | //app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); 60 | 61 | app.MapControllers(); 62 | 63 | app.Run(); -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:43494", 8 | "sslPort": 44387 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "scalar/v1", 17 | "applicationUrl": "http://localhost:5047", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "scalar/v1", 27 | "applicationUrl": "https://localhost:7221;http://localhost:5047", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "scalar/v1", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Donate with PayPal](https://raw.githubusercontent.com/androidseb25/iGotify-Notification-Assistent/main/paypal-donate-icon-7_20.png)](https://www.paypal.com/donate/?hosted_button_id=VFSL9ZECRD6D6) 2 | ![Stars](https://img.shields.io/github/stars/androidseb25/iGotify-Notification-Assistent?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAEsWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgdGlmZjpJbWFnZUxlbmd0aD0iNDgiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNDgiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjcyLzEiCiAgIHRpZmY6WVJlc29sdXRpb249IjcyLzEiCiAgIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSI0OCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjQ4IgogICBleGlmOkNvbG9yU3BhY2U9IjEiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjMtMDQtMTlUMTc6NDE6MDIrMDI6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMDQtMTlUMTc6NDE6MDIrMDI6MDAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWZmaW5pdHkgUGhvdG8gMiAyLjAuNCIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wNC0xOVQxNzo0MTowMiswMjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+0oDKYwAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHfK4NRGMc/hiamKS5cuFgaV5tmanGjTEItaaYMN9u7X2o/3t53S8utcqsocePXBX8Bt8q1UkRKbrkmbliv591WW7Ln9Jznc77nPE/nPAcsobSS0Vs8kMnmteC037EUXnZYX2mhGzuDWCOKrk7MzwdoaF8PNJnxzm3WanzuX+uIxXUFmtqExxVVywvPCAfW86rJu8I9SioSEz4XdmlyQeF7U49W+M3kZIV/TNZCwUmwdAk7knUcrWMlpWWE5eU4M+mCUr2P+RJbPLu4ILFfvA+dINP4cTDLFJP4GGZMZh9uvAzJigb5nnL+HDnJVWRWKaKxRpIUeVyiFqR6XGJC9LiMNEWz/3/7qidGvJXqNj+0vhjGxwBYd6C0bRjfx4ZROoHmZ7jK1vJzRzD6Kfp2TXMegn0TLq5rWnQPLreg90mNaJGy1CxuSSTg/Qw6w9B9C+0rlZ5V9zl9hNCGfNUN7B/AoJy3r/4CMN5nzbh35xIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAGYSURBVGiB7VjtjcMgDH2cbhx2KJ2syWSlO2Qf+sd3iji+7NhJT+JJVSKCsY39sCkwMTExMTFhhLRhSRvC1XaIQMYn+i1Wer6sFgZw270/rJSYOEBpE7KxxUKXVQTMdjyH016Qdv9Z+Xx3HlFTn0UEWrsftJVZRCA1FXpdnaoRGDnztcmsuhtpwxMDaaIZBbUIlI7OxtxFSy9rJ7IU2b/fwCNopOerMAbOSVV1gIx9MA3TRqTnWnPquyF8tfHI9MfShBYHXo1vZ6NqS5MDRLbT2oIKVufrpO+S+GInmsYDg6dQp7+xQtd4gHGMnuzEcNPHroij1VaI6DzuHAF2JSYFkSs3gJVrPCBsJUjRKpGtYCjfSzjSC8UDsmprWV7qOQhSwSMOXF3gABy4D/RuXmxDhHcEUQQ+6d+2T+GA+JIjdYCT/1GoYwiWEVidh2PUjFt/yl+IiNMhcLEojdzwJERmR6BD4GpFdf63z6m2IhIeSFIoFMZ+0qVrwM4RlVbkKAeGDc9BMnk0YmmuOtKGoFkLtNebmJj4R3gDsdt6T8W+vnEAAAAASUVORK5CYII=) 3 | [![Crowdin](https://badges.crowdin.net/igotify/localized.svg)](https://crowdin.com/project/igotify) 4 | [![Qodana](https://github.com/androidseb25/iGotify-Notification-Assistent/actions/workflows/code_quality.yml/badge.svg?branch=main)](https://github.com/androidseb25/iGotify-Notification-Assistent/actions/workflows/code_quality.yml) 5 | 6 | # iGotify 7 | 8 | A small Gotify server notification assistent that decrypt the message and trigger a Push Notifications to iOS Devices via Apple's APNs with my service SecNtfy 9 | 10 | Download Link to iGotify down below 11 | 12 |   13 | ## ⭐ Features 14 | 15 | * show received notifications with markdown 16 | * decrypted the message with a public key that is generated from the iGotify device 17 | * sending the decrypted message to SecNtfy and forwarded it to Apple's APN service, without saving the payload 18 | * multiuser support 19 | 20 |   21 | ## 🔧 How to Install Gotify & iGotify-Notification-Assist 22 | 23 | ### 🐳 Docker `docker-compose.yaml` 24 | 25 | ### Installation 26 | 27 | 1. Create a file with the name `docker-compose.yaml` or clone this repo and go to step 3 28 | 2. Please use the latest and recommended version of docker and docker compose 29 | 3. Copy the code down below in the yaml file 30 | 4. change environment variables in the compose file 31 | 5. execute `docker compose up -d` to start the docker compose 32 | 33 |   34 | ### Needed environment variables 35 | 36 | * `GOTIFY_DEFAULTUSER_PASS` = the user password for the defaultuser 37 | 38 | ### Optional environment variables 39 | 40 | * `GOTIFY_URLS` = the local gotify sever URL e.g.: `http://gotify` 41 | * `GOTIFY_CLIENT_TOKENS` = the client token from the Gotify Client e.g.: `cXXXXXXXX` 42 | * `SECNTFY_TOKENS` = the SecNtfy Token that you get from the app after configure it e.g.: `NTFY-DEVICE-XXXXXX` 43 | * `ENABLE_CONSOLE_LOG` = you can disable unnecessary console logs (default: true) 44 | * `ENABLE_SCALAR_UI` = you can now disable the Endpoint page (default: true) 45 | 46 | *please write the boolean variables (true, false) in single quotes 'true'* 47 | 48 | #### All these configuration can be found after configure the app. It will display it for you 49 | #### Please note you can configure multiple instances of local gotify server by adding a semicolon `;` after each environment value e.g.: 50 | 51 | * `GOTIFY_URLS: 'http://gotify;http://gotify2;http://gotify3;...'` 52 | * `GOTIFY_CLIENT_TOKENS: 'cXXXXXXXX1;cXXXXXXXX2;cXXXXXXXX3;...'` 53 | * `SECNTFY_TOKENS: 'NTFY-DEVICE-XXXXXX1;NTFY-DEVICE-XXXXXX2;NTFY-DEVICE-XXXXXX3;...'` 54 | 55 |   56 | 57 | ```bash 58 | version: '3.8' 59 | 60 | services: 61 | gotify: 62 | container_name: gotify 63 | hostname: gotify 64 | image: gotify/server 65 | restart: unless-stopped 66 | security_opt: 67 | - no-new-privileges:true 68 | networks: 69 | - net 70 | ports: 71 | - "8680:80" 72 | volumes: 73 | - data:/app/data 74 | environment: 75 | GOTIFY_DEFAULTUSER_PASS: 'my-very-strong-password' # Change me!!!!! 76 | 77 | igotify: 78 | container_name: igotify 79 | hostname: igotify 80 | image: ghcr.io/androidseb25/igotify-notification-assist:latest 81 | restart: unless-stopped 82 | security_opt: 83 | - no-new-privileges:true 84 | pull_policy: always 85 | networks: 86 | - net 87 | ports: 88 | - "8681:8080" 89 | volumes: 90 | - api-data:/app/data 91 | #environment: # option environment see above note 92 | # GOTIFY_URLS: '' 93 | # GOTIFY_CLIENT_TOKENS: '' 94 | # SECNTFY_TOKENS: '' 95 | 96 | networks: 97 | net: 98 | 99 | volumes: 100 | data: 101 | api-data: 102 | ``` 103 | *Thank you The_Think3r for the compose file and @herrpandora* 104 | 105 |   106 | ### (Optional) NGINX Proxy Manager 107 | 108 | When someone have problem's with incoming notifications on the app, please try this options under Advance Settings for the setuped proxies 109 | 110 | ```bash 111 | proxy_set_header Host $http_host; 112 | proxy_connect_timeout 1m; 113 | proxy_send_timeout 1m; 114 | proxy_read_timeout 1m; 115 | ``` 116 | 117 | Also **don't** check the boxes which say "HTTP/2 Support" and "HSTS enabled". 118 | 119 | *Thank you to @TBT-TBT for sharing this notice* 120 | 121 |   122 | ### Traefik Config 123 | 124 | ```bash 125 | version: "3.8" 126 | 127 | services: 128 | gotify: 129 | container_name: gotify 130 | hostname: gotify 131 | image: gotify/server 132 | restart: unless-stopped 133 | security_opt: 134 | - no-new-privileges:true 135 | volumes: 136 | - data:/app/data 137 | environment: 138 | GOTIFY_DEFAULTUSER_PASS: 'my-very-strong-password' # Change me!!!!! 139 | GOTIFY_REGISTRATION: 'false' 140 | labels: 141 | traefik.docker.network: proxy 142 | traefik.enable: "true" 143 | traefik.http.routers.gotify-secure.entrypoints: websecure 144 | traefik.http.routers.gotify-secure.middlewares: default@file 145 | traefik.http.routers.gotify-secure.rule: Host(`gotify.domain-name.de`) 146 | traefik.http.routers.gotify-secure.service: gotify 147 | traefik.http.routers.gotify-secure.tls: "true" 148 | traefik.http.routers.gotify-secure.tls.certresolver: http_resolver 149 | traefik.http.routers.gotify.entrypoints: web 150 | traefik.http.routers.gotify.rule: Host(`gotify.domain-name.de`) 151 | traefik.http.services.gotify.loadbalancer.server.port: "80" 152 | networks: 153 | default: null 154 | proxy: null 155 | volumes: 156 | - data:/app/data 157 | 158 | igotify-notification: # (iGotify-Notification-Assistent) 159 | container_name: igotify 160 | hostname: igotify 161 | image: ghcr.io/androidseb25/igotify-notification-assist:latest 162 | restart: always 163 | security_opt: 164 | - no-new-privileges:true 165 | pull_policy: always 166 | volumes: 167 | - api-data:/app/data 168 | 169 | #environment: # option environment see above note 170 | # GOTIFY_URLS: '' 171 | # GOTIFY_CLIENT_TOKENS: '' 172 | # SECNTFY_TOKENS: '' 173 | 174 | labels: 175 | traefik.docker.network: proxy 176 | traefik.enable: "true" 177 | traefik.http.routers.igotify-secure.entrypoints: websecure 178 | traefik.http.routers.igotify-secure.middlewares: default@file 179 | traefik.http.routers.igotify-secure.rule: Host(`igotify.domain-name.de`) 180 | traefik.http.routers.igotify-secure.service: igotify 181 | traefik.http.routers.igotify-secure.tls: "true" 182 | traefik.http.routers.igotify-secure.tls.certresolver: http_resolver 183 | traefik.http.routers.igotify.entrypoints: web 184 | traefik.http.routers.igotify.rule: Host(`igotify.domain-name.de`) 185 | traefik.http.services.igotify.loadbalancer.server.port: "8080" 186 | networks: 187 | default: null 188 | proxy: null 189 | 190 | networks: 191 | default: 192 | proxy: 193 | external: true 194 | volumes: 195 | data: 196 | api-data: 197 | ``` 198 | *Thank you to @majo1989 for sharing this config* 199 | 200 |   201 | ## 🔧 Install iGotify app 202 | 203 | Download from [AppStore](https://apps.apple.com/de/app/igotify/id6473452512?itsct=apps_box_badge&itscg=30200) 204 | 205 | [![Download on the App Store](https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/de-de?size=350&releaseDate=1702425600)](https://apps.apple.com/de/app/igotify/id6473452512?itsct=apps_box_badge&itscg=30200) 206 | 207 | For Bugs or feedback please send me a PM in Discord under the name sebakaderangler or fill out the issue formular here on GitHub. 208 | 209 | On the login screen you need to enter the Gotify Server URL and the URL from the Notification Assist, if you use the URL with a port please enter it, too! (Image 1) 210 | 211 | After the checks for the URL are finished and correct you need to login with your login credentials. (Image 2) 212 | 213 |   214 | 215 | ![](https://raw.githubusercontent.com/androidseb25/iGotify-Notification-Assistent/main/login_screen_1.png) 216 | ![](https://raw.githubusercontent.com/androidseb25/iGotify-Notification-Assistent/main/login_screen_2.png) 217 | 218 |   219 | 220 | And if everythink is ok, you're logged in 🎉 221 | 222 | Now you receive background notifications when Gotify receives a message. 223 | 224 | ## Translation 225 | If you want to be a part of the translation team please create a issue: 226 | 227 | **Title: Translation: *language*** 228 | 229 | **Description: crowdin username and why you would be a part of the translation team** 230 | 231 | maybe you've been invited soon 232 | 233 | The link to the crowdin project: [https://de.crowdin.com/project/igotify](https://de.crowdin.com/project/igotify) 234 | 235 | ## SecNtfy Status 236 | Here you can find the online status of the service [Status](https://ipv64.net/status/secntfy) 237 | -------------------------------------------------------------------------------- /Services/DatabaseService.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using iGotify_Notification_Assist.Models; 3 | using Microsoft.Data.Sqlite; 4 | 5 | namespace iGotify_Notification_Assist.Services; 6 | 7 | public static class DatabaseService 8 | { 9 | public static bool CreateDatebase(string path) 10 | { 11 | //Create Database File 12 | var pathToDb = Path.Combine(path, "users.db"); 13 | var isDbFileExists = File.Exists(pathToDb); 14 | 15 | if (isDbFileExists) return true; 16 | var connectionString = GetConnectionString.UsersDb(pathToDb); 17 | using var dbConnection = new SqliteConnection(connectionString); 18 | dbConnection.Open(); 19 | 20 | // Create a sample table 21 | const string createTableQuery = "create table if not exists Users (Uid integer primary key, ClientToken text not null, DeviceToken text not null, GotifyUrl text not null);"; 22 | dbConnection.Execute(createTableQuery); 23 | 24 | // Perform other database operations as needed 25 | 26 | // Close the connection when done 27 | dbConnection.Close(); 28 | 29 | return true; 30 | 31 | } 32 | 33 | public static async Task CheckIfUserExists(DeviceModel dm) 34 | { 35 | var path = $"{GetLocationsOf.App}/data"; 36 | //Create Database File 37 | var pathToDb = Path.Combine(path, "users.db"); 38 | var isDbFileExists = File.Exists(pathToDb); 39 | var isExists = false; 40 | 41 | if (!isDbFileExists) return isExists; 42 | await using var dbConnection = new SqliteConnection(GetConnectionString.UsersDb(pathToDb)); 43 | dbConnection.Open(); 44 | 45 | // Create a sample table 46 | var selectQuery = $"select count(*) from Users u where u.ClientToken = '{dm.ClientToken}';"; 47 | var id = await dbConnection.ExecuteScalarAsync(selectQuery); 48 | 49 | isExists = id > 0; 50 | 51 | // Perform other database operations as needed 52 | 53 | // Close the connection when done 54 | dbConnection.Close(); 55 | 56 | return isExists; 57 | } 58 | 59 | public static async Task DeleteUser(string clientToken) 60 | { 61 | var path = $"{GetLocationsOf.App}/data"; 62 | //Create Database File 63 | var pathToDb = Path.Combine(path, "users.db"); 64 | var isDbFileExists = File.Exists(pathToDb); 65 | var isDeleted = false; 66 | 67 | if (!isDbFileExists) return isDeleted; 68 | await using var dbConnection = new SqliteConnection(GetConnectionString.UsersDb(pathToDb)); 69 | dbConnection.Open(); 70 | 71 | // Create a sample table 72 | var deleteQuery = $"delete from Users where ClientToken = '{clientToken}';"; 73 | var rows = await dbConnection.ExecuteAsync(deleteQuery); 74 | 75 | isDeleted = rows > 0; 76 | 77 | // Perform other database operations as needed 78 | 79 | // Close the connection when done 80 | dbConnection.Close(); 81 | 82 | return isDeleted; 83 | } 84 | 85 | public static async Task InsertUser(DeviceModel dm) 86 | { 87 | var path = $"{GetLocationsOf.App}/data"; 88 | //Create Database File 89 | var pathToDb = Path.Combine(path, "users.db"); 90 | var isDbFileExists = File.Exists(pathToDb); 91 | var inserted = false; 92 | 93 | if (!isDbFileExists) return inserted; 94 | await using var dbConnection = new SqliteConnection(GetConnectionString.UsersDb(pathToDb)); 95 | dbConnection.Open(); 96 | 97 | // Create a sample table 98 | var insertQuery = $"insert into Users (ClientToken, DeviceToken, GotifyUrl) values ('{dm.ClientToken}', '{dm.DeviceToken}', '{dm.GotifyUrl}');"; 99 | var id = await dbConnection.ExecuteAsync(insertQuery); 100 | 101 | inserted = id > 0; 102 | 103 | // Perform other database operations as needed 104 | 105 | // Close the connection when done 106 | dbConnection.Close(); 107 | 108 | return inserted; 109 | } 110 | 111 | public static async Task GetUser(string clientToken) 112 | { 113 | var usr = new Users(); 114 | var path = $"{GetLocationsOf.App}/data"; 115 | //Create Database File 116 | var pathToDb = Path.Combine(path, "users.db"); 117 | var isDbFileExists = File.Exists(pathToDb); 118 | 119 | if (!isDbFileExists) return usr; 120 | await using var dbConnection = new SqliteConnection(GetConnectionString.UsersDb(pathToDb)); 121 | dbConnection.Open(); 122 | 123 | // Create a sample table 124 | var selectUserQuery = $"SELECT * FROM Users u where u.ClientToken = '{clientToken}';"; 125 | if (clientToken.Contains("NTFY-DEVICE-")) 126 | selectUserQuery = $"SELECT * FROM Users u where u.DeviceToken = '{clientToken}';"; 127 | usr = (await dbConnection.QueryAsync(selectUserQuery)).ToList().FirstOrDefault() ?? usr; 128 | 129 | // Perform other database operations as needed 130 | 131 | // Close the connection when done 132 | dbConnection.Close(); 133 | 134 | return usr; 135 | } 136 | 137 | public static async Task> GetUsers() 138 | { 139 | var userList = new List(); 140 | var path = $"{GetLocationsOf.App}/data"; 141 | //Create Database File 142 | var pathToDb = Path.Combine(path, "users.db"); 143 | var isDbFileExists = File.Exists(pathToDb); 144 | 145 | if (!isDbFileExists) return userList; 146 | await using var dbConnection = new SqliteConnection(GetConnectionString.UsersDb(pathToDb)); 147 | dbConnection.Open(); 148 | 149 | // Create a sample table 150 | const string selectAllQuery = "SELECT * FROM Users u;"; 151 | userList = (await dbConnection.QueryAsync(selectAllQuery)).ToList(); 152 | 153 | // Perform other database operations as needed 154 | 155 | // Close the connection when done 156 | dbConnection.Close(); 157 | 158 | return userList; 159 | } 160 | } -------------------------------------------------------------------------------- /Services/GotifySocketService.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using iGotify_Notification_Assist.Models; 3 | using SecNtfyNuGet; 4 | 5 | namespace iGotify_Notification_Assist.Services; 6 | 7 | public class GotifySocketService 8 | { 9 | public bool isInit { get; private set; } 10 | 11 | private static GotifySocketService? _instance; 12 | 13 | // Datenstruktur zur Verfolgung von Threads und WebSocket-Verbindungen 14 | private static Dictionary? _websocketThreads; 15 | 16 | public static GotifySocketService getInstance() 17 | { 18 | return _instance ??= new GotifySocketService(); 19 | } 20 | 21 | public void Init() 22 | { 23 | var path = $"{GetLocationsOf.App}/data"; 24 | 25 | if (!Directory.Exists(path)) 26 | Directory.CreateDirectory(path); 27 | 28 | //Create Database File 29 | var isDbFileExists = DatabaseService.CreateDatebase(path); 30 | Console.WriteLine($"Database is created: {isDbFileExists}"); 31 | isInit = isDbFileExists; 32 | } 33 | 34 | public static void KillWsThread(string clientToken) 35 | { 36 | if (_websocketThreads != null) 37 | { 38 | _websocketThreads.TryGetValue(clientToken, out var wsc); 39 | if (wsc == null) return; 40 | wsc.Stop(); 41 | _websocketThreads.Remove(clientToken); 42 | } 43 | } 44 | 45 | public static void StartWsThread(string gotifyServerUrl, string clientToken) 46 | { 47 | var thread = new Thread(() => StartWsConn(gotifyServerUrl, clientToken)); 48 | thread.Start(); 49 | } 50 | 51 | private static void StartWsConn(string gotifyServerUrl, string clientToken) 52 | { 53 | while (true) 54 | { 55 | try 56 | { 57 | string wsUrl; 58 | string socket; 59 | 60 | socket = gotifyServerUrl.Contains("http://") ? "ws" : "wss"; 61 | gotifyServerUrl = gotifyServerUrl.Replace("http://", "").Replace("https://", "").Replace("\"", ""); 62 | wsUrl = $"{socket}://{gotifyServerUrl}/stream?token={clientToken}"; 63 | 64 | // Starting WebSocket instance 65 | Console.WriteLine("Client connecting..."); 66 | var wsc = new WebSockClient { URL = wsUrl }; 67 | wsc.Start(clientToken); 68 | // Connect the client 69 | 70 | // Fügen Sie den Thread und die zugehörige WebSocket-Verbindung zur Datenstruktur hinzu 71 | if (_websocketThreads == null) 72 | _websocketThreads = new Dictionary(); 73 | 74 | _websocketThreads.TryGetValue(clientToken, out var storedWebSockClient); 75 | if (storedWebSockClient == null) 76 | _websocketThreads.Add(clientToken, wsc); 77 | else 78 | Console.WriteLine($"Client: {clientToken} already connected! Skipping..."); 79 | 80 | Thread.Sleep(Timeout.Infinite); 81 | } 82 | catch (WebSocketException wse) 83 | { 84 | Console.WriteLine($"Unable to Connect to WS or WSS connection aborted with clientToken: {clientToken}"); 85 | Console.WriteLine(wse.StackTrace); 86 | //currentProcess.Kill(true); 87 | } 88 | } 89 | } 90 | 91 | /// 92 | /// Initialise WebSocket with all passed variables from container 93 | /// 94 | public async void Start() 95 | { 96 | var secntfyUrl = Environments.secNtfyUrl; 97 | 98 | // [FEATURE REQUEST] #59 - https://github.com/androidseb25/iGotify-Notification-Assistent/issues/59 99 | // First try of implementing local running instances without app configuration 100 | var gotifyUrls = Environments.gotifyUrls; 101 | var gotifyClientTokens = Environments.gotifyClientTokens; 102 | var secntfyTokens = Environments.secNtfyTokens; 103 | 104 | var gotifyUrlList = new List(); 105 | var gotifyClientList = new List(); 106 | var secntfyTokenList = new List(); 107 | 108 | if (gotifyUrls.Length > 0 && gotifyClientTokens.Length > 0 && secntfyTokens.Length > 0) 109 | { 110 | try 111 | { 112 | gotifyUrlList = gotifyUrls.Split(";").ToList(); 113 | gotifyClientList = gotifyClientTokens.Split(";").ToList(); 114 | secntfyTokenList = secntfyTokens.Split(";").ToList(); 115 | 116 | var clientCounter = 0; 117 | foreach (string client in gotifyClientList) 118 | { 119 | var dm = new DeviceModel(); 120 | dm.ClientToken = client; 121 | if (!await DatabaseService.CheckIfUserExists(dm)) 122 | { 123 | dm.GotifyUrl = gotifyUrlList.ElementAt(clientCounter); 124 | dm.DeviceToken = secntfyTokenList.ElementAt(clientCounter); 125 | if (!await DatabaseService.InsertUser(dm)) 126 | { 127 | throw new ApplicationException("Insert Database Exception!"); 128 | } 129 | } 130 | 131 | clientCounter++; 132 | } 133 | } 134 | catch (Exception e) 135 | { 136 | Console.WriteLine($"Error: {e.Message}"); 137 | Console.WriteLine("Something went wrong when inserting you're connection!"); 138 | Console.WriteLine("Please check you're environment lists!"); 139 | } 140 | } 141 | else 142 | { 143 | var statusServerList = gotifyUrlList.Count == 0 ? "empty" : "filled"; 144 | Console.WriteLine($"Gotify Url list is: {statusServerList}"); 145 | var statusClientList = gotifyClientList.Count == 0 ? "empty" : "filled"; 146 | Console.WriteLine($"Gotify Client list is: {statusClientList}"); 147 | var statusNtfyList = secntfyTokenList.Count == 0 ? "empty" : "filled"; 148 | Console.WriteLine($"SecNtfy Token list is: {statusNtfyList}"); 149 | Console.WriteLine($"If one or more lists are empty please check the environment variable! GOTIFY_SERVERS or GOTIFY_CLIENTS or NTFY_TOKENS"); 150 | Console.WriteLine($"If all lists are empty do nothing, you will configure the gotify server over the iGotify app."); 151 | } 152 | 153 | var userList = await DatabaseService.GetUsers(); 154 | 155 | StartConnection(userList, secntfyUrl); 156 | } 157 | 158 | private void StartConnection(List userList, string secntfyUrl) 159 | { 160 | foreach (var user in userList) 161 | { 162 | string isGotifyAvailable; 163 | string isSecNtfyAvailable; 164 | try 165 | { 166 | isGotifyAvailable = SecNtfy.CheckIfUrlReachable(user.GotifyUrl) ? "yes" : "no"; 167 | 168 | if (isGotifyAvailable == "no") 169 | { 170 | StartConnection(userList, secntfyUrl); 171 | return; 172 | } 173 | 174 | isSecNtfyAvailable = SecNtfy.CheckIfUrlReachable(secntfyUrl) ? "yes" : "no"; 175 | } 176 | catch 177 | { 178 | StartDelayedConnection(userList, secntfyUrl); 179 | return; 180 | } 181 | 182 | Console.WriteLine($"Gotify - Url: {user.GotifyUrl}"); 183 | Console.WriteLine($"Is Gotify - Url available: {isGotifyAvailable}"); 184 | Console.WriteLine($"SecNtfy Server - Url: {secntfyUrl}"); 185 | Console.WriteLine($"Is SecNtfy Server - Url available: {isSecNtfyAvailable}"); 186 | Console.WriteLine($"Client - Token: {user.ClientToken}"); 187 | 188 | StartWsThread(user.GotifyUrl, user.ClientToken); 189 | } 190 | } 191 | 192 | private async void StartDelayedConnection(List userList, string secntfyUrl) 193 | { 194 | Console.WriteLine("Gotify Server is not available try to reconnect in 10s."); 195 | await Task.Delay(10000); 196 | Console.WriteLine("Reconnecting..."); 197 | StartConnection(userList, secntfyUrl); 198 | } 199 | } -------------------------------------------------------------------------------- /Services/StartUpBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace iGotify_Notification_Assist.Services; 2 | 3 | public class StartUpBuilder : IStartupFilter 4 | { 5 | public Action Configure(Action next) 6 | { 7 | return builder => 8 | { 9 | // Create GotifyInstance after starting of the API 10 | 11 | var gss = GotifySocketService.getInstance(); 12 | gss.Init(); 13 | if (gss.isInit) 14 | gss.Start(); 15 | 16 | next(builder); 17 | }; 18 | } 19 | } -------------------------------------------------------------------------------- /Services/Tool.cs: -------------------------------------------------------------------------------- 1 | namespace iGotify_Notification_Assist.Services; 2 | 3 | /// 4 | /// Get Location of the App path when application is running 5 | /// 6 | public static class GetLocationsOf 7 | { 8 | public static readonly string? App = 9 | Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); 10 | } 11 | 12 | public static class GetConnectionString 13 | { 14 | public static string UsersDb(string databaseFilePath) 15 | { 16 | return $"Data Source={databaseFilePath}"; 17 | } 18 | } 19 | 20 | 21 | public class Environments 22 | { 23 | public static bool isLogEnabled 24 | { 25 | get 26 | { 27 | return Environment.GetEnvironmentVariable("ENABLE_CONSOLE_LOG") == "true"; 28 | } 29 | } 30 | public static bool enableScalarUi 31 | { 32 | get 33 | { 34 | return Environment.GetEnvironmentVariable("ENABLE_SCALAR_UI") == "true"; 35 | } 36 | } 37 | 38 | public static string gotifyUrls 39 | { 40 | get 41 | { 42 | return Environment.GetEnvironmentVariable("GOTIFY_URLS") ?? ""; 43 | } 44 | } 45 | 46 | public static string gotifyClientTokens 47 | { 48 | get 49 | { 50 | return Environment.GetEnvironmentVariable("GOTIFY_CLIENT_TOKENS") ?? ""; 51 | } 52 | } 53 | 54 | public static string secNtfyTokens 55 | { 56 | get 57 | { 58 | return Environment.GetEnvironmentVariable("SECNTFY_TOKENS") ?? ""; 59 | } 60 | } 61 | 62 | public static string secNtfyUrl 63 | { 64 | get 65 | { 66 | return Environment.GetEnvironmentVariable("SECNTFY_SERVER_URL") ?? "https://api.secntfy.app"; 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /Services/WebSockClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using System.Reactive.Linq; 3 | using iGotify_Notification_Assist.Models; 4 | using Newtonsoft.Json; 5 | using Websocket.Client; 6 | 7 | namespace iGotify_Notification_Assist.Services; 8 | 9 | public class WebSockClient 10 | { 11 | public string? URL { get; init; } 12 | 13 | private WebsocketClient? ws; 14 | 15 | public void Start(string clientToken, bool isRestart = false) 16 | { 17 | if (URL is { Length: 0 }) 18 | throw new ApplicationException("URL is empty!"); 19 | 20 | // Init WebSocket 21 | ws = new WebsocketClient(new Uri(URL!)); 22 | ws.Name = clientToken; 23 | ws.IsReconnectionEnabled = false; 24 | 25 | ws.ReconnectionHappened.Subscribe(info => 26 | { 27 | //Console.WriteLine($"ReconnectionHappened {info.Type}"); 28 | if (info.Type == ReconnectionType.Initial && isRestart) 29 | { 30 | Console.WriteLine($"Gotify with Clienttoken: \"{clientToken}\" is successfully reconnected!"); 31 | } 32 | }); 33 | 34 | // When a disconnection happend try to reconnect the WebSocket 35 | ws.DisconnectionHappened.Subscribe(type => 36 | { 37 | var wsName = ws.Name; 38 | Console.WriteLine($"Disconnection happened, type: {type.Type}"); 39 | switch (type.Type) 40 | { 41 | case DisconnectionType.Lost: 42 | Console.WriteLine("Connection lost reconnect to Websocket..."); 43 | Stop(); 44 | Start(wsName, true); 45 | break; 46 | case DisconnectionType.Error: 47 | Console.WriteLine("Webseocket Reconnection failed with Error. Try to reconnect in 10s."); 48 | ReconnectDelayed(wsName); 49 | break; 50 | case DisconnectionType.Exit: 51 | break; 52 | case DisconnectionType.ByServer: 53 | break; 54 | case DisconnectionType.NoMessageReceived: 55 | break; 56 | case DisconnectionType.ByUser: 57 | break; 58 | } 59 | }); 60 | 61 | // Listening to the WebSocket when message received 62 | ws.MessageReceived.Select(msg => Observable.FromAsync(async () => { 63 | //Console.WriteLine($"Message received: {msg}"); 64 | // Convert the payload from gotify and replace values because so we can cast it better cast it to an object 65 | var message = msg.ToString().Replace("client::display", "clientdisplay") 66 | .Replace("client::notification", "clientnotification") 67 | .Replace("android::action", "androidaction"); 68 | if (Environments.isLogEnabled) 69 | Console.WriteLine("Message converted: " + message); 70 | // var jsonData = JsonConvert.SerializeObject(message); 71 | var gm = JsonConvert.DeserializeObject(message); 72 | // If object is null return and listen to the next message 73 | if (gm == null) 74 | { 75 | Console.WriteLine("GotifyMessage is null"); 76 | return; 77 | } 78 | 79 | // Go and send the message 80 | Console.WriteLine($"WS Instance from: {ws.Name}"); 81 | await new DeviceModel().SendNotifications(gm, ws); 82 | })) 83 | .Concat() // executes sequentially 84 | .Subscribe(); 85 | ws.Start(); 86 | if (!isRestart) 87 | Console.WriteLine("Done!"); 88 | } 89 | 90 | /// 91 | /// Stop and clear the WebSocket value 92 | /// 93 | public async void Stop() 94 | { 95 | await ws!.Stop(WebSocketCloseStatus.Empty, "Connection closing."); 96 | ws = null; 97 | await Task.Delay(1000); 98 | } 99 | 100 | private async void ReconnectDelayed(string clientToken) 101 | { 102 | ws = null; 103 | await Task.Delay(10000); 104 | Start(clientToken, true); 105 | } 106 | } -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | gotify: 5 | container_name: gotify 6 | hostname: gotify 7 | image: gotify/server # Uncommand correct server image 8 | # image: gotify/server-arm7 9 | # image: gotify/server-arm64 10 | restart: unless-stopped 11 | security_opt: 12 | - no-new-privileges:true 13 | networks: 14 | - net 15 | ports: 16 | - "8680:80" 17 | volumes: 18 | - data:/app/data 19 | environment: 20 | GOTIFY_DEFAULTUSER_PASS: 'my-very-strong-password' # Change me!!!!! 21 | 22 | igotify: 23 | container_name: igotify 24 | hostname: igotify 25 | image: ghcr.io/androidseb25/igotify-notification-assist:latest 26 | restart: unless-stopped 27 | security_opt: 28 | - no-new-privileges:true 29 | pull_policy: always 30 | networks: 31 | - net 32 | ports: 33 | - "8681:8080" 34 | volumes: 35 | - api-data:/app/data 36 | #environment: # option environment see above note 37 | # GOTIFY_URLS: '' 38 | # GOTIFY_CLIENT_TOKENS: '' 39 | # SECNTFY_TOKENS: '' 40 | 41 | networks: 42 | net: 43 | 44 | volumes: 45 | data: 46 | api-data: -------------------------------------------------------------------------------- /iGotify Notification Assist.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | iGotify_Notification_Assist 9 | 1.3.1.0 10 | 1.3.1.0 11 | 1.3.1.0 12 | default 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /iGotifyNotificationAssist.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iGotify Notification Assist", "iGotify Notification Assist.csproj", "{32B750A3-C762-41BE-B0E6-64279E8B2E89}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {32B750A3-C762-41BE-B0E6-64279E8B2E89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {32B750A3-C762-41BE-B0E6-64279E8B2E89}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {32B750A3-C762-41BE-B0E6-64279E8B2E89}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {32B750A3-C762-41BE-B0E6-64279E8B2E89}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /iGotifyNotificationAssist.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | ForceIncluded -------------------------------------------------------------------------------- /login_screen_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/iGotify-Notification-Assistent/2d88a3dfac20e5deb88e3687503bdd4332688375/login_screen_1.png -------------------------------------------------------------------------------- /login_screen_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/iGotify-Notification-Assistent/2d88a3dfac20e5deb88e3687503bdd4332688375/login_screen_2.png -------------------------------------------------------------------------------- /paypal-donate-icon-7_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/iGotify-Notification-Assistent/2d88a3dfac20e5deb88e3687503bdd4332688375/paypal-donate-icon-7_20.png -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------# 2 | # Qodana analysis is configured by qodana.yaml file # 3 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html # 4 | #-------------------------------------------------------------------------------# 5 | version: "1.0" 6 | 7 | #Specify IDE code to run analysis without container (Applied in CI/CD pipeline) 8 | ide: QDNET 9 | 10 | #Specify inspection profile for code analysis 11 | profile: 12 | name: qodana.starter 13 | exclude: 14 | - name: All 15 | paths: 16 | - Models/ServerVersion.cs 17 | 18 | #Enable inspections 19 | #include: 20 | # - name: 21 | 22 | #Disable inspections 23 | #exclude: 24 | # - name: 25 | # paths: 26 | # - 27 | 28 | #Execute shell command before Qodana execution (Applied in CI/CD pipeline) 29 | #bootstrap: sh ./prepare-qodana.sh 30 | 31 | #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) 32 | #plugins: 33 | # - id: #(plugin id can be found at https://plugins.jetbrains.com) 34 | --------------------------------------------------------------------------------