├── .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 | 
39 |
40 | 
41 |
42 | 
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/components/FilmRating.jsx:
--------------------------------------------------------------------------------
1 | export default function FilmRating({ data }) {
2 | return (
3 |
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 |
37 |
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 |
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 |
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 |
26 |
27 | {page > 1 && (
28 |
29 | handlePagination(parseInt(pages) - 1)}
31 | className="first:ml-0 text-xs font-semibold flex w-8 h-8 mx-1 p-0 rounded-full items-center justify-center leading-tight relative border border-solid border-red-400 text-red-400 hover:text-white hover:bg-red-400"
32 | >
33 |
34 |
35 |
36 | )}
37 |
38 |
39 |
40 | {!page ? 1 : pages}
41 |
42 |
43 |
44 |
45 | handlePagination(parseInt(pages) + 1)}
47 | className="first:ml-0 text-xs font-semibold flex w-8 h-8 mx-1 p-0 rounded-full items-center justify-center leading-tight relative border border-solid border-red-400 text-red-400 hover:text-white hover:bg-red-400"
48 | >
49 |
50 |
51 |
52 |
53 |
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 |
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 | setSeeMore((prev) => !prev)}
72 | >
73 | See More
74 |
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 |
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 |
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 |
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 |
23 |
24 |
25 |
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 |
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 |
99 |
110 | {isLoading ? (
111 |
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 |
31 |
44 |
45 |
46 |
47 | Gatot Kaca
48 |
49 |
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 |
123 | {isLoading ? (
124 |
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 |
108 |
119 | {isLoading ? (
120 |
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 |
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 |
--------------------------------------------------------------------------------