├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── README.md ├── components ├── FilmRating.jsx ├── Footer.jsx ├── Head.jsx ├── ImagesResult.jsx ├── Karya.jsx ├── News │ └── NewsResults.jsx ├── Pagination.jsx ├── Panelnya.jsx ├── PeopleAlsoSearch.jsx ├── RelatedSearch.jsx ├── SearchResult.jsx ├── User.jsx ├── VideoResult.jsx └── YNTKTS.jsx ├── lib ├── ga.js └── randomString.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── api │ ├── gnews.js │ ├── hello.js │ ├── images.js │ ├── news.js │ ├── search.js │ └── search2.js ├── images.jsx ├── index.jsx ├── news.jsx └── search.jsx ├── postcss.config.js ├── public ├── Avatar.png ├── favicon.ico ├── loading-500.gif ├── loading.gif ├── vercel.svg └── yntkts.png ├── styles ├── Home.module.css └── globals.css └── tailwind.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "printWidth": 80, 4 | 5 | "trailingComma": "es5", 6 | 7 | "useTabs": false, 8 | 9 | "tabWidth": 2, 10 | 11 | "semi": true, 12 | 13 | "singleQuote": true, 14 | 15 | "bracketSpacing": true, 16 | 17 | "jsxBracketSameLine": false 18 | 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Gatotkaca Search Engine 4 | 5 | License : MIT 6 |
7 | Gatotkaca search engine adalah website search engine seperti pada umumnya, untuk datanya sendiri adalah hasil scraping dari goole. 8 | 9 |
10 | 11 | ## Instalation 12 | 13 | ```bash 14 | # Clone this repo 15 | https://github.com/bagusok/gatotkaca 16 | 17 | # Install All Module 18 | npm install 19 | 20 | # Start Development 21 | npm run dev 22 | 23 | # Build Production 24 | npm run build 25 | npm start 26 | ``` 27 | 28 | ## Library yang digunakan 29 | 30 | [Google-this](https://github.com/LuanRT/google-this 'Google this') 31 |
32 | [Neo Scraper Google News](https://github.com/adarshsingh1407/neo-google-news-scraper 'Neo Scraper Google News') 33 |
34 | [Youtube Search No LImit](https://github.com/Eloquentia-Studios/youtube-search-no-limit 'Youtube Search No LImit') 35 | 36 | ## Preview Images 37 | 38 | ![indonesia Gatotkaca Search](https://user-images.githubusercontent.com/52571584/211183225-71bd2482-2853-4704-b484-352f93e4ad39.png) 39 | 40 | ![indonesia Gatotkaca Search (1)](https://user-images.githubusercontent.com/52571584/211183230-b6955f50-c8e1-4391-8cd9-1d1f2789183c.png) 41 | 42 | ![indonesia Gatotkaca Search (2)](https://user-images.githubusercontent.com/52571584/211183237-23219e5f-b8ca-4cb8-87ac-426cd20dd3cc.png) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /components/FilmRating.jsx: -------------------------------------------------------------------------------- 1 | export default function FilmRating({ data }) { 2 | return ( 3 |
4 |
5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /components/Footer.jsx: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /components/Head.jsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Script from 'next/script'; 3 | 4 | export default function HeadMeta({ title }) { 5 | return ( 6 | <> 7 | 8 | {title || 'Gatotkaca Search'} 9 | 10 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /components/ImagesResult.jsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | export default function ImageResults({ image }) { 4 | return ( 5 | <> 6 |
7 | {image && 8 | image.map((a, i) => { 9 | return ( 10 |
14 |
15 | images 23 |
24 |
25 | 29 |

30 | {a.origin.title} 31 |

32 |
33 |

34 | {a.origin.website.domain} 35 |

36 |
37 |
38 | ); 39 | })} 40 |
41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /components/Karya.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Karya({ karya }) { 4 | return ( 5 | <> 6 | {karya.length > 0 && ( 7 | <> 8 |

9 | Karya 10 |

11 |
12 | {karya?.map((a, i) => { 13 | return ( 14 |
15 | 16 |
17 |
18 |

19 | {a.title} : {a.year || a.album || a.episode} 20 |

21 |
22 |
23 | 24 |
25 | ); 26 | })} 27 |
28 | 29 | )} 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /components/News/NewsResults.jsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | 4 | export default function NewsResults({ data }) { 5 | return ( 6 | <> 7 |
8 | {data && 9 | data.map((a, i) => { 10 | return ( 11 |
12 |
13 |
14 |

15 | {a.source} 16 |

17 | 21 | {a.title} 22 | 23 | 24 |

25 | {a.time} 26 |

27 |
28 |
29 | images 37 |
38 |
39 |
40 | ); 41 | })} 42 |
43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /components/Pagination.jsx: -------------------------------------------------------------------------------- 1 | import { FiChevronsLeft, FiChevronsRight } from 'react-icons/fi'; 2 | import { useRouter } from 'next/router'; 3 | import { useEffect, useState } from 'react'; 4 | 5 | export default function Pagination() { 6 | const router = useRouter(); 7 | const { q, page } = router.query; 8 | 9 | const [pages, setPages] = useState(page || 0); 10 | 11 | useEffect(() => { 12 | const { q, page } = router.query; 13 | if (page < 1) { 14 | handlePagination(1); 15 | } 16 | setPages(page); 17 | }); 18 | 19 | const handlePagination = (e) => { 20 | router.push({ query: { q: q, page: e } }); 21 | }; 22 | 23 | return ( 24 | <> 25 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /components/Panelnya.jsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import { useState } from 'react'; 3 | import Karya from './Karya'; 4 | 5 | export default function Panel({ panel }) { 6 | const [seeMore, setSeeMore] = useState(false); 7 | 8 | return ( 9 | <> 10 |
11 |
12 |
13 |

{panel.title}

14 |

{panel.type}

15 |
16 |
17 |
18 | {panel.images && 19 | panel.images.map((a, i) => { 20 | return ( 21 |
22 |
23 | images 31 |
32 |
33 | ); 34 | })} 35 |
36 |
37 |

38 | {panel.description !== 'N/A' && panel.description} 39 |

40 |
41 | {panel.metadata?.map((a, i) => { 42 | if (a !== undefined) { 43 | return ( 44 |

45 | 46 | {a.title}: {''} 47 | 48 | {a.value} 49 |

50 | ); 51 | } 52 | })} 53 | 54 | {panel.ratings.length > 0 && ( 55 |

56 | Rating: 57 | {panel.ratings[0]?.rating} 58 |

59 | )} 60 | 61 | {panel.lyrics?.length > 0 && ( 62 |
67 |
68 | 75 |
76 |

77 | {panel.lyrics} 78 |

79 |
80 | )} 81 |
82 |
83 |
84 | 0 87 | ? panel.tv_shows_and_movies 88 | : panel.books.length > 0 89 | ? panel.books 90 | : panel.songs.length > 0 91 | ? panel.songs 92 | : [] 93 | } 94 | /> 95 |
96 |
97 | 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /components/PeopleAlsoSearch.jsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | 4 | export default function PeopleAlsoSearch({ data }) { 5 | return ( 6 | <> 7 | {data.length > 0 && ( 8 | <> 9 | {' '} 10 |

11 | Orang Juga Mencari 12 |

13 |
14 | {data && 15 | data.map((a, i) => { 16 | return ( 17 | 18 |
19 |
20 | people 28 |
29 |
30 |

{a.title}

31 |
32 |
33 | 34 | ); 35 | })} 36 |
37 | 38 | )} 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /components/RelatedSearch.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function RelatedSearch({ data }) { 4 | return ( 5 | <> 6 | {data.length > 0 && ( 7 | <> 8 |

Related Search

9 |
10 | {data && 11 | data.map((a, i) => { 12 | return ( 13 | 14 |
15 |

{a}

16 |
17 | 18 | ); 19 | })} 20 |
21 | 22 | )} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /components/SearchResult.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import VideoResult from './VideoResult'; 3 | 4 | export default function SearchResult({ search, videos }) { 5 | return ( 6 |
7 | {/* {videos && } */} 8 | {search && 9 | search.map((a, i) => { 10 | return ( 11 |
12 |

13 | {a.url} 14 |

15 | 16 | 17 | {a.title} 18 | 19 | 20 |

21 | {a.description} 22 |

23 |
24 | ); 25 | })} 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/User.jsx: -------------------------------------------------------------------------------- 1 | import { FiUser } from 'react-icons/fi'; 2 | import Image from 'next/image'; 3 | import { useState } from 'react'; 4 | 5 | export default function User() { 6 | const [isOpen, setIsOpen] = useState(false); 7 | const handleClick = () => { 8 | window.location.href = 'https://bagusok.github.io/'; 9 | }; 10 | 11 | return ( 12 | <> 13 |
14 | setIsOpen(!isOpen)} 19 | /> 20 |
21 | {isOpen && ( 22 |
23 |
24 | setIsOpen(!isOpen)} 29 | /> 30 |

Hi, Bro

31 |
32 |
handleClick()} 35 | > 36 | 37 |

38 | Author 39 |

40 |
41 |
42 | )} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /components/VideoResult.jsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | export default function VideoResult({ videos }) { 4 | return ( 5 | <> 6 | {videos && ( 7 |
8 |

Related Videos

9 |
10 | {videos.map((a, i) => { 11 | return ( 12 |
13 |
14 |
15 | {a.title} 23 |
24 |
25 |

29 | {a.title} 30 |

31 |

32 | {a.channel.name} 33 |

34 |
35 |
36 |
37 | ); 38 | })} 39 |
40 |
41 | )} 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /components/YNTKTS.jsx: -------------------------------------------------------------------------------- 1 | export default function YNTKTS() { 2 | return ( 3 |
4 | yntkts 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /lib/ga.js: -------------------------------------------------------------------------------- 1 | export const pageview = (url) => { 2 | window.gtag('config', process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS, { 3 | page_path: url, 4 | }); 5 | }; 6 | 7 | // log specific events happening. 8 | export const event = ({ action, params }) => { 9 | window.gtag('event', action, params); 10 | }; 11 | -------------------------------------------------------------------------------- /lib/randomString.js: -------------------------------------------------------------------------------- 1 | const characters = 2 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 3 | 4 | export default function randomString(length) { 5 | let result = ' '; 6 | const charactersLength = characters.length; 7 | for (let i = 0; i < length; i++) { 8 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 9 | } 10 | 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: [ 5 | 'img.youtube.com', 6 | 'i.ytimg.com', 7 | 'encrypted-tbn0.gstatic.com', 8 | 'lh3.googleusercontent.com', 9 | ], 10 | }, 11 | reactStrictMode: true, 12 | swcMinify: true, 13 | }; 14 | 15 | module.exports = nextConfig; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatotkaca", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "type": "module", 7 | "dev": "next dev -p 3001", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.27.2", 14 | "bing-scraper": "^1.4.11", 15 | "cheerio": "^1.0.0-rc.12", 16 | "google-news-json": "^2.1.0", 17 | "googlethis": "^1.3.0", 18 | "neo-scraper-google-news": "^1.3.9", 19 | "next": "12.2.4", 20 | "nextjs-cors": "^2.1.1", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "react-icons": "^4.4.0", 24 | "tailwind-scrollbar": "github:adoxography/tailwind-scrollbar#pull/36/head", 25 | "tailwind-scrollbar-hide": "^1.1.7", 26 | "youtube-search-no-limit": "^1.0.1", 27 | "youtube-search-without-api-key": "^1.0.7" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^10.4.8", 31 | "eslint": "8.21.0", 32 | "eslint-config-next": "12.2.4", 33 | "postcss": "^8.4.16", 34 | "tailwindcss": "^3.1.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import { useRouter } from 'next/router'; 3 | import { pageview } from '../lib/ga'; 4 | import { useEffect } from 'react'; 5 | 6 | function MyApp({ Component, pageProps }) { 7 | const router = useRouter(); 8 | 9 | useEffect(() => { 10 | const handleRouteChange = (url) => { 11 | pageview(url); 12 | }; 13 | 14 | router.events.on('routeChangeComplete', handleRouteChange); 15 | 16 | return () => { 17 | router.events.off('routeChangeComplete', handleRouteChange); 18 | }; 19 | }, [router.events]); 20 | 21 | return ; 22 | } 23 | 24 | export default MyApp; 25 | -------------------------------------------------------------------------------- /pages/api/gnews.js: -------------------------------------------------------------------------------- 1 | import googleNewsAPI from 'google-news-json'; 2 | import { 3 | searchGoogleNews, 4 | searchGoogleNewsTopics, 5 | } from 'neo-scraper-google-news'; 6 | 7 | export default async function handler(req, res) { 8 | // let news = await googleNewsAPI.getNews(googleNewsAPI.TOP_NEWS, null, 'id-ID'); 9 | 10 | if (req.method !== 'GET') 11 | return res 12 | .status(405) 13 | .json({ status: 'failed', message: 'Metode tidak valid.' }); 14 | 15 | // if (!req.headers['x-csrf-token'] || req.headers['x-csrf-token'].length !== 32) 16 | // return res 17 | // .status(405) 18 | // .json({ status: 'failed', message: 'Metode tidak valid bang.' }); 19 | 20 | // if (!req.query.q) 21 | // return res 22 | // .status(405) 23 | // .json({ status: 'failed', message: 'Query tidak boleh kosong.' }); 24 | 25 | const cheerio = require('cheerio'); 26 | const axios = require('axios'); 27 | 28 | const searchString = 'Berita+hari ini'; // what we want to search 29 | const encodedString = encodeURI(searchString); // what we want to search for in URI encoding 30 | 31 | const AXIOS_OPTIONS = { 32 | headers: { 33 | 'User-Agent': 34 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36', 35 | }, // adding the User-Agent header as one way to prevent the request from being blocked 36 | params: { 37 | q: encodedString, // our encoded search string 38 | tbm: 'nws', // parameter defines the type of search you want to do ("nws" means news) 39 | hl: 'id', // Parameter defines the language to use for the Google search 40 | gl: 'ID', 41 | start: 2, // parameter defines the country to use for the Google search 42 | }, 43 | }; 44 | 45 | function getNewsInfo() { 46 | return axios 47 | .get(`http://google.com/search`, AXIOS_OPTIONS) 48 | .then(function ({ data }) { 49 | let $ = cheerio.load(data); 50 | 51 | const pattern = /s='(?[^']+)';\w+\s\w+=\['(?\w+_\d+)'];/gm; 52 | const images = [...data.matchAll(pattern)].map(({ groups }) => ({ 53 | id: groups.id, 54 | img: groups.img.replace('\\x3d', ''), 55 | })); 56 | 57 | const allNewsInfo = Array.from($('.WlydOe')).map((el) => { 58 | return { 59 | link: $(el).attr('href'), 60 | source: $(el).find('.CEMjEf span').text().trim(), 61 | title: $(el).find('.nDgy9d').text().trim().replace('\n', ''), 62 | snippet: $(el).find('.GI74Re').text().trim().replace('\n', ''), 63 | image: 64 | images.find( 65 | ({ id, img }) => id === $(el).find('.uhHOwf img').attr('id') 66 | )?.img || 'No image', 67 | date: $(el).find('.ZE0LJd span').text().trim(), 68 | }; 69 | }); 70 | 71 | return allNewsInfo; 72 | }); 73 | } 74 | 75 | getNewsInfo().then((resp) => { 76 | return res.status(200).json(resp); 77 | }); 78 | // googleNewsAPI.getNews( 79 | // googleNewsAPI.SEARCH, 80 | // 'bayu', 81 | // 'id-ID', 82 | // (err, response) => { 83 | // return res.status(200).json(response); 84 | // } 85 | // ); 86 | } 87 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/api/images.js: -------------------------------------------------------------------------------- 1 | import google from 'googlethis'; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method !== 'GET') 5 | return res 6 | .status(405) 7 | .json({ status: 'failed', message: 'Metode tidak valid.' }); 8 | 9 | if (!req.headers['x-csrf-token'] || req.headers['x-csrf-token'].length !== 32) 10 | return res 11 | .status(405) 12 | .json({ status: 'failed', message: 'Metode tidak valid bang.' }); 13 | 14 | if (!req.query.q) 15 | return res 16 | .status(404) 17 | .json({ status: 'failed', message: 'Query tidak boleh kosong.' }); 18 | 19 | const searchQuery = req.query.q; 20 | const response = await google.image(searchQuery, { safe: false }); 21 | 22 | return res.status(200).json({ 23 | status: 'success', 24 | results: response, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /pages/api/news.js: -------------------------------------------------------------------------------- 1 | import googleNewsAPI from 'google-news-json'; 2 | import { 3 | searchGoogleNews, 4 | searchGoogleNewsTopics, 5 | } from 'neo-scraper-google-news'; 6 | 7 | export default async function handler(req, res) { 8 | // let news = await googleNewsAPI.getNews(googleNewsAPI.TOP_NEWS, null, 'id-ID'); 9 | 10 | if (req.method !== 'GET') 11 | return res 12 | .status(405) 13 | .json({ status: 'failed', message: 'Metode tidak valid.' }); 14 | 15 | if (!req.headers['x-csrf-token'] || req.headers['x-csrf-token'].length !== 32) 16 | return res 17 | .status(405) 18 | .json({ status: 'failed', message: 'Metode tidak valid bang.' }); 19 | 20 | if (!req.query.q) 21 | return res 22 | .status(405) 23 | .json({ status: 'failed', message: 'Query tidak boleh kosong.' }); 24 | 25 | try { 26 | const news = await searchGoogleNews({ 27 | searchTerm: encodeURI(req.query.q), 28 | shouldFetchPrettyUrls: false, 29 | shouldFetchOGData: false, 30 | queryVars: { 31 | hl: 'id-ID', 32 | gl: 'ID', 33 | ceid: 'ID:id', 34 | }, 35 | timeframe: '1d', 36 | }); 37 | 38 | return res.status(200).json({ status: 'success', results: news }); 39 | } catch (err) { 40 | return res.status(500).json({ status: 'error', message: 'Error.' }); 41 | } 42 | 43 | // googleNewsAPI.getNews( 44 | // googleNewsAPI.SEARCH, 45 | // 'bayu', 46 | // 'id-ID', 47 | // (err, response) => { 48 | // return res.status(200).json(response); 49 | // } 50 | // ); 51 | } 52 | -------------------------------------------------------------------------------- /pages/api/search.js: -------------------------------------------------------------------------------- 1 | import * as yt from 'youtube-search-without-api-key'; 2 | import { search } from 'youtube-search-no-limit'; 3 | const bing = require('bing-scraper'); 4 | import NextCors from 'nextjs-cors'; 5 | 6 | import google from 'googlethis'; 7 | 8 | export default async function handler(req, res) { 9 | // const videos = await yt.search('Rafi Ahmad'); 10 | // await NextCors(req, res, { 11 | // // Options 12 | // methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'], 13 | // origin: '*', 14 | // optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204 15 | // }); 16 | 17 | const options = { 18 | page: 0, 19 | safe: true, 20 | additional_params: { 21 | hl: 'id', 22 | }, 23 | }; 24 | 25 | const videos = await google.search(req.query.q, options); 26 | return res.status(200).json(videos); 27 | 28 | // bing.search( 29 | // { 30 | // q: 'Sandika galih', 31 | // enforceLanguage: true, 32 | // }, 33 | // function (err, resp) { 34 | // if (err) { 35 | // console.log(err); 36 | // } else { 37 | // console.log(resp); 38 | // return res.status(200).json(resp); 39 | // } 40 | // } 41 | // ); 42 | } 43 | -------------------------------------------------------------------------------- /pages/api/search2.js: -------------------------------------------------------------------------------- 1 | import google from 'googlethis'; 2 | import * as yt from 'youtube-search-without-api-key'; 3 | 4 | import { search } from 'youtube-search-no-limit'; 5 | 6 | export default async function handler(req, res) { 7 | if (req.method !== 'GET') 8 | return res 9 | .status(405) 10 | .json({ status: 'failed', message: 'Metode tidak valid.' }); 11 | 12 | if (!req.headers['x-csrf-token'] || req.headers['x-csrf-token'].length !== 32) 13 | return res 14 | .status(405) 15 | .json({ status: 'failed', message: 'Metode tidak valid bang.' }); 16 | 17 | if (!req.query.q) 18 | return res 19 | .status(404) 20 | .json({ status: 'failed', message: 'Query tidak boleh kosong.' }); 21 | 22 | const searchQuery = req.query.q; 23 | const page = req.query.page - 1 || 0; 24 | let newVideos; 25 | 26 | const options = { 27 | page: page, 28 | safe: false, 29 | additional_params: { 30 | hl: 'id', 31 | }, 32 | }; 33 | 34 | const response = await google.search(searchQuery, options); 35 | 36 | // if (response.videos) { 37 | // const videos = await search(searchQuery); 38 | // newVideos = videos.map((a) => { 39 | // return { 40 | // id: a.id, 41 | // url: a.url, 42 | // title: a.title, 43 | // thumbnails: a.thumbnails[0], 44 | // duration: a.durationText, 45 | // channel: { name: a.channel.name, url: a.channel.url }, 46 | // }; 47 | // }); 48 | // } 49 | 50 | let knowledge_panel = response.knowledge_panel; 51 | 52 | if ( 53 | response.knowledge_panel.title || 54 | response.knowledge_panel.title !== 'N/A' || 55 | !response.knowledge_panel.images 56 | ) { 57 | let dataImages = await google.image(searchQuery, { safe: false }); 58 | let dummy = []; 59 | dataImages.slice(0, 8).map((a) => { 60 | dummy.push({ url: a.preview.url }); 61 | }); 62 | knowledge_panel.images = dummy; 63 | } 64 | 65 | return res.status(200).json({ 66 | status: 'success', 67 | results: response.results, 68 | panel: knowledge_panel, 69 | people_also_search: response.people_also_search, 70 | related_search: response.people_also_ask, 71 | mungkin: response.did_you_mean, 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /pages/images.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import Link from 'next/dist/client/link'; 3 | import { useEffect, useState } from 'react'; 4 | import { useRouter } from 'next/router'; 5 | import { FiSearch } from 'react-icons/fi'; 6 | import YNTKTS from '../components/YNTKTS'; 7 | import Footer from '../components/Footer'; 8 | import User from '../components/User'; 9 | import HeadMeta from '../components/Head'; 10 | import { event } from '../lib/ga'; 11 | import ImageResults from '../components/ImagesResult'; 12 | import randomString from '../lib/randomString'; 13 | 14 | export function getServerSideProps(ctx) { 15 | const query = ctx.req; 16 | 17 | return { props: { a: 'a' } }; 18 | } 19 | 20 | export default function search(props) { 21 | const router = useRouter(); 22 | 23 | const { q, page } = router.query; 24 | 25 | const [isLoading, setIsLoading] = useState(true); 26 | const [search, setSearch] = useState(''); 27 | const [input, setInput] = useState(q || 'bunga'); 28 | 29 | const getSearch = async () => { 30 | let get = await fetch(`/api/images?q=${q}&page=${page || 0}`, { 31 | method: 'GET', 32 | headers: { 33 | 'X-CSRF-TOKEN': randomString(32).toString(), 34 | }, 35 | }); 36 | let res = await get.json(); 37 | return res; 38 | }; 39 | 40 | useEffect(() => { 41 | if (!q) { 42 | router.push('/'); 43 | } 44 | setIsLoading(true); 45 | getSearch().then((res, err) => { 46 | if (res.status === 'success') { 47 | setSearch(res); 48 | setIsLoading(false); 49 | } else { 50 | setSearch({ reults: '' }); 51 | setIsLoading(false); 52 | } 53 | }); 54 | }, [router]); 55 | 56 | const handleSearch = (e) => { 57 | e.preventDefault(); 58 | event({ 59 | action: 'search3', 60 | params: { 61 | search_term: input, 62 | }, 63 | }); 64 | router.push({ query: { q: input } }); 65 | }; 66 | 67 | return ( 68 | <> 69 | 70 |
71 |
72 |
73 |
74 | 75 | 76 | Gatot Kaca 77 | 78 | 79 |
handleSearch(e)} 81 | className="w-full md:w-2/3 relative" 82 | > 83 | setInput(e.target.value)} 87 | value={input} 88 | /> 89 | 95 |
96 |
97 | 98 |
99 | 110 | {isLoading ? ( 111 | loading 116 | ) : search.results.length !== 0 ? ( 117 | <> 118 | 119 | 120 | ) : ( 121 | 122 | )} 123 |
124 | 125 |
126 |
127 | 128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /pages/index.jsx: -------------------------------------------------------------------------------- 1 | import { FiSearch } from 'react-icons/fi'; 2 | import { useRouter } from 'next/router'; 3 | import { useState } from 'react'; 4 | import HeadMeta from '../components/Head'; 5 | import User from '../components/User'; 6 | import Footer from '../components/Footer'; 7 | import { event } from '../lib/ga'; 8 | 9 | export default function Home() { 10 | const router = useRouter(); 11 | 12 | const [input, setInput] = useState(''); 13 | 14 | const handleSearch = (e) => { 15 | e.preventDefault(); 16 | event({ 17 | action: 'search', 18 | params: { 19 | search_term: input, 20 | }, 21 | }); 22 | 23 | router.push('/search?q=' + input + '&page=1'); 24 | }; 25 | 26 | return ( 27 | <> 28 | 29 |
30 | 45 |
46 |

47 | Gatot Kaca 48 |

49 |
50 |
handleSearch(e)}> 51 |
52 | setInput(e.target.value)} 56 | value={input} 57 | placeholder="Cari apa..." 58 | /> 59 |
60 | 61 |
62 |
63 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /pages/news.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import Link from 'next/link'; 3 | import { useEffect, useState } from 'react'; 4 | import { useRouter } from 'next/router'; 5 | import { FiSearch } from 'react-icons/fi'; 6 | import SearchResult from '../components/SearchResult'; 7 | import Pagination from '../components/Pagination'; 8 | import YNTKTS from '../components/YNTKTS'; 9 | import Footer from '../components/Footer'; 10 | import User from '../components/User'; 11 | import HeadMeta from '../components/Head'; 12 | import { event } from '../lib/ga'; 13 | import randomString from '../lib/randomString'; 14 | import Panelnya from '../components/Panelnya'; 15 | import PeopleAlsoSearch from '../components/PeopleAlsoSearch'; 16 | import RelatedSearch from '../components/RelatedSearch'; 17 | import NewsResults from '../components/News/NewsResults'; 18 | 19 | export function getServerSideProps(ctx) { 20 | const page = ctx.query.page || 1; 21 | const { q } = ctx.query; 22 | return { props: { q, page } }; 23 | } 24 | 25 | export default function news(props) { 26 | const router = useRouter(); 27 | 28 | const { q, page } = router.query; 29 | 30 | const [isLoading, setIsLoading] = useState(true); 31 | const [search, setSearch] = useState(''); 32 | const [input, setInput] = useState(props.q || q); 33 | 34 | const getSearch = async (q, pages) => { 35 | let get = await fetch(`/api/news?q=${q}`, { 36 | method: 'GET', 37 | headers: { 38 | 'X-CSRF-TOKEN': randomString(32).toString(), 39 | }, 40 | }); 41 | if (get.ok) { 42 | let res = await get.json(); 43 | return res; 44 | } else { 45 | return { status: 'failed' }; 46 | } 47 | }; 48 | 49 | useEffect(() => { 50 | if (!q) { 51 | router.push('/'); 52 | } 53 | setIsLoading(true); 54 | getSearch(q, page).then((res) => { 55 | if (res.status === 'success') { 56 | setSearch(res); 57 | setInput(props.q); 58 | setIsLoading(false); 59 | } else { 60 | setSearch({ results: '' }); 61 | setIsLoading(false); 62 | } 63 | }); 64 | }, [router]); 65 | 66 | const handleSearch = (e) => { 67 | e.preventDefault(); 68 | event({ 69 | action: 'search2', 70 | params: { 71 | search_term: input, 72 | }, 73 | }); 74 | router.push({ query: { q: input, page: 1 } }); 75 | }; 76 | 77 | console.log(search); 78 | 79 | return ( 80 | <> 81 | 82 |
83 |
84 |
85 |
86 | 87 | 88 | Gatot Kaca 89 | 90 | 91 |
handleSearch(e)} 93 | className="w-full md:w-2/3 relative" 94 | > 95 | setInput(e.target.value)} 99 | value={input} 100 | /> 101 | 107 |
108 |
109 | 110 |
111 | 122 |
123 | {isLoading ? ( 124 | loading 129 | ) : search.results.length !== 0 ? ( 130 | <> 131 |
132 | 133 |
134 | 135 | ) : ( 136 | 137 | )} 138 |
139 |
140 |
141 |
142 | 143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /pages/search.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import Link from 'next/link'; 3 | import { useEffect, useState } from 'react'; 4 | import { useRouter } from 'next/router'; 5 | import { FiSearch } from 'react-icons/fi'; 6 | import SearchResult from '../components/SearchResult'; 7 | import Pagination from '../components/Pagination'; 8 | import YNTKTS from '../components/YNTKTS'; 9 | import Footer from '../components/Footer'; 10 | import User from '../components/User'; 11 | import HeadMeta from '../components/Head'; 12 | import { event } from '../lib/ga'; 13 | import randomString from '../lib/randomString'; 14 | import Panelnya from '../components/Panelnya'; 15 | import PeopleAlsoSearch from '../components/PeopleAlsoSearch'; 16 | import RelatedSearch from '../components/RelatedSearch'; 17 | 18 | export function getServerSideProps(ctx) { 19 | const page = ctx.query.page || 1; 20 | const { q } = ctx.query; 21 | return { props: { q, page } }; 22 | } 23 | 24 | export default function Search(props) { 25 | const router = useRouter(); 26 | 27 | const { q, page } = router.query; 28 | 29 | const [isLoading, setIsLoading] = useState(true); 30 | const [search, setSearch] = useState(''); 31 | const [input, setInput] = useState(props.q || q); 32 | 33 | const getSearch = async (q, pages) => { 34 | let get = await fetch(`/api/search2?q=${q}&page=${pages || 0}`, { 35 | method: 'GET', 36 | headers: { 37 | 'X-CSRF-TOKEN': randomString(32).toString(), 38 | }, 39 | }); 40 | if (get.ok) { 41 | let res = await get.json(); 42 | return res; 43 | } else { 44 | return { status: 'failed' }; 45 | } 46 | }; 47 | 48 | useEffect(() => { 49 | if (!q) { 50 | router.push('/'); 51 | } 52 | setIsLoading(true); 53 | getSearch(q, page).then((res) => { 54 | if (res.status === 'success') { 55 | setSearch(res); 56 | setInput(props.q); 57 | setIsLoading(false); 58 | } else { 59 | setSearch({ results: '' }); 60 | setIsLoading(false); 61 | } 62 | }); 63 | }, [router]); 64 | 65 | const handleSearch = (e) => { 66 | e.preventDefault(); 67 | event({ 68 | action: 'search2', 69 | params: { 70 | search_term: input, 71 | }, 72 | }); 73 | router.push({ query: { q: input, page: 1 } }); 74 | }; 75 | 76 | return ( 77 | <> 78 | 79 |
80 |
81 |
82 |
83 | 84 | 85 | Gatot Kaca 86 | 87 | 88 |
handleSearch(e)} 90 | className="w-full md:w-2/3 relative" 91 | > 92 | setInput(e.target.value)} 96 | value={input} 97 | /> 98 | 104 |
105 |
106 | 107 |
108 | 119 | {isLoading ? ( 120 | loading 125 | ) : search.results.length !== 0 ? ( 126 | <> 127 | {search.mungkin && ( 128 |

129 | Mungkin anda mencari{' '} 130 | 131 | 132 | {search.mungkin} 133 | 134 | 135 |

136 | )} 137 |
138 |
139 | 143 |
144 | 145 |
146 |
147 | 148 |
149 | {search.panel.title && search.panel.title !== 'N/A' && ( 150 | <> 151 | 152 | 153 |
154 | 155 |
156 | 157 | )} 158 |
159 |
160 | 161 |
162 | 163 |
164 | 165 | ) : ( 166 | 167 | )} 168 |
169 | 170 |
171 |
172 | 173 | ); 174 | } 175 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/Avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagusok/gatotkaca/105a108b0751774b5b24369d5ece2077efbd0478/public/Avatar.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagusok/gatotkaca/105a108b0751774b5b24369d5ece2077efbd0478/public/favicon.ico -------------------------------------------------------------------------------- /public/loading-500.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagusok/gatotkaca/105a108b0751774b5b24369d5ece2077efbd0478/public/loading-500.gif -------------------------------------------------------------------------------- /public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagusok/gatotkaca/105a108b0751774b5b24369d5ece2077efbd0478/public/loading.gif -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /public/yntkts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagusok/gatotkaca/105a108b0751774b5b24369d5ece2077efbd0478/public/yntkts.png -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | 118 | @media (prefers-color-scheme: dark) { 119 | .card, 120 | .footer { 121 | border-color: #222; 122 | } 123 | .code { 124 | background: #111; 125 | } 126 | .logo img { 127 | filter: invert(1); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); 2 | 3 | /* @import 'tailwindcss/base'; 4 | @import 'tailwindcss/components'; 5 | @import 'tailwindcss/utilities'; */ 6 | 7 | @tailwind base; 8 | @tailwind components; 9 | @tailwind utilities; 10 | 11 | body{ 12 | font-family: 'Poppins',sans-serif; 13 | } 14 | 15 | .custom-img { 16 | object-fit: contain; 17 | width: 100% !important; 18 | position: relative !important; 19 | height: unset !important; 20 | } 21 | 22 | .unset-img { 23 | width: 100%; 24 | } 25 | .unset-img > div { 26 | position: unset !important; 27 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx}', 5 | './components/**/*.{js,ts,jsx,tsx}', 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [require('tailwind-scrollbar-hide'), require('tailwind-scrollbar')], 11 | variants: { 12 | scrollbar: ['rounded'], 13 | }, 14 | }; 15 | --------------------------------------------------------------------------------