0
0
mirror of https://github.com/yude-jp/yude.jp synced 2024-12-22 12:10:11 +09:00

Compare commits

...

17 Commits

15 changed files with 182 additions and 25 deletions

3
.env.local.sample Normal file
View File

@ -0,0 +1,3 @@
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPOTIFY_REFRESH_TOKEN=

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ dist
yarn-error.log
.vimrc~
..vimrc.un~
.env.local

View File

@ -7,7 +7,7 @@ Built with [Next.js](https://nextjs.org/) and [Tailwind CSS](https://tailwindcss
## Development
* To setup your repository, please run `yarn`.
* To view this website in your computer, please run `yarn dev`.
* To preview, please run `yarn dev`.
## License
This repository is licensed under the MIT License.

View File

@ -2,5 +2,6 @@
"footer": "This page is licensed under the MIT License.",
"source": "Source code",
"tos": "yude.jp Terms of Service",
"yes_playing": "Playing {{playing}}"
"yes_playing": "Playing {{playing}}",
"listening": "Listening to {{listening}}"
}

View File

@ -2,5 +2,6 @@
"footer": "このページは MIT License の下でライセンスされています。",
"source": "ソースコード",
"tos": "yude.jp サービス利用規約",
"yes_playing": "{{playing}} をプレイ中"
"yes_playing": "{{playing}} をプレイ中",
"listening": "{{listening}} を再生中"
}

View File

@ -4,7 +4,7 @@
"description": "Front page of yude.jp",
"main": "index.js",
"scripts": {
"dev": "next -p 30221",
"dev": "next",
"build": "next build",
"start": "next start",
"export": "next export"
@ -27,17 +27,20 @@
"@fortawesome/react-fontawesome": "^0.1.14",
"@tailwindcss/typography": "^0.4.0",
"autoprefixer": "^10.2.5",
"axios": "^0.21.1",
"next": "^10.2.2",
"next-themes": "^0.0.14",
"next-translate": "^1.0.7",
"popper.js": "^1.16.1",
"postcss": "^8.3.0",
"querystring": "^0.2.1",
"raw-loader": "^4.0.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-markdown": "^6.0.2",
"react-switch": "^6.0.0",
"remark-gfm": "^1.0.0",
"swr": "^0.5.6",
"tailwindcss": "^2.1.2",
"tailwindcss-filters": "^3.0.0",
"tailwindcss-responsive-embed": "^1.0.0",

58
pages/api/Spotify.js Normal file
View File

@ -0,0 +1,58 @@
import querystring from 'querystring';
const {
SPOTIFY_CLIENT_ID: client_id,
SPOTIFY_CLIENT_SECRET: client_secret,
SPOTIFY_REFRESH_TOKEN: refresh_token,
} = process.env;
const basic = Buffer.from(`${client_id}:${client_secret}`).toString('base64');
const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`;
const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`;
const getAccessToken = async () => {
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Basic ${basic}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: querystring.stringify({
grant_type: 'refresh_token',
refresh_token,
}),
});
return response.json();
};
export const getNowPlaying = async () => {
const { access_token } = await getAccessToken();
return fetch(NOW_PLAYING_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
});
};
export default async (_, res) => {
const response = await getNowPlaying();
if (response.status === 204 || response.status > 400) {
return res.status(200).json({ isPlaying: false });
}
const song = await response.json();
const isPlaying = song.is_playing;
const title = song.item.name;
const artist = song.item.artists.map((_artist) => _artist.name).join(', ');
const album = song.item.album.name;
return res.status(200).json({
album,
artist,
isPlaying,
title,
});
};

View File

@ -1,26 +1,44 @@
import React from "react";
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import useTranslation from 'next-translate/useTranslation'
import { useRouter } from 'next/router'
const url = "https://discord.com/api/guilds/723409709306216498/widget.json";
const App = () => {
function App (){
const router = useRouter()
const { locale, locales, defaultLocale, pathname } = router
const { t, lang } = useTranslation("common")
const [playing, setPlaying] = React.useState(0);
React.useEffect(() => {
fetch(url)
.then((r) => r.json())
.then((j) => setPlaying(j.members[0].game.name))
}, []);
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'https://discord.com/api/guilds/723409709306216498/widget.json',
);
const yes_playing = t('yes_playing', {playing: playing})
if (playing){
return <p>{yes_playing}</p>
setData(result.data);
}, []);
if (data === undefined){
console.log("[Discord API] データの取得に失敗しました。 / Failed to retrieve data.")
return <p></p>
}else{
return <p>&#32;</p>
const str = JSON.stringify(data)
if (str.indexOf("game") !== -1){
const yes_playing = t('yes_playing', {playing: data.members[0].game.name})
return <p>{yes_playing}</p>
console.log("[Discord API] Playing: " + data.members[0].game.name)
}else{
return <p></p>
console.log("[Discord API] Nothing playing")
}
}
};
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(url)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default App;

View File

@ -8,7 +8,10 @@ const App = () => {
.then((r) => r.json())
.then((j) => setStatus(j.members[0].status))
}, []);
if (status === undefined){
console.log("[Discord API] オンライン状態を取得できませんでした。 / Failed to retrieve online status.")
return <div></div>
}else{
if (status === "online") {
return <div className="font-bold text-gray-700 rounded-full bg-green-500 flex w-5 h-5 items-center justify-center"></div>
}else{
@ -23,5 +26,6 @@ const App = () => {
}
}
};
}
export default App;

View File

@ -24,7 +24,7 @@ const Layout = (props) => {
</Head>
<main>
<Navbar />
<div className="md:mx-40 mx-6">
<div className="max-w-2xl mx-auto">
{children}
</div>
<Footer />

View File

@ -0,0 +1,33 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import useTranslation from 'next-translate/useTranslation'
import { useRouter } from 'next/router'
function App () {
const router = useRouter()
const { locale, locales, defaultLocale, pathname } = router
const { t, lang } = useTranslation("common")
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'/api/Spotify',
);
setData(result.data);
}, []);
if (data === undefined){
console.log("[Spotify Web API] データの取得に失敗しました。 / Failed to retrieve data.")
return <p></p>
}else{
if (data.isPlaying){
const status = data.artist + ' / ' + data.title
const listening = t('listening', {listening: status})
return <p>{listening}</p>
}else{
return <p></p>
}
};
}
export default App;

View File

@ -34,7 +34,7 @@ export default function Index(props) {
unoptimized = {true}
/>
</div>
<div className="grid grid-cols-5 gap-10 max-w-xl mx-auto">
<div className="grid grid-cols-5 gap-10">
<div className="has-tooltip"><span className="tooltip rounded shadow-lg p-1 bg-yellow-600 transform translate-y-10 -translate-x-10">{profile}</span><Link href="/profile"><a><FontAwesomeIcon icon={faUser} className="w-10 h-10 fill-current inline transition duration-200 ease-in-out transform hover:-translate-y-1 hover:scale-110" /></a></Link></div>
<div className="has-tooltip"><span className="tooltip rounded shadow-lg p-1 bg-yellow-600 transform translate-y-10 -translate-x-9">{blog}</span><Link href="https://blog.yude.jp"><a><FontAwesomeIcon icon={faBlog} className="w-10 h-10 fill-current inline transition duration-200 ease-in-out transform hover:-translate-y-1 hover:scale-110" /></a></Link></div>
<div className="has-tooltip"><span className="tooltip rounded shadow-lg p-1 bg-yellow-600 transform translate-y-10 -translate-x-12">{status}</span><Link href="/status"><a><FontAwesomeIcon icon={faServer} className="w-10 h-10 fill-current inline transition duration-200 ease-in-out transform hover:-translate-y-1 hover:scale-110" /></a></Link></div>

View File

@ -1,13 +1,14 @@
import Layout from "./components/Layout"
import useTranslation from 'next-translate/useTranslation'
import { faDiscord, faTwitter, faGithub, faKeybase, faInstagram, faMastodon, faSteam } from '@fortawesome/free-brands-svg-icons'
import { faEnvelope, faBirthdayCake, faMapPin, faSchool, faPhone, faInfo, faKey, faDownload, faEye } from '@fortawesome/free-solid-svg-icons'
import { faEnvelope, faBirthdayCake, faMapPin, faSchool, faPhone, faInfo, faKey, faDownload, faEye, faUserClock } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import Image from 'next/image'
import DiscordStatus from './components/DiscordStatus'
import { useRouter } from 'next/router'
import DiscordPlaying from './components/DiscordPlaying'
import Spotify from './components/Spotify'
export default function About(props) {
const router = useRouter()
@ -55,6 +56,7 @@ export default function About(props) {
<p className="text-4xl subpixel-antialiased">yude</p>
<DiscordPlaying />
<Spotify />
</div>
</div>
@ -194,7 +196,14 @@ export default function About(props) {
</ul>
</div>
{
// WakaTime
}
<div className="text-left my-6">
<p className="text-2xl"><FontAwesomeIcon icon={faUserClock} className="w-5 h-5 inline"/> WakaTime</p>
<figure className="max-w-2xl"><embed src="https://wakatime.com/share/@yude/6f15075a-b9d5-464a-8b4f-545d31933dfb.svg"></embed></figure>
<figure className="max-w-2xl"><embed src="https://wakatime.com/share/@yude/a8c52934-488b-4bdd-aed1-4f3fc73eb78e.svg"></embed></figure>
</div>
<div>
</div>
</div>

View File

@ -13,7 +13,9 @@
@apply list-disc mx-10
}
}
a {
@apply hover:underline
}
.tooltip {
@apply invisible absolute;
}

View File

@ -549,6 +549,13 @@ available-typed-arrays@^1.0.2:
dependencies:
array.prototype.filter "^1.0.0"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
babel-plugin-inline-react-svg@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-2.0.1.tgz#68c9c119d643a8f2d7bf939b942534d89ae3ade9"
@ -1081,6 +1088,11 @@ depd@~1.1.2:
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
dequal@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
des.js@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
@ -1372,6 +1384,11 @@ find-up@^4.0.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
@ -2716,7 +2733,7 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystring@^0.2.0:
querystring@^0.2.0, querystring@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
@ -3208,6 +3225,13 @@ svgo@^2.0.3:
csso "^4.2.0"
stable "^0.1.8"
swr@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/swr/-/swr-0.5.6.tgz#70bfe9bc9d7ac49a064be4a0f4acf57982e55a31"
integrity sha512-Bmx3L4geMZjYT5S2Z6EE6/5Cx6v1Ka0LhqZKq8d6WL2eu9y6gHWz3dUzfIK/ymZVHVfwT/EweFXiYGgfifei3w==
dependencies:
dequal "2.0.2"
tailwindcss-aspect-ratio@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/tailwindcss-aspect-ratio/-/tailwindcss-aspect-ratio-3.0.0.tgz#4f9fc7ca0f3468373290faaa8b92652157bee6a4"