discord notifications
All checks were successful
Build and push Doorman UI / API / docker (push) Successful in 1m27s
All checks were successful
Build and push Doorman UI / API / docker (push) Successful in 1m27s
This commit is contained in:
parent
f4ac30260c
commit
4cad1fbe2b
@ -25,6 +25,7 @@ jobs:
|
|||||||
AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
|
AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
|
||||||
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
|
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Deploy Doorman Buzzer Client
|
- name: Deploy Doorman Buzzer Client
|
||||||
run: bun run deploy-buzzer-client
|
run: bun run deploy-buzzer-client
|
||||||
|
|||||||
@ -4,4 +4,7 @@ AUTH_TOKEN=
|
|||||||
|
|
||||||
# aws stuff
|
# aws stuff
|
||||||
AWS_ACCESS_KEY=
|
AWS_ACCESS_KEY=
|
||||||
AWS_SECRET_ACCESS_KEY=
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
|
# discord notifs
|
||||||
|
DISCORD_BOT_TOKEN=
|
||||||
@ -49,6 +49,7 @@ exports.handler = async function(context, event, callback) {
|
|||||||
door,
|
door,
|
||||||
fallbackNumbers: config.Item.fallbackNumbers.SS,
|
fallbackNumbers: config.Item.fallbackNumbers.SS,
|
||||||
pressKey: config.Item.pressKey.S,
|
pressKey: config.Item.pressKey.S,
|
||||||
|
discordUsers: config.Item?.discordUsers?.SS || [],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await client.send(ddb.getLockStatusCommand(door))
|
await client.send(ddb.getLockStatusCommand(door))
|
||||||
|
|||||||
15
packages/doorman-api/functions/api/door/notify.js
Normal file
15
packages/doorman-api/functions/api/door/notify.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
exports.handler = async function(context, event, callback) {
|
||||||
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
|
const discordPath = Runtime.getFunctions()['common/discord'].path;
|
||||||
|
const discord = require(discordPath);
|
||||||
|
|
||||||
|
// user must be in "Doorman" server
|
||||||
|
await discord.sendMessageToUser(context,
|
||||||
|
event.discordUser,
|
||||||
|
event.msg,
|
||||||
|
)
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
|
||||||
|
return callback(null, response);
|
||||||
|
};
|
||||||
39
packages/doorman-api/functions/common/discord.private.js
Normal file
39
packages/doorman-api/functions/common/discord.private.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// reusing from refbounty:
|
||||||
|
// https://gitea.chromart.cc/martin/refbounty/src/commit/1f40d7870a530fe7e7acbc3b901024092c9fb90a/packages/server/src/connectors/DiscordConnector.ts
|
||||||
|
// https://gitea.chromart.cc/martin/refbounty/src/commit/1f40d7870a530fe7e7acbc3b901024092c9fb90a/packages/server/src/utils/DiscordUtils.ts
|
||||||
|
|
||||||
|
const { Client, GatewayIntentBits } = require("discord.js");
|
||||||
|
|
||||||
|
let conn;
|
||||||
|
|
||||||
|
exports.getDiscordClient = async (context) => {
|
||||||
|
if (!conn) {
|
||||||
|
const client = new Client({
|
||||||
|
intents: [GatewayIntentBits.DirectMessages],
|
||||||
|
rest: {
|
||||||
|
timeout: 60_000, // 1 minute
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.login(context.DISCORD_BOT_TOKEN);
|
||||||
|
conn = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.sendMessageToUser = async (
|
||||||
|
context,
|
||||||
|
userId,
|
||||||
|
msg,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const client = await exports.getDiscordClient(context);
|
||||||
|
const user = await client.users.fetch(userId);
|
||||||
|
return user.send(msg);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
console.log(`Failed to send msg to ${userId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,12 +4,13 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "twilio-run",
|
"start": "twilio-run --live --port 8080",
|
||||||
"deploy": "twilio-run deploy --load-system-env --env .env.example --service-name doorman --environment=prod --override-existing-project"
|
"deploy": "twilio-run deploy --load-system-env --env .env.example --service-name doorman --environment=prod --override-existing-project"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-dynamodb": "^3.609.0",
|
"@aws-sdk/client-dynamodb": "^3.609.0",
|
||||||
"@twilio/runtime-handler": "1.3.0",
|
"@twilio/runtime-handler": "1.3.0",
|
||||||
|
"discord.js": "^14.16.3",
|
||||||
"twilio": "^3.56"
|
"twilio": "^3.56"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
DOORMAN_URL=https://doorman.chromart.cc
|
DOORMAN_URL=https://doorman.chromart.cc
|
||||||
NTFY_DOMAIN=ntfy.chromart.cc
|
|
||||||
|
|
||||||
# twilio auth
|
# twilio auth
|
||||||
ACCOUNT_SID=
|
ACCOUNT_SID=
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
exports.handler = async function(context, event, callback) {
|
exports.handler = async function(context, event, callback) {
|
||||||
|
|
||||||
let twiml = new Twilio.twiml.VoiceResponse();
|
let twiml = new Twilio.twiml.VoiceResponse();
|
||||||
|
|
||||||
let config = await fetch(context.DOORMAN_URL + `/api/door/info?buzzer=${event.From}`)
|
let config = await fetch(context.DOORMAN_URL + `/api/door/info?buzzer=${event.From}`)
|
||||||
@ -24,7 +23,7 @@ exports.handler = async function(context, event, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = new Promise(() => null, () => null);
|
let configQuery = `config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||||
|
|
||||||
// poll Doorman, to see if we should unlock
|
// poll Doorman, to see if we should unlock
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
@ -33,19 +32,16 @@ exports.handler = async function(context, event, callback) {
|
|||||||
// handle the case where doorman is explictly rejecting the buzzer
|
// handle the case where doorman is explictly rejecting the buzzer
|
||||||
if (res.status === 410) {
|
if (res.status === 410) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
twiml.redirect('/text-me?method=doorman-time-lock');
|
twiml.redirect(`/text-me?method=doorman-time-lock&${configQuery}`);
|
||||||
callback(null, twiml);
|
callback(null, twiml);
|
||||||
promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we got the successful unlock
|
// we got the successful unlock
|
||||||
else if (res.status === 200) {
|
else if (res.status === 200) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
twiml.redirect(`/door-open?fingerprint=${encodeURIComponent(JSON.stringify(body))}&pressKey=${config.pressKey}`);
|
twiml.redirect(`/door-open?fingerprint=${encodeURIComponent(JSON.stringify(body))}&${configQuery}`);
|
||||||
callback(null, twiml);
|
callback(null, twiml);
|
||||||
promise.resolve();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => console.log(err));
|
.catch(err => console.log(err));
|
||||||
@ -53,11 +49,9 @@ exports.handler = async function(context, event, callback) {
|
|||||||
|
|
||||||
// redirect to call after 6s
|
// redirect to call after 6s
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
twiml.redirect(`/call-residents?numbers=${encodeURIComponent(config.fallbackNumbers)}`);
|
twiml.redirect(`/call-residents?${configQuery}`);
|
||||||
callback(null, twiml);
|
callback(null, twiml);
|
||||||
promise.resolve();
|
|
||||||
}, 6000);
|
}, 6000);
|
||||||
|
|
||||||
await promise;
|
// return callback(null, twiml);
|
||||||
return callback(null, twiml);
|
|
||||||
};
|
};
|
||||||
@ -4,13 +4,18 @@
|
|||||||
exports.handler = function(context, event, callback) {
|
exports.handler = function(context, event, callback) {
|
||||||
let twiml = new Twilio.twiml.VoiceResponse();
|
let twiml = new Twilio.twiml.VoiceResponse();
|
||||||
|
|
||||||
// numbers are passed in
|
let config = JSON.parse(event.config);
|
||||||
let numbers = event.numbers.split(',');
|
console.log(config);
|
||||||
|
let configQuery = `config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||||
|
|
||||||
// If no valid answer after timeout, dial all residents until someone picks up
|
// If no valid answer after timeout, dial all residents until someone picks up
|
||||||
let dial = twiml.dial({action: '/text-me?Method=call', timeLimit: 20, timeout: 20});
|
let dial = twiml.dial({
|
||||||
|
action: `/text-me?Method=call&${configQuery}`,
|
||||||
|
timeLimit: 20,
|
||||||
|
timeout: 20
|
||||||
|
});
|
||||||
|
|
||||||
numbers.forEach(number => {
|
config.fallbackNumbers.forEach(number => {
|
||||||
dial.number(number);
|
dial.number(number);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,13 @@
|
|||||||
exports.handler = function(context, event, callback) {
|
exports.handler = function(context, event, callback) {
|
||||||
let twiml = new Twilio.twiml.VoiceResponse();
|
let twiml = new Twilio.twiml.VoiceResponse();
|
||||||
|
|
||||||
let passAlong = `fingerprint=${encodeURIComponent(event.fingerprint)}`;
|
let config = JSON.parse(event.config);
|
||||||
|
let configQuery = `config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||||
|
|
||||||
twiml.play('https://smart-door-buzzer-3172.twil.io/buzzing_up_boosted.mp3');
|
let passAlong = `fingerprint=${encodeURIComponent(event.fingerprint)}&${configQuery}`;
|
||||||
twiml.play({ digits: event.pressKey }); // configured in doorman what button to click and passed into this function
|
|
||||||
|
twiml.play('https://buzzer-2439-prod.twil.io/buzzing_up_boosted.mp3');
|
||||||
|
twiml.play({ digits: config.pressKey }); // configured in doorman what button to click and passed into this function
|
||||||
twiml.pause({ length: 1 });
|
twiml.pause({ length: 1 });
|
||||||
twiml.redirect(`/text-me?Method=doorman&${passAlong}`);
|
twiml.redirect(`/text-me?Method=doorman&${passAlong}`);
|
||||||
|
|
||||||
|
|||||||
@ -4,27 +4,31 @@
|
|||||||
|
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
exports.handler = function(context, event, callback) {
|
exports.handler = async function(context, event, callback) {
|
||||||
let twiml = new Twilio.twiml.VoiceResponse();
|
let twiml = new Twilio.twiml.VoiceResponse();
|
||||||
|
|
||||||
|
// should be a list of strings representing user ids in discord
|
||||||
|
let discordUsers = JSON.parse(event.config).discordUsers || [];
|
||||||
|
|
||||||
let bodyText;
|
let bodyText;
|
||||||
|
|
||||||
if (event.Method == 'doorman') {
|
if (event.Method == 'doorman') {
|
||||||
bodyText = 'Doorman buzzed someone up!';
|
bodyText = 'Doorman buzzed someone up!';
|
||||||
const fingerprint = JSON.parse(event.fingerprint);
|
const fingerprint = JSON.parse(event.fingerprint);
|
||||||
bodyText += `\n\n${JSON.stringify(fingerprint, null, 4)}`;
|
bodyText += `\n\n\`\`\`${JSON.stringify(fingerprint, null, 4)}\`\`\``;
|
||||||
} else if (event.Method == 'doorman-time-lock') {
|
} else if (event.Method == 'doorman-time-lock') {
|
||||||
bodyText = 'Doorman rejected a buzzer call due to time restriction';
|
bodyText = 'Doorman rejected a buzzer call due to time restriction';
|
||||||
} else if (event.Method == 'call') {
|
} else if (event.Method == 'call') {
|
||||||
bodyText = 'Somebody buzzed the door and it dialed through to a phone.';
|
bodyText = 'Somebody buzzed the door and it dialed through to a phone.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// send webhook to ntfy
|
let promises = discordUsers.map((u) =>
|
||||||
fetch(`https://${context.NTFY_DOMAIN}/buzzer`, {
|
fetch(context.DOORMAN_URL + `/api/door/notify?discordUser=${u}&msg=${bodyText}`)
|
||||||
method: "POST",
|
.catch(err => console.log(err))
|
||||||
body: bodyText,
|
);
|
||||||
})
|
|
||||||
.then(res => callback(null, twiml))
|
await Promise.all(promises)
|
||||||
// even if we error then we should just end the call normally
|
.catch (e => console.log(e));
|
||||||
.catch(err => callback(null, twiml));
|
|
||||||
|
return callback(null, twiml);
|
||||||
};
|
};
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "twilio-run --live",
|
"start": "twilio-run --live --port 4500",
|
||||||
"deploy": "twilio-run deploy --load-system-env --env .env.example --service-name buzzer --environment=prod --override-existing-project"
|
"deploy": "twilio-run deploy --load-system-env --env .env.example --service-name buzzer --environment=prod --override-existing-project"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user