├── .gitattributes ├── README.md ├── css ├── main.css └── normalize.min.css ├── img ├── Demo.PNG ├── Thumbs.db ├── cover.jpg └── logo.png ├── index.html └── js └── main.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Sheets CMS 2 | Example of a blog completely made by Google Sheets, Google Forms and Google Apps Scripts. New posts are added via a Google Forms interface and stored in a Google Sheets spreadsheet. Google Apps Script is used to create an API when Deploy As Web-App to make the content accessible in an easy to use format. The blog is designed as a single page application with pagination and category filtering. 3 | 4 | ![Blog screenshot](img/Demo.PNG) 5 | 6 | ## Google Apps Script API 7 | Google Apps Script is a platform to extend Google’s G Suite of online products through a scripting language derived from JavaScript. It’s analogous to VBA, which is built into the majority of Microsoft Office products. To get started with Google Apps Script you can access the online editor by going to *Tools > Script Editor* in the menu bar from a Google Sheets spreadsheet. A script can then be made publicly available by going to *Publish > Deploy as webapp* from the script editor menu bar. Ensure the app is being executed as *me* and that *anyone, even anonymous* has access. 8 | 9 | ``` 10 | var API_KEY = ''; 11 | var SPREADSHEET_ID = ''; 12 | var RESULTS_PER_PAGE = 5; 13 | 14 | function doGet(e) { 15 | if (!isAuthorized(e)) { 16 | return buildErrorResponse('not authorized'); 17 | } 18 | 19 | var options = { 20 | page: getPageParam(e), 21 | category: getCategoryParam(e) 22 | } 23 | 24 | var spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID); 25 | var worksheet = spreadsheet.getSheets()[0]; 26 | var rows = worksheet.getDataRange().sort({column: 2, ascending: false}).getValues(); 27 | 28 | var headings = rows[0].map(String.toLowerCase); 29 | var posts = rows.slice(1); 30 | 31 | var postsWithHeadings = addHeadings(posts, headings); 32 | var postsPublic = removeDrafts(postsWithHeadings); 33 | var postsFiltered = filter(postsPublic, options.category); 34 | 35 | var paginated = paginate(postsFiltered, options.page); 36 | 37 | return buildSuccessResponse(paginated.posts, paginated.pages); 38 | } 39 | 40 | function addHeadings(posts, headings) { 41 | return posts.map(function(postAsArray) { 42 | var postAsObj = {}; 43 | 44 | headings.forEach(function(heading, i) { 45 | postAsObj[heading] = postAsArray[i]; 46 | }); 47 | 48 | return postAsObj; 49 | }); 50 | } 51 | 52 | function removeDrafts(posts, category) { 53 | return posts.filter(function(post) { 54 | return post['published'] === true; 55 | }); 56 | } 57 | 58 | function filter(posts, category) { 59 | return posts.filter(function(post) { 60 | if (category !== null) { 61 | return post['category'].toLowerCase() === category.toLowerCase(); 62 | } else { 63 | return true; 64 | } 65 | }); 66 | } 67 | 68 | function paginate(posts, page) { 69 | var postsCopy = posts.slice(); 70 | var postsChunked = []; 71 | var postsPaginated = { 72 | posts: [], 73 | pages: { 74 | previous: null, 75 | next: null 76 | } 77 | }; 78 | 79 | while (postsCopy.length > 0) { 80 | postsChunked.push(postsCopy.splice(0, RESULTS_PER_PAGE)); 81 | } 82 | 83 | if (page - 1 in postsChunked) { 84 | postsPaginated.posts = postsChunked[page - 1]; 85 | } else { 86 | postsPaginated.posts = []; 87 | } 88 | 89 | if (page > 1 && page <= postsChunked.length) { 90 | postsPaginated.pages.previous = page - 1; 91 | } 92 | 93 | if (page >= 1 && page < postsChunked.length) { 94 | postsPaginated.pages.next = page + 1; 95 | } 96 | 97 | return postsPaginated; 98 | } 99 | 100 | function isAuthorized(e) { 101 | return 'key' in e.parameters && e.parameters.key[0] === API_KEY; 102 | } 103 | 104 | function getPageParam(e) { 105 | if ('page' in e.parameters) { 106 | var page = parseInt(e.parameters['page'][0]); 107 | if (!isNaN(page) && page > 0) { 108 | return page; 109 | } 110 | } 111 | 112 | return 1 113 | } 114 | 115 | function getCategoryParam(e) { 116 | if ('category' in e.parameters) { 117 | return e.parameters['category'][0]; 118 | } 119 | 120 | return null 121 | } 122 | 123 | function buildSuccessResponse(posts, pages) { 124 | var output = JSON.stringify({ 125 | status: 'success', 126 | data: posts, 127 | pages: pages 128 | }); 129 | 130 | return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON); 131 | } 132 | 133 | function buildErrorResponse(message) { 134 | var output = JSON.stringify({ 135 | status: 'error', 136 | message: message 137 | }); 138 | return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON); 139 | 140 | } 141 | ``` 142 | 143 | 144 | If you still have any query regarding the same, please mail your queries at t3rabyt3@protonmail.com. 145 | 146 | PEACE OUT! 147 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: #f8f8f8; 3 | color: #2d3436; 4 | width:100%; 5 | height:100%; 6 | color: #000000; 7 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 8 | font-size: normal; 9 | line-height: 1.5; 10 | } 11 | 12 | a { 13 | color: #1a1a1a; 14 | border-bottom: solid 1px #b2bec3; 15 | text-decoration: none; 16 | } 17 | 18 | a:hover { 19 | border-color: #1a1a1a; 20 | } 21 | 22 | img.imgsize { 23 | position: relative; 24 | left: -450px; 25 | top: -50; 26 | width: 1570px; 27 | height: 850px; 28 | } 29 | 30 | button { 31 | border-radius: 5px; 32 | border: solid 1px #b2bec3; 33 | cursor: pointer; 34 | display: inline-block; 35 | padding: 0.2em 0.5em; 36 | } 37 | 38 | button:hover { 39 | background: #dfe6e9; 40 | border-color: #b2bec3; 41 | } 42 | 43 | button.selected { 44 | border-color: #2d3436; 45 | } 46 | 47 | header, main, footer { 48 | padding: 4em 5%; 49 | } 50 | 51 | header { 52 | background: #2d3436; 53 | color: white; 54 | text-align: center; 55 | } 56 | 57 | header h1 { 58 | margin-bottom: 0; 59 | } 60 | 61 | .information { 62 | background: #dfe6e9; 63 | font-size: 15px; 64 | margin-bottom: 4em; 65 | padding: 2em; 66 | } 67 | 68 | nav { 69 | margin-bottom: 4em; 70 | } 71 | 72 | nav span { 73 | display: block; 74 | font-size: 0.9em; 75 | margin-bottom: 1em; 76 | } 77 | 78 | nav button { 79 | margin: 0 0.5em 1em 0; 80 | } 81 | 82 | article { 83 | margin-bottom: 6em; 84 | } 85 | 86 | .article-details { 87 | border-bottom: solid 2px #dfe6e9; 88 | font-size: 0.9em; 89 | padding-bottom: 1em; 90 | } 91 | 92 | .article-details div:first-child { 93 | margin-bottom: 0.5em; 94 | } 95 | 96 | #notice { 97 | font-weight: bold; 98 | text-align: center; 99 | } 100 | 101 | footer { 102 | background: #dfe6e9; 103 | text-align: center; 104 | } 105 | 106 | footer p { 107 | margin-top: 0; 108 | } 109 | 110 | @media only screen and (min-width : 992px) { 111 | .article-details { 112 | align-items: center; 113 | display: flex; 114 | justify-content: space-between; 115 | } 116 | 117 | .article-details div:first-child { 118 | margin-bottom: 0; 119 | } 120 | 121 | header, main, footer { 122 | padding: 4em 25%; 123 | } 124 | } 125 | 126 | @media only screen and (min-width : 1200px) { 127 | header, main, footer { 128 | padding: 4em 30%; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /css/normalize.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*# sourceMappingURL=normalize.min.css.map */ 2 | -------------------------------------------------------------------------------- /img/Demo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3rabyt3-zz/Blog-Made-With-Google-App-Script/538e25c64e87d975f61d5a5ef10841e228f50342/img/Demo.PNG -------------------------------------------------------------------------------- /img/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3rabyt3-zz/Blog-Made-With-Google-App-Script/538e25c64e87d975f61d5a5ef10841e228f50342/img/Thumbs.db -------------------------------------------------------------------------------- /img/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3rabyt3-zz/Blog-Made-With-Google-App-Script/538e25c64e87d975f61d5a5ef10841e228f50342/img/cover.jpg -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3rabyt3-zz/Blog-Made-With-Google-App-Script/538e25c64e87d975f61d5a5ef10841e228f50342/img/logo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | T3raByt3 7 | 8 | 9 | 10 | 11 | 12 |
13 | T3raByt3 14 |
15 |
16 |
17 |

Powered by its resources & original content, T3raByt3 is the go-to source for Cyber News Feed, Hacking Tutorials, Proof Of Concepts and WalkThrough contents for its dedicated and influential audience, 24x7. 18 |

19 |
20 | 21 |
22 |
23 |
24 |
25 | 28 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const app = function () { 4 | const API_BASE = ''; 5 | const API_KEY = ''; 6 | const CATEGORIES = ['Category-1', 'Category-2', 'Category-3', 'Category-4']; 7 | 8 | const state = {activePage: 1, activeCategory: null}; 9 | const page = {}; 10 | 11 | function init () { 12 | page.notice = document.getElementById('notice'); 13 | page.filter = document.getElementById('filter'); 14 | page.container = document.getElementById('container'); 15 | 16 | _buildFilter(); 17 | _getNewPosts(); 18 | } 19 | 20 | function _getNewPosts () { 21 | page.container.innerHTML = ''; 22 | _getPosts(); 23 | } 24 | 25 | function _getPosts () { 26 | _setNotice('Loading posts'); 27 | 28 | fetch(_buildApiUrl(state.activePage, state.activeCategory)) 29 | .then((response) => response.json()) 30 | .then((json) => { 31 | if (json.status !== 'success') { 32 | _setNotice(json.message); 33 | } 34 | 35 | _renderPosts(json.data); 36 | _renderPostsPagination(json.pages); 37 | }) 38 | .catch((error) => { 39 | _setNotice('Not Sure What Just Happened!! Sorry!!'); 40 | }) 41 | } 42 | 43 | function _buildFilter () { 44 | page.filter.appendChild(_buildFilterLink('T3raByt3', true)); 45 | 46 | CATEGORIES.forEach(function (category) { 47 | page.filter.appendChild(_buildFilterLink(category, false)); 48 | }); 49 | } 50 | 51 | function _buildFilterLink (label, isSelected) { 52 | const link = document.createElement('button'); 53 | link.innerHTML = _capitalize(label); 54 | link.classList = isSelected ? 'selected' : ''; 55 | link.onclick = function (event) { 56 | let category = label === 'T3raByt3' ? null : label.toLowerCase(); 57 | 58 | _resetActivePage(); 59 | _setActiveCategory(category); 60 | _getNewPosts(); 61 | }; 62 | 63 | return link; 64 | } 65 | 66 | function _buildApiUrl (page, category) { 67 | let url = API_BASE; 68 | url += '?key=' + API_KEY; 69 | url += '&page=' + page; 70 | url += category !== null ? '&category=' + category : ''; 71 | 72 | return url; 73 | } 74 | 75 | function _setNotice (label) { 76 | page.notice.innerHTML = label; 77 | } 78 | 79 | function _renderPosts (posts) { 80 | posts.forEach(function (post) { 81 | const article = document.createElement('article'); 82 | article.innerHTML = ` 83 |

${post.title}

84 |
85 |
By ${post.author} on ${_formatDate(post.timestamp)}
86 |
Posted in ${post.category}
87 |
88 | ${_formatContent(post.content)} 89 | `; 90 | page.container.appendChild(article); 91 | }); 92 | } 93 | 94 | function _renderPostsPagination (pages) { 95 | if (pages.next) { 96 | const link = document.createElement('button'); 97 | link.innerHTML = 'Load more posts'; 98 | link.onclick = function (event) { 99 | _incrementActivePage(); 100 | _getPosts(); 101 | }; 102 | 103 | page.notice.innerHTML = ''; 104 | page.notice.appendChild(link); 105 | } else { 106 | _setNotice('...& The Posts End Here!!!! '); 107 | } 108 | } 109 | 110 | function _formatDate (string) { 111 | return new Date(string).toLocaleDateString('en-GB'); 112 | } 113 | 114 | function _formatContent (string) { 115 | return string.split('\n') 116 | .filter((str) => str !== '') 117 | .map((str) => `

${str}

`) 118 | .join(''); 119 | } 120 | 121 | function _capitalize (label) { 122 | return label.slice(0, 1).toUpperCase() + label.slice(1).toLowerCase(); 123 | } 124 | 125 | function _resetActivePage () { 126 | state.activePage = 1; 127 | } 128 | 129 | function _incrementActivePage () { 130 | state.activePage += 1; 131 | } 132 | 133 | function _setActiveCategory (category) { 134 | state.activeCategory = category; 135 | 136 | const label = category === null ? 'T3raByt3' : category; 137 | Array.from(page.filter.children).forEach(function (element) { 138 | element.classList = label === element.innerHTML.toLowerCase() ? 'selected' : ''; 139 | }); 140 | } 141 | 142 | return { 143 | init: init 144 | }; 145 | }(); 146 | --------------------------------------------------------------------------------