mirror of
https://github.com/SrIzan10/lynn-come-out.git
synced 2026-06-06 00:56:57 +00:00
Create ugly website with Slack signin and signature collection
Co-authored-by: SrIzan10 <66965250+SrIzan10@users.noreply.github.com>
This commit is contained in:
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
SLACK_CLIENT_ID=your_slack_client_id_here
|
||||
SLACK_CLIENT_SECRET=your_slack_client_secret_here
|
||||
SLACK_REDIRECT_URI=http://localhost:3000/auth/slack/callback
|
||||
SESSION_SECRET=your_random_session_secret_here
|
||||
PORT=3000
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
.env
|
||||
signatures.json
|
||||
*.log
|
||||
.DS_Store
|
||||
67
README.md
67
README.md
@@ -1,2 +1,69 @@
|
||||
# lynn-come-out
|
||||
lynn come out
|
||||
|
||||
## 🌈 The Ugliest Website Ever! 🌈
|
||||
|
||||
A beautifully ugly website where people can sign in with Slack and sign a petition for Lynn to come out!
|
||||
|
||||
### Features
|
||||
- 🎨 Intentionally terrible design (Comic Sans, rainbow backgrounds, spinning animations!)
|
||||
- 🔐 Slack OAuth sign-in
|
||||
- ✍️ Signature collection system
|
||||
- 📊 Real-time signature counter
|
||||
- 💾 Persistent storage of signatures
|
||||
|
||||
### Setup Instructions
|
||||
|
||||
1. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **Set up Slack App:**
|
||||
- Go to https://api.slack.com/apps
|
||||
- Create a new app
|
||||
- Under "OAuth & Permissions", add redirect URL: `http://localhost:3000/auth/slack/callback`
|
||||
- Under "OAuth & Permissions" > "Scopes", add these scopes:
|
||||
- `identity.basic`
|
||||
- `identity.avatar`
|
||||
- Copy your Client ID and Client Secret
|
||||
|
||||
3. **Configure environment variables:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` and add your Slack credentials:
|
||||
```
|
||||
SLACK_CLIENT_ID=your_slack_client_id_here
|
||||
SLACK_CLIENT_SECRET=your_slack_client_secret_here
|
||||
SLACK_REDIRECT_URI=http://localhost:3000/auth/slack/callback
|
||||
SESSION_SECRET=your_random_session_secret_here
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
4. **Start the server:**
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
5. **Open your browser:**
|
||||
Navigate to http://localhost:3000 and enjoy the ugliest website ever! 🎉
|
||||
|
||||
### How It Works
|
||||
1. Users click "SIGN IN WITH SLACK" 🔐
|
||||
2. They authenticate with their Slack account
|
||||
3. Once signed in, they can sign the petition ✍️
|
||||
4. Signatures are stored persistently and displayed on the page
|
||||
5. Each user can only sign once
|
||||
|
||||
### Files
|
||||
- `server.js` - Express server with Slack OAuth and signature handling
|
||||
- `package.json` - Dependencies and scripts
|
||||
- `.env.example` - Example environment variables
|
||||
- `signatures.json` - Stored signatures (created automatically)
|
||||
|
||||
### Notes
|
||||
- The website is intentionally designed to be as ugly as possible with clashing colors, Comic Sans, spinning animations, and more!
|
||||
- Signatures are stored in a JSON file (signatures.json)
|
||||
- Each user can only sign once (tracked by Slack user ID)
|
||||
|
||||
1201
package-lock.json
generated
Normal file
1201
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
package.json
Normal file
19
package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "lynn-come-out",
|
||||
"version": "1.0.0",
|
||||
"description": "lynn come out",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"@slack/web-api": "^6.9.0"
|
||||
}
|
||||
}
|
||||
430
server.js
Normal file
430
server.js
Normal file
@@ -0,0 +1,430 @@
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET || 'super-secret-key-change-this',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: false } // Set to true if using HTTPS
|
||||
}));
|
||||
|
||||
// Serve static files
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Data file path
|
||||
const SIGNATURES_FILE = path.join(__dirname, 'signatures.json');
|
||||
|
||||
// Initialize signatures file if it doesn't exist
|
||||
if (!fs.existsSync(SIGNATURES_FILE)) {
|
||||
fs.writeFileSync(SIGNATURES_FILE, JSON.stringify([]));
|
||||
}
|
||||
|
||||
// Helper function to read signatures
|
||||
function getSignatures() {
|
||||
const data = fs.readFileSync(SIGNATURES_FILE, 'utf8');
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
// Helper function to save signatures
|
||||
function saveSignatures(signatures) {
|
||||
fs.writeFileSync(SIGNATURES_FILE, JSON.stringify(signatures, null, 2));
|
||||
}
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
const signatures = getSignatures();
|
||||
const isSignedIn = req.session.user ? true : false;
|
||||
const userName = req.session.user ? req.session.user.name : '';
|
||||
const userImage = req.session.user ? req.session.user.image : '';
|
||||
const hasUserSigned = req.session.user ? signatures.some(s => s.userId === req.session.user.id) : false;
|
||||
|
||||
res.send(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LYNN COME OUT!!!</title>
|
||||
<style>
|
||||
@keyframes rainbow {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 50%, 100% { opacity: 1; }
|
||||
25%, 75% { opacity: 0; }
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
background: linear-gradient(45deg, #ff00ff, #00ffff, #ffff00, #ff00ff);
|
||||
background-size: 400% 400%;
|
||||
animation: rainbow 3s ease infinite;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
color: #ff0000;
|
||||
text-shadow: 2px 2px #00ff00;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffff00;
|
||||
border: 10px dashed #ff00ff;
|
||||
padding: 30px;
|
||||
box-shadow: 0 0 50px rgba(255, 0, 255, 0.8);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 72px;
|
||||
text-align: center;
|
||||
animation: bounce 1s infinite;
|
||||
color: #ff0000;
|
||||
text-shadow: 5px 5px #0000ff, 10px 10px #00ff00;
|
||||
text-decoration: underline wavy #ff00ff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#ff00ff,
|
||||
#ff00ff 10px,
|
||||
#00ffff 10px,
|
||||
#00ffff 20px
|
||||
);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
animation: blink 2s infinite;
|
||||
color: #00ff00;
|
||||
text-shadow: 3px 3px #ff0000;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.sign-in-btn {
|
||||
display: block;
|
||||
margin: 30px auto;
|
||||
padding: 20px 40px;
|
||||
font-size: 28px;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
background-color: #ff00ff;
|
||||
color: #ffff00;
|
||||
border: 5px solid #00ff00;
|
||||
cursor: pointer;
|
||||
animation: spin 3s linear infinite;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 0 20px rgba(255, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.sign-in-btn:hover {
|
||||
background-color: #00ffff;
|
||||
color: #ff0000;
|
||||
border-color: #ff00ff;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.signature-btn {
|
||||
display: block;
|
||||
margin: 30px auto;
|
||||
padding: 20px 40px;
|
||||
font-size: 32px;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
background-color: #00ff00;
|
||||
color: #ff0000;
|
||||
border: 8px double #0000ff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 0 30px rgba(0, 255, 0, 0.9);
|
||||
animation: bounce 0.5s infinite;
|
||||
}
|
||||
|
||||
.signature-btn:hover {
|
||||
background-color: #ff0000;
|
||||
color: #ffff00;
|
||||
transform: rotate(5deg) scale(1.1);
|
||||
}
|
||||
|
||||
.signatures {
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
background-color: #00ffff;
|
||||
border: 5px solid #ff0000;
|
||||
}
|
||||
|
||||
.signatures h2 {
|
||||
color: #ff00ff;
|
||||
text-shadow: 2px 2px #ffff00;
|
||||
font-size: 48px;
|
||||
animation: blink 1.5s infinite;
|
||||
}
|
||||
|
||||
.signature-item {
|
||||
background-color: #ffff00;
|
||||
border: 3px dotted #ff00ff;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
.signature-item img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #ff0000;
|
||||
animation: spin 5s linear infinite;
|
||||
}
|
||||
|
||||
.signature-item .name {
|
||||
font-size: 24px;
|
||||
color: #0000ff;
|
||||
text-shadow: 1px 1px #ff00ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.signature-item .timestamp {
|
||||
font-size: 16px;
|
||||
color: #ff0000;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
background-color: #ff00ff;
|
||||
color: #ffff00;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border: 5px ridge #00ff00;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-shadow: 2px 2px #ff0000;
|
||||
}
|
||||
|
||||
.user-info img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid #00ffff;
|
||||
animation: spin 4s linear infinite;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
padding: 10px 20px;
|
||||
font-size: 18px;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
background-color: #ff0000;
|
||||
color: #ffff00;
|
||||
border: 3px solid #00ff00;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 48px;
|
||||
text-align: center;
|
||||
color: #ff00ff;
|
||||
text-shadow: 3px 3px #00ffff;
|
||||
margin: 30px 0;
|
||||
animation: bounce 1.5s infinite;
|
||||
background-color: #00ff00;
|
||||
padding: 20px;
|
||||
border: 5px solid #ff0000;
|
||||
}
|
||||
|
||||
marquee {
|
||||
font-size: 32px;
|
||||
color: #ff0000;
|
||||
background-color: #ffff00;
|
||||
padding: 10px;
|
||||
border: 3px solid #ff00ff;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.already-signed {
|
||||
background-color: #ff0000;
|
||||
color: #ffff00;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border: 5px solid #00ff00;
|
||||
margin: 20px 0;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🌈 LYNN COME OUT!!! 🌈</h1>
|
||||
<marquee behavior="scroll" direction="left">✨ LYNN COME OUT ✨ LYNN COME OUT ✨ LYNN COME OUT ✨</marquee>
|
||||
<div class="subtitle">💖 Sign the Petition NOW!!! 💖</div>
|
||||
|
||||
${isSignedIn ? `
|
||||
<div class="user-info">
|
||||
<img src="${userImage}" alt="User">
|
||||
<div>Welcome, ${userName}! 🎉</div>
|
||||
<a href="/logout" class="logout-btn">Sign Out</a>
|
||||
</div>
|
||||
|
||||
${hasUserSigned ? `
|
||||
<div class="already-signed">
|
||||
✅ YOU ALREADY SIGNED! THANK YOU! ✅
|
||||
</div>
|
||||
` : `
|
||||
<form method="POST" action="/sign">
|
||||
<button type="submit" class="signature-btn">
|
||||
✍️ SIGN THE PETITION ✍️
|
||||
</button>
|
||||
</form>
|
||||
`}
|
||||
` : `
|
||||
<a href="/auth/slack" class="sign-in-btn">
|
||||
🔐 SIGN IN WITH SLACK 🔐
|
||||
</a>
|
||||
`}
|
||||
|
||||
<div class="count">
|
||||
📝 TOTAL SIGNATURES: ${signatures.length} 📝
|
||||
</div>
|
||||
|
||||
<div class="signatures">
|
||||
<h2>💫 SUPPORTERS 💫</h2>
|
||||
${signatures.length === 0 ? '<p style="font-size: 24px; text-align: center; color: #ff0000;">No signatures yet! Be the first! 🎯</p>' : ''}
|
||||
${signatures.map(sig => `
|
||||
<div class="signature-item">
|
||||
<img src="${sig.userImage}" alt="${sig.userName}">
|
||||
<span class="name">${sig.userName}</span>
|
||||
<span class="timestamp">${new Date(sig.timestamp).toLocaleString()}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<marquee behavior="scroll" direction="right">🏳️🌈 LYNN WE SUPPORT YOU 🏳️🌈 COME OUT COME OUT WHEREVER YOU ARE 🏳️🌈</marquee>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
});
|
||||
|
||||
// Slack OAuth - Start
|
||||
app.get('/auth/slack', (req, res) => {
|
||||
const clientId = process.env.SLACK_CLIENT_ID;
|
||||
const redirectUri = process.env.SLACK_REDIRECT_URI;
|
||||
const scopes = 'identity.basic,identity.avatar';
|
||||
|
||||
if (!clientId) {
|
||||
return res.send('<h1>Error: SLACK_CLIENT_ID not configured</h1><p>Please set up your .env file with Slack credentials.</p>');
|
||||
}
|
||||
|
||||
const authUrl = `https://slack.com/oauth/authorize?client_id=${clientId}&scope=${scopes}&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
||||
res.redirect(authUrl);
|
||||
});
|
||||
|
||||
// Slack OAuth - Callback
|
||||
app.get('/auth/slack/callback', async (req, res) => {
|
||||
const code = req.query.code;
|
||||
const clientId = process.env.SLACK_CLIENT_ID;
|
||||
const clientSecret = process.env.SLACK_CLIENT_SECRET;
|
||||
const redirectUri = process.env.SLACK_REDIRECT_URI;
|
||||
|
||||
if (!code) {
|
||||
return res.send('<h1>Error: No authorization code received</h1><a href="/">Go back</a>');
|
||||
}
|
||||
|
||||
try {
|
||||
// Exchange code for access token
|
||||
const tokenUrl = 'https://slack.com/api/oauth.access';
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code: code,
|
||||
redirect_uri: redirectUri
|
||||
});
|
||||
|
||||
const response = await fetch(`${tokenUrl}?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.ok) {
|
||||
// Store user info in session
|
||||
req.session.user = {
|
||||
id: data.user_id,
|
||||
name: data.user.name,
|
||||
image: data.user.image_48 || data.user.image_72 || 'https://via.placeholder.com/48'
|
||||
};
|
||||
res.redirect('/');
|
||||
} else {
|
||||
res.send(`<h1>Error: ${data.error}</h1><p>Failed to authenticate with Slack</p><a href="/">Go back</a>`);
|
||||
}
|
||||
} catch (error) {
|
||||
res.send(`<h1>Error: ${error.message}</h1><a href="/">Go back</a>`);
|
||||
}
|
||||
});
|
||||
|
||||
// Sign the petition
|
||||
app.post('/sign', (req, res) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
const signatures = getSignatures();
|
||||
|
||||
// Check if user already signed
|
||||
if (signatures.some(s => s.userId === req.session.user.id)) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
// Add signature
|
||||
signatures.push({
|
||||
userId: req.session.user.id,
|
||||
userName: req.session.user.name,
|
||||
userImage: req.session.user.image,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
saveSignatures(signatures);
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Logout
|
||||
app.get('/logout', (req, res) => {
|
||||
req.session.destroy();
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
console.log('Make sure to set up your .env file with Slack credentials!');
|
||||
});
|
||||
Reference in New Issue
Block a user