mirror of
https://github.com/neongamerbot-qk/hackclub-nest
synced 2025-06-07 12:09:29 +00:00
feat(batch): almost done zeon:ai_comment
This commit is contained in:
parent
989b582cf4
commit
3f409706fc
11 changed files with 1073 additions and 61 deletions
|
@ -1,6 +0,0 @@
|
|||
SLACK_CLIENT_ID=
|
||||
SLACK_CLIENT_SECRET=
|
||||
SLACK_TOKEN=
|
||||
SPOTIFY_CLIENT_ID=
|
||||
SPOTIFY_CLIENT_SECRET=
|
||||
SPOTIFY_REDIRECT_URI=
|
|
@ -1,15 +0,0 @@
|
|||
- [ ] Slack stuff
|
||||
- - [ ] web api [docs](https://tools.slack.dev/node-slack-sdk/web-api/)
|
||||
- - [ ] oauth [docs](https://tools.slack.dev/node-slack-sdk/oauth)
|
||||
- - [ ] fix the [channel](https://app.slack.com/client/T0266FRGM/C07RE4N7S4B)
|
||||
- - - [ ] Add ping for new-song event & send message ovs
|
||||
- [ ] Spotify
|
||||
- - [ ] web api [docs](https://developer.spotify.com/documentation/web-api/)
|
||||
- - [ ] oauth [docs](https://developer.spotify.com/documentation/general/guides/authorization-guide/)
|
||||
- - [ ] refresh token [docs](https://developer.spotify.com/documentation/general/guides/authorization-guide/#refresh-an-access-token)
|
||||
- - [ ] playlist tools (creation,modifcation,deletion)
|
||||
- [ ] keydb (quick db or smthing)
|
||||
- [ ] express
|
||||
- [ ] transparency of added songs
|
||||
- - [ ] export db -> into csv with properties (slack_id, slack_name, spotify_id, spotify_url)
|
||||
|
|
@ -1,19 +1,48 @@
|
|||
const path = require("path");
|
||||
require("dotenv").config({ path: path.join(__dirname, ".env") });
|
||||
console.debug(process.env);
|
||||
require("dotenv").config();
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const { WebClient } = require("@slack/web-api");
|
||||
const { InstallProvider } = require("@slack/oauth");
|
||||
const { getLoginUrl, refreshToken } = require("./spotify");
|
||||
const FileStore = require('session-file-store')(session);
|
||||
const { InstallProvider, FileInstallationStore } = require("@slack/oauth");
|
||||
const { getLoginUrl, refreshToken, getCredentials, saveCredentials, spotifyRoutes, addSongToPlaylist } = require("./spotify");
|
||||
const { QuickDB } = require("quick.db");
|
||||
|
||||
const db = new QuickDB({
|
||||
filePath: "./data/songs.sqlite",
|
||||
});
|
||||
let cacheDb = {};
|
||||
const app = express();
|
||||
const userScopes = ['identity.avatar', 'identity.basic', 'identity.team']
|
||||
// Initialize
|
||||
const web = new WebClient(process.env.SLACK_TOKEN);
|
||||
const oauth = new InstallProvider({
|
||||
clientId: process.env.SLACK_CLIENT_ID,
|
||||
clientSecret: process.env.SLACK_CLIENT_SECRET,
|
||||
stateSecret: Math.random().toString(36).substring(2),
|
||||
stateSecret: process.env.STATE_SECRET,
|
||||
stateVerification: false,
|
||||
stateStore: new FileInstallationStore(path.join(__dirname, '../data/states.json')),
|
||||
installationStore: new FileInstallationStore(path.join(__dirname, '../data/installations.json')),
|
||||
// installationStore: {
|
||||
|
||||
//}
|
||||
stateStore: {
|
||||
generateStateParam: (installUrlOptions, date) => {
|
||||
// generate a random string to use as state in the URL
|
||||
const randomState = process.env.STATE_SECRET + Math.random().toString(36).substring(7);
|
||||
// save installOptions to cache/db
|
||||
cacheDb[randomState] = installUrlOptions;
|
||||
// myDB.set(randomState, installUrlOptions);
|
||||
// return a state string that references saved options in DB
|
||||
return randomState;
|
||||
},
|
||||
// verifyStateParam's first argument is a date object and the second argument is a string representing the state
|
||||
// verifyStateParam is expected to return an object representing installUrlOptions
|
||||
verifyStateParam: (date, state) => {
|
||||
return cacheDb[state];
|
||||
// fetch saved installOptions from DB using state reference
|
||||
const installUrlOptions = myDB.get(randomState);
|
||||
return installUrlOptions;
|
||||
},
|
||||
}
|
||||
});
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
@ -22,24 +51,164 @@ app.set("views", "src/views");
|
|||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(
|
||||
session({
|
||||
secret: Math.random().toString(36).substring(2),
|
||||
resave: false,
|
||||
secret: process.env.STATE_SECRET,
|
||||
resave: true,
|
||||
store: new FileStore({
|
||||
path: path.join(__dirname, '../data/sessions'),
|
||||
}),
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: true },
|
||||
cookie: { secure: "auto", maxAge: 1000 * 60 * 60 * 24 * 365 },
|
||||
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
const statusMonitor = require('express-status-monitor')({
|
||||
healthChecks: [{
|
||||
protocol: 'http',
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
path: '/',
|
||||
timeout: 1000,
|
||||
interval: 1000,
|
||||
}]
|
||||
});
|
||||
app.use(statusMonitor);
|
||||
app.use((req,res,next) => {
|
||||
// console.debug([req.headers, req.session])
|
||||
next()
|
||||
})
|
||||
} catch (e) {
|
||||
// we can ignore since this is an optional dependency
|
||||
}
|
||||
|
||||
app.get("/login", async (req, res) => {
|
||||
if (req.session.token) {
|
||||
res.redirect("/home");
|
||||
} else {
|
||||
res.redirect(await oauth.generateInstallUrl({
|
||||
// Add the scopes your app needs
|
||||
redirectUri: process.env.SLACK_REDIRECT_URI,
|
||||
scopes: [],
|
||||
|
||||
userScopes: userScopes
|
||||
}))
|
||||
}
|
||||
});
|
||||
app.get('/slack/callback', (req, res) => {
|
||||
// console.debug(req.headers, req.url)
|
||||
oauth.handleCallback(req, res, {
|
||||
success: async (install) => {
|
||||
// typings
|
||||
// user: { token:string , scopes: string[], id: string}
|
||||
// console.log(install)
|
||||
req.session.info = install
|
||||
req.session.token = install.user.token
|
||||
res.redirect('/home')
|
||||
},
|
||||
failure: (err) => {
|
||||
console.log(err);
|
||||
res.send("Failed to install!, please contact neon in the slack!, \n" + err.stack);
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
app.get('/logout', (req,res) => {
|
||||
req.session.destroy();
|
||||
res.redirect('/');
|
||||
})
|
||||
app.get("/", (req, res) => {
|
||||
res.render("index", {
|
||||
title: "Hack Club Spotify Bot",
|
||||
description: "Contribute to the hackclub spotify playlist!",
|
||||
});
|
||||
});
|
||||
app.get("/login", async (req, res) => {
|
||||
if (req.session.token) {
|
||||
}
|
||||
});
|
||||
const errorStrings = [
|
||||
"Invalid CSRF Token!", // token = csrf token
|
||||
"Song is not a track! (or not even a spotify song url)",
|
||||
"Song already exists in the database! (its in the playlist or banned from the playlist)",
|
||||
]
|
||||
app.get('/home', async (req,res) => {
|
||||
if(!req.session.info) return res.redirect('/login');
|
||||
let onetimetoken = Math.random().toString(36).substring(7);
|
||||
cacheDb[onetimetoken] = true;
|
||||
res.render("home", {
|
||||
title: "Hack Club Spotify Bot",
|
||||
description: "Contribute to the hackclub spotify playlist!",
|
||||
userinfo: req.session.info,
|
||||
onetimetoken,
|
||||
error: errorStrings[req.query.error],
|
||||
s: req.query.s
|
||||
});
|
||||
})
|
||||
app.post('/spotify/submitsong', async (req,res) => {
|
||||
if(!req.session.token) return res.redirect('/login');
|
||||
if(!cacheDb[req.query.token]) return res.redirect(`/home?error=0`);
|
||||
delete cacheDb[req.query.token];
|
||||
|
||||
app.listen(process.env.PORT || 3000, () => {
|
||||
console.log("Example app listening on port 3000!");
|
||||
const songurl = req.body.songurl;
|
||||
|
||||
const songuriinfo = require('spotify-uri').parse(songurl);
|
||||
if(songuriinfo.type !== "track") return res.redirect(`/home?error=1`);
|
||||
const alreadyExists = await db.has(songuriinfo.id);
|
||||
if(alreadyExists) return res.redirect(`/home?error=2`);
|
||||
const formattedURI = require('spotify-uri').formatURI(songuriinfo)
|
||||
await db.set(songuriinfo.id, {
|
||||
song_url: songurl,
|
||||
added_by: req.session.info.user.id,
|
||||
added_at: Date.now()
|
||||
});
|
||||
addSongToPlaylist(formattedURI);
|
||||
fetch("https://slack.mybot.saahild.com/send-private", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
channel: "C07RE4N7S4B",
|
||||
text: `:new_spotify: New Song: ${songurl} - added by <@${req.session.info.user.id}>`,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: process.env.AUTH_FOR_ZEON,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(r=>r.json()).then(d => {
|
||||
fetch("https://slack.mybot.saahild.com/send-private", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
channel: "C07RE4N7S4B",
|
||||
thread_ts: d.ts,
|
||||
text: `:thread: Responses about new song here please!`,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: process.env.AUTH_FOR_ZEON,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
});
|
||||
if(!process.env.TESTING) {
|
||||
fetch("https://slack.mybot.saahild.com/send-private", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
channel: "C07RE4N7S4B",
|
||||
text: `<!subteam^S07RGTY93J8>`,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: process.env.AUTH_FOR_ZEON,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
}
|
||||
res.redirect('/home?s=1')
|
||||
})
|
||||
app.get('/spotify/link', async (req,res) => {
|
||||
if(!req.session.info) return res.redirect('/login');
|
||||
if(req.session.info.user.id !== "U07L45W79E1" ) return res.status(401).end("unauthorized");
|
||||
res.redirect(getLoginUrl())
|
||||
})
|
||||
|
||||
spotifyRoutes(app);
|
||||
|
||||
app.listen(process.env.PORT || 3000, async () => {
|
||||
console.log("Example app listening on port 3000!");
|
||||
// if(!await db.has())
|
||||
if(getCredentials() !== null) refreshToken(getCredentials().refresh_token);
|
||||
|
||||
});
|
||||
|
|
|
@ -237,7 +237,43 @@
|
|||
box-shadow: var(--shadow-elevated);
|
||||
transform: scale(1.0625);
|
||||
}
|
||||
.button {
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-weight: var(--font-weight-bold);
|
||||
border-radius: var(--radii-circle);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: var(--shadow-card);
|
||||
letter-spacing: var(--letter-spacing-headline);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: transform 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
text-align: center;
|
||||
line-height: inherit;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: var(--theme-ui-colors-white, #ffffff);
|
||||
background-color: var(--theme-ui-colors-primary, #ec3750);
|
||||
border: 0;
|
||||
font-size: var(--font-2);
|
||||
}
|
||||
|
||||
.button:focus,
|
||||
.button:hover {
|
||||
box-shadow: var(--shadow-elevated);
|
||||
transform: scale(1.0625);
|
||||
}
|
||||
button.lg {
|
||||
font-size: var(--font-3)!important;
|
||||
line-height: var(--line-height-title);
|
||||
|
|
|
@ -21,24 +21,24 @@ async function fetchWebApi(endpoint, method, body) {
|
|||
function getLoginUrl() {
|
||||
const state = generateRandomString(16);
|
||||
const scope = [
|
||||
"ugc-image-upload",
|
||||
"user-read-playback-state",
|
||||
"user-modify-playback-state",
|
||||
"user-read-currently-playing",
|
||||
"app-remote-control",
|
||||
"streaming",
|
||||
// "ugc-image-upload",
|
||||
// "user-read-playback-state",
|
||||
// "user-modify-playback-state",
|
||||
// "user-read-currently-playing",
|
||||
// "app-remote-control",
|
||||
// "streaming",
|
||||
"playlist-read-private",
|
||||
"playlist-read-collaborative",
|
||||
"playlist-modify-private",
|
||||
"playlist-modify-public",
|
||||
"user-follow-modify",
|
||||
"user-follow-read",
|
||||
"user-read-playback-position",
|
||||
"user-top-read",
|
||||
"user-read-recently-played",
|
||||
// "user-follow-modify",
|
||||
// "user-follow-read",
|
||||
// "user-read-playback-position",
|
||||
// "user-top-read",
|
||||
// "user-read-recently-played",
|
||||
"user-library-modify",
|
||||
"user-library-read",
|
||||
"user-read-email",
|
||||
// "user-library-read",
|
||||
// "user-read-email",
|
||||
"user-read-private",
|
||||
].join(" ");
|
||||
|
||||
|
@ -77,7 +77,6 @@ async function fetchWebApi(endpoint, method, body) {
|
|||
},
|
||||
json: true,
|
||||
};
|
||||
console.log(authOptions);
|
||||
const formdm = new URLSearchParams();
|
||||
|
||||
formdm.append("grant_type", "refresh_token");
|
||||
|
@ -90,14 +89,15 @@ async function fetchWebApi(endpoint, method, body) {
|
|||
})
|
||||
.then(async (r) => {
|
||||
const text = await r.text();
|
||||
console.log(text);
|
||||
// console.log(text);
|
||||
return JSON.parse(text);
|
||||
})
|
||||
.then((auth) => {
|
||||
if (!auth.refresh_token) auth.refresh_token = refresh_token;
|
||||
console.log(auth);
|
||||
// console.log(auth);
|
||||
authStuff = auth;
|
||||
token = auth.access_token;
|
||||
saveCredentials(auth);
|
||||
if (auth.expires_in) {
|
||||
setTimeout(() => {
|
||||
refreshToken(auth.refresh_token);
|
||||
|
@ -110,4 +110,87 @@ async function fetchWebApi(endpoint, method, body) {
|
|||
refreshToken(refresh_token);
|
||||
}
|
||||
}
|
||||
|
||||
function saveCredentials(creds) {
|
||||
require('fs').writeFileSync('data/credentials.json', JSON.stringify(creds, null, 2));
|
||||
}
|
||||
function getCredentials() {
|
||||
try {
|
||||
return JSON.parse(require('fs').readFileSync('data/credentials.json', 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
function spotifyRoutes(app) {
|
||||
app.get('/spotify/callback', async (req,res) => {
|
||||
const code = req.query.code || null;
|
||||
const state = req.query.state || null;
|
||||
|
||||
if (state === null) {
|
||||
res.redirect(
|
||||
"/#" +
|
||||
querystring.stringify({
|
||||
error: "state_mismatch",
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const authOptions = {
|
||||
url: "https://accounts.spotify.com/api/token",
|
||||
form: {
|
||||
code: code,
|
||||
redirect_uri: redirect_uri,
|
||||
grant_type: "authorization_code",
|
||||
},
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
Authorization:
|
||||
"Basic " +
|
||||
new Buffer.from(client_id + ":" + client_secret).toString("base64"),
|
||||
},
|
||||
json: true,
|
||||
};
|
||||
const formdm = new URLSearchParams();
|
||||
// Object.entries(authOptions.form).forEach(([key, value]) => {
|
||||
// formdm.append(key, value);
|
||||
// })
|
||||
formdm.append("code", code);
|
||||
formdm.append("redirect_uri", redirect_uri);
|
||||
formdm.append("grant_type", "authorization_code");
|
||||
|
||||
fetch(authOptions.url, {
|
||||
body: formdm,
|
||||
headers: authOptions.headers,
|
||||
method: "POST",
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((auth) => {
|
||||
// console.log(auth);
|
||||
authStuff = auth;
|
||||
saveCredentials(auth)
|
||||
token = auth.access_token;
|
||||
if (auth.expires_in) {
|
||||
setTimeout(() => {
|
||||
refreshToken(auth.refresh_token);
|
||||
}, auth.expires_in * 1000);
|
||||
}
|
||||
res.status(200).end("Successfully logged in!");
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
function addSongToPlaylist(url) {
|
||||
fetchWebApi('v1/playlists/3gRv97fvllFFLVdCH6XzsE/tracks', 'POST', {
|
||||
uris: [url],
|
||||
position: 0,
|
||||
})
|
||||
}
|
||||
module.exports = {
|
||||
getLoginUrl,
|
||||
refreshToken,
|
||||
saveCredentials,
|
||||
getCredentials,
|
||||
spotifyRoutes,
|
||||
addSongToPlaylist
|
||||
|
||||
// getToken
|
||||
}
|
||||
|
|
62
hackclub-spotify-bot/src/views/home.ejs
Normal file
62
hackclub-spotify-bot/src/views/home.ejs
Normal file
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= title %></title>
|
||||
|
||||
<meta property="og:title" content="<%=title%>" />
|
||||
<meta name="twitter:title" content="<%=title%>" />
|
||||
<meta name="description" content="<%=description %>" />
|
||||
<meta property="og:description" content="<%=description %>" />
|
||||
<meta name="twitter:description" content="<%=description %>" />
|
||||
<link rel="stylesheet" href="./hackclub.css" />
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<br>
|
||||
<header>
|
||||
<h1 class="ultratitle">Hackclub spotify </h1>
|
||||
<!-- <p class="headline">Looking for a basic website template that includes Hack Club's theme CSS? Look no further!</p> -->
|
||||
</header>
|
||||
<div>
|
||||
<div class="card container" style="max-width: 550px;">
|
||||
<h2 class="headline">Submit Song</h2>
|
||||
<!-- <p> -->
|
||||
<form action="/spotify/submitsong?token=<%=onetimetoken%>" method="POST">
|
||||
|
||||
<div class="interactive">
|
||||
<label>Song URL</label>
|
||||
<input type="url" name="songurl" placeholder="https://open.spotify.com/track/6aWOvqmjb3343D5sq7zMgl?si=b72b81a97e564dc3" />
|
||||
<br>
|
||||
</div>
|
||||
<!-- </p> -->
|
||||
<% if (s) { %>
|
||||
<div class="success">
|
||||
<h2>Success!</h2>
|
||||
<p>Your song has been added to the playlist!</p>
|
||||
<br>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (error) { %>
|
||||
<div class="error">
|
||||
<%= error %>
|
||||
<br>
|
||||
</div>
|
||||
<% } %>
|
||||
<br>
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="/logout" class="button">Logout</a>
|
||||
</footer>
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
|
@ -16,7 +16,7 @@
|
|||
<center>
|
||||
<br>
|
||||
<header>
|
||||
<h1 class="ultratitle">Hackclub spotify bot</h1>
|
||||
<h1 class="ultratitle">Hackclub spotify</h1>
|
||||
<!-- <p class="headline">Looking for a basic website template that includes Hack Club's theme CSS? Look no further!</p> -->
|
||||
</header>
|
||||
<div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue