├── Procfile ├── _config.yml ├── app.json ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── codeql-analysis.yml ├── images └── plex-icon.png ├── package.json ├── README.md ├── plex-discord-webhook.sln ├── LICENSE ├── .gitignore └── index.js /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "stack": "heroku-18" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Floydan] 4 | -------------------------------------------------------------------------------- /images/plex-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Floydan/plex-discord-webhook/HEAD/images/plex-icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plex-notifications", 3 | "version": "0.0.1", 4 | "engines": { 5 | "node": "12.15.x", 6 | "npm": "6.13.x" 7 | }, 8 | "private": true, 9 | "scripts": { 10 | "start": "node index.js" 11 | }, 12 | "dependencies": { 13 | "express": "^4.18.1", 14 | "jimp": "0.16.2", 15 | "multer": "^1.4.4", 16 | "node-freegeoip": "0.0.1", 17 | "redis": "^3.1.2", 18 | "request": "^2.88.2", 19 | "sha1": "^1.1.1" 20 | }, 21 | "devDependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 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 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plex Discord Webhook integration 2 | This small node app is based on the https://github.com/plexinc/webhooks-slack library created by plexinc. 3 | I've only added the support for Discord webhooks and increased address lookup information 4 | 5 | In order to run this app: 6 | 7 | - Install [node.js](https://nodejs.org/en/). 8 | - Clone the repository. 9 | - Install dependencies using `npm install`. 10 | - Make a new app at Heroku, and add the Redis Cloud add-on (free plan) and note the app URL. 11 | - Make a Discord webhook and note the URL, add the last part after /webhooks/ as a config var named DISCORD_WEBHOOK_KEY. 12 | - For example: '279401144396748544/gPy8loljUVY3MzsvIvFd9o7tllolp8SWavdwi0JwCpphKGLdadlsE8Dv4hlolhkd0hFA' 13 | - Edit the options at the top of the index.js file. (namely: appURL) 14 | - Deploy to Heroku 15 | - Have anyone who wants to contribute add the webhook on https://app.plex.tv/web/app#!/account/webhooks -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /plex-discord-webhook.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26127.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "plex-discord-webhook", "plex-discord-webhook.njsproj", "{92F70DC3-F633-4169-9E82-7DA7D8F8EFD6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {92F70DC3-F633-4169-9E82-7DA7D8F8EFD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {92F70DC3-F633-4169-9E82-7DA7D8F8EFD6}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {92F70DC3-F633-4169-9E82-7DA7D8F8EFD6}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {92F70DC3-F633-4169-9E82-7DA7D8F8EFD6}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Joakim Höglund 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 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 12 * * 0' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['javascript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | *.env 254 | 255 | *.njsproj -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , request = require('request') 3 | , multer = require('multer') 4 | , redis = require('redis') 5 | //, lwip = require('lwip') 6 | , jimp = require('jimp') 7 | , sha1 = require('sha1') 8 | , freegeoip = require('node-freegeoip'); 9 | 10 | // Configuration. 11 | var appUrl = process.env.APP_URL || 'https://plex-discord-webhook.herokuapp.com'; 12 | var webhookKey = process.env.DISCORD_WEBHOOK_KEY; 13 | 14 | var redisClient = redis.createClient(process.env.REDISCLOUD_URL, { return_buffers: true }); 15 | var upload = multer({ storage: multer.memoryStorage() }); 16 | var app = express(); 17 | 18 | app.use(express.static('images')); 19 | 20 | function formatTitle(metadata) { 21 | if (metadata.grandparentTitle) { 22 | return metadata.grandparentTitle; 23 | } else { 24 | let ret = metadata.title; 25 | if (metadata.year) { 26 | ret += ` (${metadata.year})`; 27 | } 28 | return ret; 29 | } 30 | } 31 | 32 | function formatSubtitle(metadata) { 33 | var ret = ''; 34 | if (metadata.grandparentTitle) { 35 | if (metadata.type === 'track') { 36 | ret = metadata.parentTitle; 37 | } else if (metadata.index && metadata.parentIndex) { 38 | ret = `S${metadata.parentIndex} E${metadata.index}`; 39 | } else if (metadata.originallyAvailableAt) { 40 | ret = metadata.originallyAvailableAt; 41 | } 42 | 43 | if (metadata.title) { 44 | ret += ` - ${metadata.title}`; 45 | } 46 | } else if (metadata.type === 'movie') { 47 | ret = metadata.tagline; 48 | } 49 | 50 | return ret; 51 | } 52 | 53 | function formatSummary(summary) { 54 | var ret = ''; 55 | 56 | if (summary && summary.length) { 57 | if (summary.length > 300) { 58 | ret += summary.substring(0, 300) + '...'; 59 | } 60 | else { 61 | ret += summary; 62 | } 63 | 64 | if (ret.length > 0) { 65 | ret = `\r\n\r\n${ret}`; 66 | } 67 | } 68 | 69 | return ret; 70 | } 71 | 72 | function notifyDiscord(imageUrl, payload, location, action) { 73 | var locationText = ''; 74 | if (location) { 75 | if (location.city) { 76 | locationText = ` near ${location.city}, ${(location.country_code === 'US' ? location.region_name : location.country_name)}`; 77 | } 78 | else { 79 | locationText = `, ${(location.country_code === 'US' ? location.region_name : location.country_name)}`; 80 | } 81 | } 82 | 83 | const data = { 84 | "content": '', 85 | "username": 'Plex', 86 | "avatar_url": appUrl + '/plex-icon.png', 87 | "embeds": [ 88 | { 89 | "title": formatTitle(payload.Metadata), 90 | "description": formatSubtitle(payload.Metadata) + formatSummary(payload.Metadata.summary), 91 | "footer": { 92 | "text": `${action} by ${payload.Account.title} on ${payload.Player.title} from ${payload.Server.title} ${locationText}`, 93 | "icon_url": payload.Account.thumb 94 | }, 95 | "thumbnail": { 96 | "url": imageUrl, 97 | "height": 200, 98 | "width": '200' 99 | } 100 | } 101 | ] 102 | }; 103 | 104 | request.post(`https://discordapp.com/api/webhooks/${webhookKey}`, 105 | { json: data }, 106 | function (error, response, body) { 107 | if (!error && response.statusCode === 200) { 108 | //console.log(body) 109 | } 110 | } 111 | ); 112 | } 113 | 114 | app.post('/', upload.single('thumb'), function (req, res, next) { 115 | var payload = JSON.parse(req.body.payload); 116 | const isVideo = payload.Metadata.librarySectionType === 'movie' || payload.Metadata.librarySectionType === 'show'; 117 | const isAudio = payload.Metadata.librarySectionType === 'artist'; 118 | 119 | if (payload.user === true && payload.Metadata && (isAudio || isVideo)) { 120 | var key = sha1(payload.Server.uuid + payload.Metadata.guid); 121 | 122 | if (payload.event === 'media.play' || payload.event === 'media.rate') { 123 | // Save the image. 124 | if (req.file && req.file.buffer) { 125 | jimp.read(req.file.buffer) 126 | .then(image => { 127 | image.contain(75, 75) 128 | .getBuffer(jimp.MIME_JPEG, 129 | (error, buffer) => { 130 | redisClient.setex(key, 7 * 24 * 60 * 60, buffer); 131 | }); 132 | }); 133 | } 134 | } 135 | 136 | if ((payload.event === 'media.scrobble' && isVideo) || payload.event === 'media.rate' || payload.event === 'media.play') { 137 | // Geolocate player. 138 | freegeoip.getLocation(payload.Player.publicAddress, function (err, location) { 139 | 140 | var action; 141 | if (payload.event === 'media.scrobble' || payload.event === 'media.play') { 142 | action = 'played'; 143 | } else if (payload.event === 'media.rate') { 144 | if (payload.rating > 0) { 145 | action = 'rated '; 146 | for (var i = 0; i < payload.rating / 2; i++) 147 | action += '★'; 148 | } else { 149 | action = 'unrated'; 150 | } 151 | } 152 | 153 | // Send the event to Discord. 154 | redisClient.get(key, function (err, reply) { 155 | if (!location || (location && location.city && location.city.length > 1)) { 156 | if (reply) { 157 | notifyDiscord(appUrl + '/images/' + key, payload, location, action); 158 | } else { 159 | notifyDiscord(null, payload, location, action); 160 | } 161 | } 162 | else { 163 | console.log('location city missing, trying OSM lat lng lookup'); 164 | 165 | var options = { 166 | url: `http://nominatim.openstreetmap.org/reverse?format=json&lat=${location.latitude}&lon=${location.longitude}&accept-language=en`, 167 | method: 'GET', 168 | headers: { 169 | 'User-Agent': 'hoglund.joakim@gmail.com' 170 | } 171 | }; 172 | 173 | request(options, function (error, response, body) { 174 | if (error) console.log('OSM lookup error', error); 175 | 176 | if (!error && response.statusCode === 200) { 177 | location = JSON.parse(body).address; 178 | location.region_name = location.state; 179 | location.country_name = location.country; 180 | } 181 | 182 | if (reply) { 183 | notifyDiscord(appUrl + '/images/' + key, payload, location, action); 184 | } else { 185 | notifyDiscord(null, payload, location, action); 186 | } 187 | } 188 | ); 189 | } 190 | }); 191 | }); 192 | } 193 | } 194 | 195 | res.sendStatus(200); 196 | }); 197 | 198 | app.get('/images/:key', function (req, res, next) { 199 | redisClient.get(req.params.key, function (err, value) { 200 | if (err) { 201 | next(err); 202 | } else { 203 | if (!value) { 204 | next(); 205 | } else { 206 | res.setHeader('Content-Type', 'image/jpeg'); 207 | res.end(value); 208 | } 209 | } 210 | }); 211 | }); 212 | 213 | app.listen(process.env.PORT || 11000); --------------------------------------------------------------------------------