├── .babelrc ├── .gitignore ├── README.md ├── dist └── index.html ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── assets │ ├── favicon.jpg │ ├── fb-example.png │ ├── opt1.jpg │ ├── opt2.jpg │ ├── opt3.jpg │ ├── opt4.jpg │ ├── opt5.jpg │ └── rickastley.jpg ├── scripts │ ├── components │ │ ├── botCheck.component.jsx │ │ ├── main.component.jsx │ │ ├── resultDisplay.component.jsx │ │ └── sendForm.component.jsx │ ├── index.jsx │ └── services │ │ ├── formService.jsx │ │ └── pageService.jsx └── styles │ ├── main.scss │ └── partials │ ├── __form.scss │ ├── __globals.scss │ └── __result.scss ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/**/*.js 3 | dist/**/*.css 4 | dist/assets -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom rick roll 2 | 3 | This is a for-fun project designed to let people generate web pages with misleading open graph titles and descriptions, to be able to disguise their rickroll attempts on sites that show webpage previews. 4 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom Rick Roll 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'), 2 | connect = require('gulp-connect'), 3 | shell = require('gulp-shell'), 4 | proxy = require('http-proxy-middleware'), 5 | sass = require('gulp-sass')(require('sass')), 6 | babel = require('gulp-babel'), 7 | autoPrefix = require('gulp-autoprefixer') 8 | webpack = require('gulp-webpack'); 9 | 10 | const paths = { 11 | scripts:['src/scripts/*.jsx','src/scripts/**/*.jsx'], 12 | styles:['src/styles/main.scss','src/styles/**/*.scss'], 13 | assets:['src/assets/**/*.*'] 14 | } 15 | 16 | gulp.task('serve',function(){ 17 | connect.server({ 18 | root:'dist', 19 | port:'3030', 20 | livereload:true, 21 | fallback:'dist/index.html' 22 | }); 23 | }); 24 | 25 | gulp.task('build:scripts',function(){ 26 | var task = gulp.src(paths.scripts) 27 | .pipe(webpack(require('./webpack.config.js'))) 28 | // .pipe(shell('webpack')) 29 | .pipe(gulp.dest('dist/scripts')) 30 | .pipe(connect.reload()); 31 | return task; 32 | }); 33 | 34 | gulp.task('build:styles',function(){ 35 | return gulp.src(paths.styles[0]) 36 | .pipe(sass().on('error',sass.logError)) 37 | .pipe(autoPrefix()) 38 | .pipe(gulp.dest('dist/styles')) 39 | .pipe(connect.reload()); 40 | }); 41 | 42 | gulp.task('copy:assets',function(){ 43 | return gulp.src(paths.assets) 44 | .pipe(gulp.dest('dist/assets/')) 45 | .pipe(connect.reload()); 46 | }); 47 | 48 | gulp.task('watch:scripts',function(){ 49 | gulp.watch(paths.scripts, gulp.series('build:scripts')); 50 | }); 51 | 52 | gulp.task('watch:styles',function(){ 53 | gulp.watch(paths.styles,gulp.series('build:styles')); 54 | }); 55 | 56 | gulp.task('watch:assets',function(){ 57 | gulp.watch(paths.assets,gulp.series('copy:assets')); 58 | }); 59 | 60 | gulp.task('build',gulp.parallel('build:scripts','build:styles','copy:assets')); 61 | gulp.task('watch',gulp.parallel('watch:scripts','watch:styles','watch:assets')); 62 | gulp.task('start',gulp.series('build', gulp.parallel('watch','serve'))); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-rick-roll", 3 | "version": "1.0.0", 4 | "description": "Custom rick roll", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sorenrehkopf/custom-rick-roll.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "global": "^4.4.0", 17 | "gulp-cli": "^2.3.0", 18 | "react": "^15.3.2", 19 | "react-dom": "^15.3.2", 20 | "react-router": "^2.8.1" 21 | }, 22 | "devDependencies": { 23 | "babel-core": "^6.17.0", 24 | "babel-loader": "^6.2.5", 25 | "babel-preset-es2015": "^6.16.0", 26 | "babel-preset-react": "^6.16.0", 27 | "gulp": "^4.0.2", 28 | "gulp-autoprefixer": "^3.1.1", 29 | "gulp-babel": "^6.1.2", 30 | "gulp-connect": "^5.0.0", 31 | "gulp-nodemon": "^2.2.1", 32 | "gulp-sass": "5", 33 | "gulp-shell": "^0.5.2", 34 | "gulp-webpack": "^1.5.0", 35 | "http-proxy-middleware": "^0.17.2", 36 | "sass": "^1.38.0", 37 | "serve": "^1.4.0", 38 | "webpack": "^1.13.2" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/sorenrehkopf/custom-rick-roll/issues" 42 | }, 43 | "homepage": "https://github.com/sorenrehkopf/custom-rick-roll#readme" 44 | } 45 | -------------------------------------------------------------------------------- /src/assets/favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/favicon.jpg -------------------------------------------------------------------------------- /src/assets/fb-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/fb-example.png -------------------------------------------------------------------------------- /src/assets/opt1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/opt1.jpg -------------------------------------------------------------------------------- /src/assets/opt2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/opt2.jpg -------------------------------------------------------------------------------- /src/assets/opt3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/opt3.jpg -------------------------------------------------------------------------------- /src/assets/opt4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/opt4.jpg -------------------------------------------------------------------------------- /src/assets/opt5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/opt5.jpg -------------------------------------------------------------------------------- /src/assets/rickastley.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorenrehkopf/custom-rick-roll/7fdaf63fa1f66312538cb7b4a34d3df6dd9b7114/src/assets/rickastley.jpg -------------------------------------------------------------------------------- /src/scripts/components/botCheck.component.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class BotCheck extends Component{ 4 | 5 | constructor(){ 6 | super(); 7 | this.val = 0; 8 | } 9 | 10 | updateVal(){ 11 | this.val+=1000 12 | this.forceUpdate(); 13 | } 14 | 15 | render(){ 16 | if(this.val < 3000) setTimeout(this.updateVal.bind(this),1000); 17 | return( 18 | 19 | ) 20 | } 21 | 22 | } 23 | 24 | export default BotCheck; -------------------------------------------------------------------------------- /src/scripts/components/main.component.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | import SendForm from './sendForm.component.jsx'; 4 | import ResultDisplay from './resultDisplay.component.jsx'; 5 | 6 | import FormService from '../services/formService.jsx'; 7 | 8 | class Main extends Component { 9 | 10 | constructor(){ 11 | super(); 12 | this.state = { 13 | showForm:true, 14 | responseUrl:false 15 | }; 16 | } 17 | 18 | responseHandler(data){ 19 | this.setState({ 20 | responseUrl:data, 21 | showForm:false 22 | }) 23 | } 24 | 25 | reload(){ 26 | this.setState({ 27 | responseUrl:false, 28 | showForm:true 29 | }); 30 | } 31 | 32 | render(){ 33 | return ( 34 |
35 |
36 |

Custom rick roll generator

37 |

When you share a link on almost all social media and messaging platforms, there is a preview displayed to show people what the link is about.

38 | ex. 39 |

Now you can use this fact to mislead and irritate people!

40 |

Use the form to get a link to a custom webpage that will preview the info you enter, but actually just autoplay Rick Astley's iconic hit Never Gonna Give You Up as soon as the page loads.

41 |

You can even add a brief message to display to your victim while they are reminded that they "wouldn't get this from any other guy."

42 |

Enjoy!

43 |
44 | 45 | 46 |
47 | ) 48 | } 49 | } 50 | 51 | export default Main; 52 | -------------------------------------------------------------------------------- /src/scripts/components/resultDisplay.component.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class ResultDisplay extends Component{ 4 | 5 | preventDefault(e){ 6 | e.preventDefault(); 7 | } 8 | 9 | render(){ 10 | var el = null; 11 | if(this.props.responseUrl){ 12 | el = ( 13 |
14 |
15 |

Here's your link!

16 | 17 |

Visit it here!

18 |

To manage hosting costs, your page will be deleted in 72 hours. Use it before then!

19 | 20 | 24 | 25 | 26 |
27 |
28 | ); 29 | }; 30 | return el; 31 | } 32 | } 33 | 34 | export default ResultDisplay; 35 | -------------------------------------------------------------------------------- /src/scripts/components/sendForm.component.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import PageService from '../services/pageService.jsx'; 4 | import FormService from '../services/formService.jsx'; 5 | 6 | import BotCheck from './botCheck.component.jsx'; 7 | 8 | var imgInput; 9 | 10 | class SendForm extends Component { 11 | 12 | constructor(props){ 13 | super(props); 14 | this.submitForm = this.submitForm.bind(this); 15 | this.imgTimeout; 16 | this.state = { 17 | selectedImg:0, 18 | imgs:[ 19 | '', 20 | 'opt1.jpg', 21 | 'opt2.jpg', 22 | 'opt3.jpg', 23 | 'opt4.jpg', 24 | 'opt5.jpg' 25 | ] 26 | } 27 | } 28 | 29 | submitForm(e){ 30 | e.preventDefault(); 31 | var data = FormService.getJSON(e.target); 32 | if(FormService.botCheck(e.target)){ 33 | PageService.createPage(data) 34 | .then(this.props.responseHandler); 35 | }else{ 36 | this.props.responseHandler("http://localhost:3000/index.html") 37 | } 38 | } 39 | 40 | componentDidUpdate(){ 41 | imgInput = document.getElementById('img-input'); 42 | } 43 | 44 | componentDidMount(){ 45 | imgInput = document.getElementById('img-input'); 46 | } 47 | 48 | updateImgPreview(e){ 49 | clearTimeout(this.imgTimeout); 50 | var thiz = this; 51 | this.imgTimeout = setTimeout(function(){ 52 | var imgs = thiz.state.imgs; 53 | imgs[0] = imgInput.value; 54 | thiz.setState({ 55 | imgs:imgs, 56 | selectedImg:0 57 | }); 58 | },1000); 59 | } 60 | 61 | chooseImg(i){ 62 | var url = i?window.location.origin+"/assets/"+this.state.imgs[i]:this.state.imgs[i]; 63 | imgInput.value = url; 64 | this.setState({ 65 | selectedImg:i 66 | }); 67 | } 68 | 69 | render(){ 70 | var el = null; 71 | if(this.props.showMe){ 72 | var imgs = this.state.imgs.map((img,i)=>{ 73 | var message = img?'':Paste the url to your image to see the preview here!; 74 | var img = i?window.location.origin+"/assets/"+img:img; 75 | var style = { 76 | backgroundImage:"url("+img+")" 77 | } 78 | return
79 | {message} 80 |
81 | }); 82 | el = ( 83 |
84 |
85 | 86 | 87 | 88 |
89 | {imgs} 90 |
91 | 92 | 93 | 94 | 98 |
99 | 100 |
101 | ); 102 | } 103 | return el; 104 | } 105 | 106 | } 107 | 108 | export default SendForm; 109 | -------------------------------------------------------------------------------- /src/scripts/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | //custom components 5 | import Main from './components/main.component.jsx'; 6 | 7 | render(
, 8 | document.getElementById('app') 9 | ); -------------------------------------------------------------------------------- /src/scripts/services/formService.jsx: -------------------------------------------------------------------------------- 1 | var sourceOptionsMap = { 2 | blog: ['https://www.thisworldthesedays.com', 'https://www.tomorrowtides.com'], 3 | paper: ['https://www.theraleighregister.com', 'https://www.sanfransentinel.com'] 4 | } 5 | 6 | class FormService { 7 | 8 | static getJSON(el){ 9 | var inputs = el.querySelectorAll('[name]'); 10 | var data = {}; 11 | 12 | for(var i = 0;i < inputs.length ;i++) { 13 | if (inputs[i].getAttribute('name') === 'source') { 14 | data[inputs[i].getAttribute('name')] = sourceOptionsMap[inputs[i].value][Math.round(Math.random())]; 15 | } else { 16 | data[inputs[i].getAttribute('name')] = inputs[i].value; 17 | } 18 | }; 19 | return data; 20 | }; 21 | 22 | static botCheck(el){ 23 | var input = el.querySelector('[name="botcheck"]'); 24 | console.log(input); 25 | return input.value > 2999; 26 | }; 27 | 28 | } 29 | 30 | export default FormService; -------------------------------------------------------------------------------- /src/scripts/services/pageService.jsx: -------------------------------------------------------------------------------- 1 | var apiUrls = { 2 | 'localhost':'http://localhost:3000', 3 | 'www.secretrickroll.com':'https://www.theraleighregister.com' 4 | }; 5 | 6 | var apiUrl = apiUrls[window.location.hostname]; 7 | 8 | class PageService { 9 | 10 | static createPage(content){ 11 | return new Promise(function(resolve,reject){ 12 | var http = new XMLHttpRequest(); 13 | http.open( 14 | 'POST', 15 | apiUrl+'/api/pages/create' 16 | ) 17 | http.setRequestHeader("Content-Type","application/json"); 18 | http.send(JSON.stringify(content)); 19 | http.onreadystatechange = function(){ 20 | if(http.readyState === 4){ 21 | if(http.status===200){; 22 | resolve(http.response); 23 | } 24 | else reject(http.status); 25 | } 26 | }; 27 | }) 28 | 29 | } 30 | 31 | } 32 | 33 | export default PageService; -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import 'partials/__globals', 2 | 'partials/__form', 3 | 'partials/__result'; -------------------------------------------------------------------------------- /src/styles/partials/__form.scss: -------------------------------------------------------------------------------- 1 | .form,.result{ 2 | max-width:100%; 3 | flex:1; 4 | box-sizing:border-box; 5 | color:black; 6 | padding:30px 5%; 7 | height:100%; 8 | display:flex; 9 | flex-direction:column; 10 | justify-content:center; 11 | background-image:url(../assets/rickastley.jpg); 12 | background-size:cover; 13 | background-attachment: fixed; 14 | .img-div{ 15 | width:100%; 16 | overflow:auto; 17 | display:flex; 18 | align-items:flex-start; 19 | background-color:white; 20 | height:14vw; 21 | &__img{ 22 | height:100%; 23 | width:31%; 24 | height:100%; 25 | flex-shrink:0; 26 | background-position:center; 27 | background-size:cover; 28 | display:flex; 29 | align-items:center; 30 | &.selected{ 31 | border:2px solid blue; 32 | height:calc(100% - 5px); 33 | } 34 | } 35 | } 36 | .select{ 37 | height:auto; 38 | } 39 | } 40 | @media all and (max-width: 768px){ 41 | .form,.result{ 42 | height:auto; 43 | width:100%; 44 | .img-div{ 45 | height:28vw; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/partials/__globals.scss: -------------------------------------------------------------------------------- 1 | body{ 2 | display:flex; 3 | justify-content:center; 4 | align-items:center; 5 | flex-direction:column; 6 | color:white; 7 | p,h1,h2,h3,h4,h5,input,button,label,textarea,span,div{ 8 | font-family:"Source Sans Pro", sans-serif; 9 | } 10 | #app{ 11 | min-height:100vh; 12 | width:100%; 13 | } 14 | .app-container{ 15 | width:100%; 16 | display:flex; 17 | height:100vh; 18 | align-items:center; 19 | justify-content:center; 20 | flex-wrap:wrap; 21 | box-sizing:border-box; 22 | background-color:rgba(50,50,50,.8); 23 | border-radius:5px; 24 | &__instructions{ 25 | flex:1; 26 | max-width:100%; 27 | height:100%; 28 | background-color:black; 29 | display:flex; 30 | flex-direction:column; 31 | justify-content:center; 32 | align-items:flex-end; 33 | padding:0px 5%; 34 | box-sizing:border-box; 35 | >*{ 36 | width:100%; 37 | 38 | } 39 | &--example{ 40 | width:100%; 41 | align-self:flex-start; 42 | } 43 | } 44 | } 45 | .hidden{ 46 | width:0px; 47 | height:0px; 48 | overflow:hidden; 49 | } 50 | .button-success, 51 | .button-error, 52 | .button-warning, 53 | .button-secondary { 54 | color: white; 55 | border-radius: 4px; 56 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 57 | } 58 | .button-success { 59 | background: rgb(28, 184, 65); /* this is a green */ 60 | } 61 | 62 | .button-error { 63 | background: rgb(202, 60, 60); /* this is a maroon */ 64 | } 65 | 66 | .button-warning { 67 | background: rgb(223, 117, 20); /* this is an orange */ 68 | } 69 | 70 | .button-secondary { 71 | background: rgb(66, 184, 221); /* this is a light blue */ 72 | } 73 | @media all and (max-width: 768px){ 74 | .app-container{ 75 | flex-direction:column; 76 | height:auto; 77 | &__instructions{ 78 | padding:5%; 79 | height:auto; 80 | width:100%; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/styles/partials/__result.scss: -------------------------------------------------------------------------------- 1 | .result{ 2 | color:white; 3 | >div{ 4 | background-color:rgba(50,50,50,.7); 5 | padding:8%; 6 | border-radius:5px; 7 | >button{ 8 | width:100%; 9 | } 10 | } 11 | &__show{ 12 | color:#cdcd88; 13 | } 14 | &__input{ 15 | color:black; 16 | width:100%; 17 | padding:5px; 18 | } 19 | &__copy-btn{ 20 | margin-bottom:5px; 21 | } 22 | 23 | &__support-cta { 24 | width: 100%; 25 | display: block; 26 | margin-bottom: 10px; 27 | text-decoration: none; 28 | 29 | button { 30 | width: 100%; 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | 36 | img { 37 | border-radius: 50%; 38 | margin-right: 10px; 39 | } 40 | } 41 | 42 | @media all and (max-width: 764px){ 43 | border-radius:0px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var build_dir = path.resolve(__dirname, 'dist/scripts'); 4 | var app_dir = path.resolve(__dirname, 'src'); 5 | 6 | var config = { 7 | entry: app_dir+'/scripts/index.jsx', 8 | output: { 9 | path:build_dir, 10 | filename:'bundle.js' 11 | }, 12 | module:{ 13 | loaders:[ 14 | { 15 | test:/\.jsx?/, 16 | include:app_dir, 17 | loader:'babel' 18 | } 19 | ] 20 | } 21 | }; 22 | 23 | module.exports = config; --------------------------------------------------------------------------------