diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..0465677e
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,28 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
+{
+ "name": "Node.js & TypeScript",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
+ "features": {
+ "ghcr.io/devcontainers/features/node:1": {
+ "version": "latest",
+ "nvmVersion": "latest"
+ }
+ }
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "yarn install",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/.env.sample b/.env.sample
index a51af8a1..db8dd629 100644
--- a/.env.sample
+++ b/.env.sample
@@ -1,5 +1,3 @@
-VITE_ADSENSE_PUB_ID =
-VITE_GOOGLE_ANALYTICS_ID =
-VITE_GOOGLE_SEARCH_CONSOLE_VERIFICATION =
-VITE_PXIMG_BASEURL_I = /-/
-VITE_PXIMG_BASEURL_S = /~/
+NUXT_ADSENSE_PUB_ID =
+NUXT_GOOGLE_ANALYTICS_ID =
+NUXT_GOOGLE_SEARCH_CONSOLE_VERIFICATION =
diff --git a/.npmrc b/.npmrc
index 9a0c9516..0ba08b34 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
+shamefully-hoist = true
registry = https://registry.npmmirror.com
diff --git a/.vscode/vue.code-snippets b/.vscode/vue.code-snippets
deleted file mode 100644
index abd10498..00000000
--- a/.vscode/vue.code-snippets
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "Init vue components": {
- "scope": "vue",
- "prefix": "vue",
- "body": [
- "",
- "$0",
- "",
- "",
- "",
- "",
- ""
- ],
- "description": "Init vue components"
- }
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 1f0dcb8c..1ee1775e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
-![PixivNow Logo](src/assets/LogoH.png)
+![PixivNow Logo](public/images/LogoH.png)
Pixiv Service Proxy
diff --git a/api/http.ts b/api/http.ts
deleted file mode 100644
index f74dd74f..00000000
--- a/api/http.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { VercelRequest, VercelResponse } from '@vercel/node'
-import escapeRegExp from 'lodash.escaperegexp'
-import { ajax } from './utils.js'
-
-export default async function (req: VercelRequest, res: VercelResponse) {
- if (!isAccepted(req)) {
- return res.status(403).send('403')
- }
-
- try {
- const { __PREFIX, __PATH } = req.query
- const { data } = await ajax({
- method: req.method ?? 'GET',
- url: `/${encodeURI(`${__PREFIX}${__PATH ? '/' + __PATH : ''}`)}`,
- params: req.query ?? {},
- data: req.body || undefined,
- headers: req.headers as Record
,
- })
- res.status(200).send(data)
- } catch (e: any) {
- res.status(e?.response?.status || 500).send(e?.response?.data || e)
- }
-}
-
-function isAccepted(req: VercelRequest) {
- const { UA_BLACKLIST = '[]' } = process.env
- try {
- const list: string[] = JSON.parse(UA_BLACKLIST)
- const ua = req.headers['user-agent'] ?? ''
- return (
- !!ua &&
- Array.isArray(list) &&
- (list.length > 0
- ? !new RegExp(
- `(${list.map((str) => escapeRegExp(str)).join('|')})`,
- 'gi'
- ).test(ua)
- : true)
- )
- } catch (e) {
- return false
- }
-}
diff --git a/api/image.ts b/api/image.ts
deleted file mode 100644
index 8c87b104..00000000
--- a/api/image.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { VercelRequest, VercelResponse } from '@vercel/node'
-import axios from 'axios'
-import { USER_AGENT } from './utils.js'
-
-export default async (req: VercelRequest, res: VercelResponse) => {
- const { __PREFIX, __PATH } = req.query
- if (!__PREFIX || !__PATH) {
- return res.status(400).send({ message: 'Missing param(s)' })
- }
-
- switch (__PREFIX) {
- case '~': {
- return axios
- .get(`https://s.pximg.net/${__PATH}`, {
- responseType: 'arraybuffer',
- headers: {
- referer: 'https://www.pixiv.net/',
- 'user-agent': USER_AGENT,
- },
- })
- .then(
- ({ data, headers }) => {
- res.setHeader('Content-Type', headers['content-type'])
- res.setHeader(
- 'Cache-Control',
- `public, max-age=${12 * 60 * 60 * 3600}`
- )
- res.status(200).send(Buffer.from(data))
- },
- (err) => {
- return res
- .status(err?.response?.status || 500)
- .send(err?.response?.data || err)
- }
- )
- }
- default:
- return res.status(400).send({ message: 'Invalid request' })
- }
-}
diff --git a/api/random.ts b/api/random.ts
deleted file mode 100644
index 51bdeed7..00000000
--- a/api/random.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { VercelRequest, VercelResponse } from '@vercel/node'
-import { formatInTimeZone } from 'date-fns-tz'
-import { PXIMG_BASEURL_I, ajax } from './utils.js'
-import { Artwork } from '../src/types/Artworks.js'
-
-type ArtworkOrAd = Artwork | { isAdContainer: boolean }
-
-export default async (req: VercelRequest, res: VercelResponse) => {
- const requestImage =
- (req.headers.accept?.includes('image') || req.query.format === 'image') &&
- req.query.format !== 'json'
- try {
- const data: { illusts?: ArtworkOrAd[] } = (
- await ajax({
- url: '/ajax/illust/discovery',
- params: {
- mode: req.query.mode ?? 'safe',
- max: requestImage ? '1' : req.query.max ?? '18',
- },
- headers: req.headers,
- })
- ).data
- const illusts = (data.illusts ?? []).filter((value): value is Artwork =>
- Object.keys(value).includes('id')
- )
- illusts.forEach((value) => {
- const middle = `img/${formatInTimeZone(
- value.updateDate,
- 'Asia/Tokyo',
- 'yyyy/MM/dd/HH/mm/ss'
- )}/${value.id}`
- value.urls = {
- mini: `${PXIMG_BASEURL_I}c/48x48/img-master/${middle}_p0_square1200.jpg`,
- thumb: `${PXIMG_BASEURL_I}c/250x250_80_a2/img-master/${middle}_p0_square1200.jpg`,
- small: `${PXIMG_BASEURL_I}c/540x540_70/img-master/${middle}_p0_master1200.jpg`,
- regular: `${PXIMG_BASEURL_I}img-master/${middle}_p0_master1200.jpg`,
- original: `${PXIMG_BASEURL_I}img-original/${middle}_p0.jpg`,
- }
- })
- if (requestImage) {
- res.redirect(illusts[0].urls.regular)
- return
- } else {
- res.send(illusts)
- return
- }
- } catch (e: any) {
- res.status(e?.response?.status ?? 500).send(e?.response?.data ?? e)
- }
-}
diff --git a/api/user.ts b/api/user.ts
deleted file mode 100644
index 3ab88501..00000000
--- a/api/user.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { VercelRequest, VercelResponse } from '@vercel/node'
-import { load } from 'cheerio'
-import { ajax, replacePximgUrlsInObject } from './utils.js'
-
-export default async (req: VercelRequest, res: VercelResponse) => {
- const token = req.cookies.PHPSESSID || req.query.token
- if (!token) {
- return res.status(403).send({ message: '未配置用户密钥' })
- }
-
- ajax
- .get('/', { params: req.query, headers: req.headers })
- .then(async ({ data }) => {
- const $ = load(data)
- const $meta = $('meta[name="global-data"]')
- if ($meta.length < 0 || !$meta.attr('content')) {
- return res.status(403).send({ message: '无效的用户密钥' })
- }
-
- let meta
- try {
- meta = JSON.parse($meta.attr('content') as string)
- } catch (error) {
- res.status(403).send({
- message: '意料外的元数据',
- cheerio: {
- length: $meta.length,
- html: $meta.prop('outerHTML'),
- },
- error,
- })
-
- return
- }
-
- if (!meta.userData) {
- res.status(403).send({
- message: '无法获取登录状态',
- meta,
- })
- return
- }
-
- res.setHeader('cache-control', 'no-cache')
- res.setHeader(
- 'set-cookie',
- `CSRFTOKEN=${meta.token}; path=/; secure; sameSite=Lax`
- )
- res.send(replacePximgUrlsInObject(meta))
- })
- .catch((err) => {
- return res
- .status(err?.response?.status || 500)
- .send(err?.response?.data || err)
- })
-}
diff --git a/api/utils.ts b/api/utils.ts
deleted file mode 100644
index b26b3ea1..00000000
--- a/api/utils.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import { VercelRequest, VercelResponse } from '@vercel/node'
-import axios from 'axios'
-import colors from 'picocolors'
-
-// HTTP handler
-export default async function (req: VercelRequest, res: VercelResponse) {
- res.status(404).send({
- error: true,
- message: 'Not Found',
- body: null,
- })
-}
-
-export const PROD = process.env.NODE_ENV === 'production'
-export const DEV = !PROD
-export const USER_AGENT =
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'
-export const PXIMG_BASEURL_I = (() => {
- const i = process.env.VITE_PXIMG_BASEURL_I
- return i ? i.replace(/\/$/, '') + '/' : 'https://i.pximg.net/'
-})()
-export const PXIMG_BASEURL_S = (() => {
- const s = process.env.VITE_PXIMG_BASEURL_S
- return s ? s.replace(/\/$/, '') + '/' : 'https://s.pximg.net/'
-})()
-
-export class CookieUtils {
- static toJSON(raw: string) {
- return Object.fromEntries(new URLSearchParams(raw.replace(/;\s*/g, '&')))
- }
- static toString(obj: any) {
- return Object.keys(obj)
- .map((i) => `${i}=${obj[i]}`)
- .join(';')
- }
-}
-
-export const ajax = axios.create({
- baseURL: 'https://www.pixiv.net/',
- params: {},
- headers: {
- 'user-agent': USER_AGENT,
- },
- timeout: 9 * 1000,
-})
-ajax.interceptors.request.use((ctx) => {
- // 去除内部参数
- ctx.params = ctx.params || {}
- delete ctx.params.__PATH
- delete ctx.params.__PREFIX
-
- const cookies = CookieUtils.toJSON(ctx.headers.cookie || '')
- const csrfToken = ctx.headers['x-csrf-token'] ?? cookies.CSRFTOKEN ?? ''
- // 强制覆写部分 headers
- ctx.headers = ctx.headers || {}
- ctx.headers.host = 'www.pixiv.net'
- ctx.headers.origin = 'https://www.pixiv.net'
- ctx.headers.referer = 'https://www.pixiv.net/'
- ctx.headers['user-agent'] = USER_AGENT
- ctx.headers['accept-language'] ??=
- 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'
- csrfToken && (ctx.headers['x-csrf-token'] = csrfToken)
-
- if (DEV) {
- console.info(
- colors.green(`[${ctx.method?.toUpperCase()}] <`),
- colors.cyan(ctx.url || '')
- )
- console.info({
- params: ctx.params,
- data: ctx.data,
- cookies,
- })
- }
-
- return ctx
-})
-ajax.interceptors.response.use((ctx) => {
- typeof ctx.data === 'object' &&
- (ctx.data = replacePximgUrlsInObject(ctx.data?.body ?? ctx.data))
- if (DEV) {
- const out: string =
- typeof ctx.data === 'object'
- ? JSON.stringify(ctx.data, null, 2)
- : ctx.data.toString().trim()
- console.info(
- colors.green('[SEND] >'),
- colors.cyan(ctx.request?.path?.replace('https://www.pixiv.net', '')),
- `\n${colors.yellow(typeof ctx.data)} ${
- out.length >= 200 ? out.slice(0, 200).trim() + '\n...' : out
- }`
- )
- }
- return ctx
-})
-
-export function replacePximgUrlsInString(str: string): string {
- if (!str.includes('pximg.net')) return str
- return str
- .replaceAll('https://i.pximg.net/', PXIMG_BASEURL_I)
- .replaceAll('https://s.pximg.net/', PXIMG_BASEURL_S)
-}
-
-export function replacePximgUrlsInObject(
- obj: Record | string
-): Record | string {
- if (typeof obj === 'string') return replacePximgUrlsInString(obj)
-
- return deepReplaceString(obj, replacePximgUrlsInString)
-}
-
-function isObject(value: any): value is Record {
- return typeof value === 'object' && value !== null
-}
-
-export function deepReplaceString(
- obj: T,
- replacer: (value: string) => string
-): T {
- if (Array.isArray(obj)) {
- return obj.map((value) =>
- deepReplaceString(value, replacer)
- ) as unknown as T
- } else if (isObject(obj)) {
- if (
- ['arraybuffer', 'blob', 'formdata'].includes(
- obj.constructor.name.toLowerCase()
- )
- ) {
- return obj
- }
- const result: Record = {}
- for (const [key, value] of Object.entries(obj)) {
- result[key] = deepReplaceString(value, replacer)
- }
- return result as T
- } else if (typeof obj === 'string') {
- return replacer(obj) as unknown as T
- }
- return obj
-}
-
-export function safelyStringify(value: any, space?: number) {
- const visited = new WeakSet()
-
- const replacer = (key: string, val: any) => {
- // 处理 BigInt
- if (typeof val === 'bigint') {
- return val.toString()
- }
-
- // 处理 Set
- if (val instanceof Set) {
- return Array.from(val)
- }
-
- // 处理 Map
- if (val instanceof Map) {
- return Array.from(val.entries())
- }
-
- // 处理 function
- if (typeof val === 'function') {
- return val.toString()
- }
-
- // 处理自循环引用
- if (typeof val === 'object' && val !== null) {
- if (visited.has(val)) {
- return ''
- }
- visited.add(val)
- }
-
- return val
- }
-
- return JSON.stringify(value, replacer, space)
-}
-
-JSON.safelyStringify = safelyStringify
-declare global {
- interface JSON {
- safelyStringify: typeof safelyStringify
- }
-}
diff --git a/app/app.config.ts b/app/app.config.ts
new file mode 100644
index 00000000..2d8fbc69
--- /dev/null
+++ b/app/app.config.ts
@@ -0,0 +1,15 @@
+import { version } from '../package.json'
+
+export default defineAppConfig({
+ version,
+ githubOwner: 'FreeNowOrg',
+ githubRepo: 'PixivNow',
+ githubUrl: 'https://github.com/FreeNowOrg/PixivNow',
+ projectName: 'PixivNow',
+ projectTagline: 'Enjoy Pixiv Now (pixiv.js.org)',
+ imageCacheSeconds: 12 * 60 * 60 * 1000,
+ siteEnv:
+ process.env.NODE_ENV === 'development' || version.includes('-')
+ ? 'development'
+ : 'production',
+})
diff --git a/app/app.vue b/app/app.vue
new file mode 100644
index 00000000..d5e48983
--- /dev/null
+++ b/app/app.vue
@@ -0,0 +1,65 @@
+
+#app-full-container(
+ :data-env='config.public.mode',
+ :data-locale='routeLocale',
+ :data-route='routeName'
+)
+ NuxtLayout
+ NaiveuiProvider
+ LazySiteHeader
+ LazySideNavBody
+ main: article: NuxtPage(tag='main')
+ LazySiteFooter
+ NProgress
+
+
+
+
+
diff --git a/src/styles/animate.sass b/app/assets/styles/animate.sass
similarity index 100%
rename from src/styles/animate.sass
rename to app/assets/styles/animate.sass
diff --git a/src/styles/elements.sass b/app/assets/styles/elements.sass
similarity index 100%
rename from src/styles/elements.sass
rename to app/assets/styles/elements.sass
diff --git a/src/styles/formats.sass b/app/assets/styles/formats.sass
similarity index 100%
rename from src/styles/formats.sass
rename to app/assets/styles/formats.sass
diff --git a/src/styles/index.sass b/app/assets/styles/index.sass
similarity index 97%
rename from src/styles/index.sass
rename to app/assets/styles/index.sass
index 0a9f735b..0aaefd81 100644
--- a/src/styles/index.sass
+++ b/app/assets/styles/index.sass
@@ -12,7 +12,7 @@ body
*
box-sizing: border-box
-#app
+#app-full-container
font-family: Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
diff --git a/src/styles/variables.sass b/app/assets/styles/variables.sass
similarity index 100%
rename from src/styles/variables.sass
rename to app/assets/styles/variables.sass
diff --git a/src/components/ArtTag.vue b/app/components/ArtTag.vue
similarity index 71%
rename from src/components/ArtTag.vue
rename to app/components/ArtTag.vue
index 3747fc42..8850683c 100644
--- a/src/components/ArtTag.vue
+++ b/app/components/ArtTag.vue
@@ -1,6 +1,6 @@
NTag.artwork-tag(
- @click='$router.push({ name: "search", params: { keyword: tag, p: 1 } })'
+ @click='router.push({ name: "search", params: { keyword: tag, p: 1 } })'
type='info'
) {{ '#' }}{{ tag }}
@@ -8,6 +8,8 @@ NTag.artwork-tag(
diff --git a/src/components/ArtworksList/ArtworksByUser.vue b/app/components/Artwork/ArtworksByUser.vue
similarity index 78%
rename from src/components/ArtworksList/ArtworksByUser.vue
rename to app/components/Artwork/ArtworksByUser.vue
index 5652b106..47b1fd07 100644
--- a/src/components/ArtworksList/ArtworksByUser.vue
+++ b/app/components/Artwork/ArtworksByUser.vue
@@ -19,21 +19,16 @@
diff --git a/src/components/AuthorCard.vue b/app/components/AuthorCard.vue
similarity index 71%
rename from src/components/AuthorCard.vue
rename to app/components/AuthorCard.vue
index 59c05392..979454a3 100644
--- a/src/components/AuthorCard.vue
+++ b/app/components/AuthorCard.vue
@@ -3,12 +3,12 @@
.author-inner(v-if='user')
.flex-center
.left
- RouterLink(:to='"/users/" + user.userId')
+ NuxtLink(:to='"/users/" + user.userId')
img(:src='user.imageBig' alt='')
.right
.flex
h4.plain
- RouterLink(:to='"/users/" + user.userId') {{ user.name }}
+ NuxtLink(:to='"/users/" + user.userId') {{ user.name }}
NButton(
:loading='loadingUserFollow',
:type='user.isFollowed ? "success" : undefined'
@@ -16,11 +16,11 @@
round
secondary
size='small'
- v-if='user.userId !== userStore.userId'
+ v-if='user.userId !== userStore.id'
)
template(#icon)
- IFasCheck(v-if='user.isFollowed')
- IFasPlus(v-else)
+ ICheck(v-if='user.isFollowed')
+ IPlus(v-else)
| {{ user.isFollowed ? '已关注' : '关注' }}
NEllipsis.description.pre(:line-clamp='3', :tooltip='false') {{ user.comment }}
ArtworkList.tiny(:list='user.illusts' inline)
@@ -35,24 +35,19 @@
diff --git a/src/components/NProgress.vue b/app/components/NProgress.vue
similarity index 92%
rename from src/components/NProgress.vue
rename to app/components/NProgress.vue
index 5a9f6bec..9eaab7ab 100644
--- a/src/components/NProgress.vue
+++ b/app/components/NProgress.vue
@@ -1,4 +1,6 @@
-
+
+#nprogress
+
diff --git a/src/components/SideNav/SideNav.vue b/app/components/SideNav/Body.vue
similarity index 60%
rename from src/components/SideNav/SideNav.vue
rename to app/components/SideNav/Body.vue
index 9247a69c..8cfdf48c 100644
--- a/src/components/SideNav/SideNav.vue
+++ b/app/components/SideNav/Body.vue
@@ -10,54 +10,44 @@ aside.global-side-nav(:class='{ hidden: !sideNavStore.isOpened }')
.group
.title 导航
ul
- ListLink(link='/' text='首页')
- IFasHome.link-icon
- ListLink.not-allowed(link='' text='探索发现')
- IFasImage.link-icon
- ListLink(link='/ranking' text='排行榜')
- IFasCrown.link-icon
+ SideNavListLink(link='/' text='首页')
+ IHome.svg--SideNavListLink
+ SideNavListLink.not-allowed(link='' text='插画')
+ IImage.svg--SideNavListLink
+ SideNavListLink(link='' text='用户')
+ IUser.svg--SideNavListLink
+ SideNavListLink(link='/ranking' text='排行榜')
+ ICrown.svg--SideNavListLink
.group
.title 用户
ul
- ListLink(
+ SideNavListLink(
:text='userStore.isLoggedIn ? "查看令牌" : "设置令牌"'
link='/login'
)
- IFasFingerprint.link-icon
- ListLink(
- :link='userStore.isLoggedIn ? `/users/${userStore.userId}` : `/login?back=${$route.fullPath}`'
- text='我的页面'
- )
- IFasUser.link-icon
- ListLink(
- :link='userStore.isLoggedIn ? `/users/${userStore.userId}/following` : `/login?back=${$route.fullPath}`'
- text='我的关注'
- )
- IFasUser.link-icon
- ListLink(link='/following/latest' text='关注用户的作品')
- IFasUser.link-icon
+ IFingerprint.svg--SideNavListLink
.group
.title PixivNow
ul
- ListLink(externalLink='https://www.pixiv.net/' text='Pixiv.net')
- IFasExternalLinkAlt.link-icon
- ListLink(link='/about' text='关于我们')
- IFasHeart.link-icon
+ SideNavListLink(
+ externalLink='https://www.pixiv.net/'
+ text='Pixiv.net'
+ )
+ IExternalLinkAlt.svg--SideNavListLink
+ SideNavListLink(link='/about' text='关于我们')
+ IHeart.svg--SideNavListLink
diff --git a/src/assets/logo.png b/src/assets/logo.png
deleted file mode 100644
index f3d2503f..00000000
Binary files a/src/assets/logo.png and /dev/null differ
diff --git a/src/components/LazyLoad.vue b/src/components/LazyLoad.vue
deleted file mode 100644
index 772fe87a..00000000
--- a/src/components/LazyLoad.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-
-Component(
- :class='{ lazyload: true, isLoading: !loaded && !error, isLoaded: loaded, isError: error }',
- :height='height',
- :is='loaded ? "img" : "svg"',
- :key='src',
- :src='src',
- :width='width'
- ref='imgRef'
- role='img'
-)
-
-
-
-
-
diff --git a/src/components/userData.ts b/src/components/userData.ts
deleted file mode 100644
index 840c1337..00000000
--- a/src/components/userData.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { PixivUser } from '@/types'
-import Cookies from 'js-cookie'
-
-export function existsSessionId(): boolean {
- const sessionId = Cookies.get('PHPSESSID')
- if (sessionId) {
- return true
- } else {
- Cookies.remove('CSRFTOKEN')
- return false
- }
-}
-
-export async function initUser(): Promise {
- try {
- const { data } = await axios.get<{ userData: PixivUser; token: string }>(
- `/api/user`,
- {
- headers: {
- 'Cache-Control': 'no-store',
- },
- }
- )
- if (data.token) {
- console.log('session ID认证成功', data)
- Cookies.set('CSRFTOKEN', data.token, { secure: true, sameSite: 'Strict' })
- const res = data.userData
- return res
- } else {
- Cookies.remove('CSRFTOKEN')
- return Promise.reject('无效的session ID')
- }
- } catch (err) {
- Cookies.remove('CSRFTOKEN')
- return Promise.reject(err)
- }
-}
-
-export function login(token: string): Promise {
- if (!validateSessionId(token)) {
- console.error('访问令牌格式错误')
- return Promise.reject('访问令牌格式错误')
- }
- Cookies.set('PHPSESSID', token, {
- expires: 180,
- path: '/',
- secure: true,
- sameSite: 'Strict',
- })
- return initUser()
-}
-
-export function logout(): void {
- const token = Cookies.get('PHPSESSID')
- if (token && confirm(`您要移除您的令牌吗?\n${token}`)) {
- Cookies.remove('PHPSESSID')
- Cookies.remove('CSRFTOKEN')
- }
-}
-
-export function validateSessionId(token: string): boolean {
- return /^\d{2,10}_[0-9A-Za-z]{32}$/.test(token)
-}
-
-export function exampleSessionId(): string {
- const uid = new Uint32Array(1)
- window.crypto.getRandomValues(uid)
- const secret = (() => {
- const strSet =
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
- const final = []
- const indexes = new Uint8Array(32)
- window.crypto.getRandomValues(indexes)
- for (const i of indexes) {
- const charIndex = Math.floor((i * strSet.length) / 256)
- final.push(strSet[charIndex])
- }
- return final.join('')
- })()
- return `${uid[0]}_${secret}`
-}
diff --git a/src/composables/states.ts b/src/composables/states.ts
deleted file mode 100644
index 74e9bb46..00000000
--- a/src/composables/states.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { defineStore } from 'pinia'
-import { PixivUser } from '@/types'
-
-export const useSideNavStore = defineStore('sidenav', () => {
- const openState = ref(false)
- const isOpened = computed(() => openState.value)
- function toggle() {
- openState.value = !openState.value
- }
- function open() {
- openState.value = true
- }
- function close() {
- openState.value = false
- }
- return { openState, isOpened, toggle, open, close }
-})
-
-export const useUserStore = defineStore('user', () => {
- const user = ref(null)
- const isLoggedIn = computed(() => !!user.value)
- const userId = computed(() => user.value?.id)
- const userName = computed(() => user.value?.name)
- const userPixivId = computed(() => user.value?.pixivId)
- const userProfileImg = computed(() => user.value?.profileImg)
- const userProfileImgBig = computed(() => user.value?.profileImgBig)
- function login(data: PixivUser) {
- user.value = data
- }
- function logout() {
- user.value = null
- }
- return {
- user,
- isLoggedIn,
- userId,
- userName,
- userPixivId,
- userProfileImg,
- userProfileImgBig,
- login,
- logout,
- }
-})
diff --git a/src/config.ts b/src/config.ts
deleted file mode 100644
index 4a7a186d..00000000
--- a/src/config.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-// Env
-import { version } from '../package.json'
-export { version }
-
-export const SITE_ENV =
- import.meta.env.MODE === 'development' ||
- version.includes('-') ||
- location.hostname === 'pixiv-next.vercel.app'
- ? 'development'
- : 'production'
-
-// Copyright links
-// Do not modify please
-export const GITHUB_OWNER = 'FreeNowOrg'
-export const GITHUB_REPO = 'PixivNow'
-export const GITHUB_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}`
-
-// Site name
-export const PROJECT_NAME = 'PixivNow'
-export const PROJECT_TAGLINE = 'Enjoy Pixiv Now (pixiv.js.org)'
-
-// Image proxy cache seconds
-export const IMAGE_CACHE_SECONDS = 12 * 60 * 60 * 1000
diff --git a/src/env.d.ts b/src/env.d.ts
deleted file mode 100644
index f86d5a0a..00000000
--- a/src/env.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-///
-
-// declare module '*.vue' {
-// import { ComponentOptions } from 'vue'
-// const componentOptions: ComponentOptions
-// export default componentOptions
-// }
diff --git a/src/main.ts b/src/main.ts
deleted file mode 100644
index 3cc4358c..00000000
--- a/src/main.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { createApp } from 'vue'
-import { SITE_ENV } from '@/config'
-import { registerPlugins } from '@/plugins'
-import App from './App.vue'
-import '@/styles/index.sass'
-
-// Create App
-const app = createApp(App)
-
-registerPlugins(app)
-
-// Mount
-app.mount('#app')
-document.body?.setAttribute('data-env', SITE_ENV)
diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts
deleted file mode 100644
index 55e63af6..00000000
--- a/src/plugins/i18n.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { createI18n, I18n } from 'vue-i18n'
-
-export const SUPPORTED_LOCALES = ['zh-Hans']
-
-export function setupI18n(options = { locale: 'zh-Hans' }) {
- const i18n = createI18n({ ...options, legacy: false })
- setI18nLanguage(i18n, options.locale)
- return i18n
-}
-
-export function setI18nLanguage(
- i18n: I18n,
- locale: string
-) {
- i18n.global.locale.value = locale
- document.querySelector('html')?.setAttribute('lang', locale)
-}
-
-export async function loadLocaleMessages(
- i18n: I18n,
- locale: string
-) {
- const messages = await import(
- /* webpackChunkName: "locale-[request]" */ `@/locales/${locale}.json`
- )
- i18n.global.setLocaleMessage(locale, messages.default)
- setI18nLanguage(i18n, locale)
-
- return nextTick()
-}
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
deleted file mode 100644
index 8e451be0..00000000
--- a/src/plugins/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { router } from './router'
-import { loadLocaleMessages, setupI18n } from './i18n'
-import { createPinia } from 'pinia'
-import VueGtag from 'vue-gtag'
-import type { App } from 'vue'
-
-export async function registerPlugins(app: App) {
- const i18n = setupI18n()
- const initialLocale = 'zh-Hans'
- app.use(i18n)
- app.use(router)
- app.use(createPinia())
-
- if (import.meta.env.VITE_GOOGLE_ANALYTICS_ID) {
- app.use(
- VueGtag,
- {
- config: { id: import.meta.env.VITE_GOOGLE_ANALYTICS_ID as string },
- },
- router
- )
- }
-
- await loadLocaleMessages(i18n, initialLocale)
-}
diff --git a/src/plugins/router.ts b/src/plugins/router.ts
deleted file mode 100644
index 965979dc..00000000
--- a/src/plugins/router.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
-import { createDiscreteApi } from 'naive-ui'
-const { message } = createDiscreteApi(['message'])
-
-const routes: RouteRecordRaw[] = [
- {
- path: '/',
- name: 'home',
- component: () => import('@/view/index.vue'),
- },
- {
- path: '/artworks/:id',
- alias: ['/illust/:id', '/i/:id'],
- name: 'artworks',
- component: () => import('@/view/artworks.vue'),
- },
- {
- path: '/following/latest',
- alias: ['/bookmark_new_illust'],
- name: 'following-latest',
- component: () => import('@/view/following-latest.vue'),
- },
- {
- path: '/users/:id',
- name: 'users',
- alias: ['/u/:id'],
- component: () => import('@/view/users.vue'),
- },
- {
- path: '/users/:id/following',
- name: 'following',
- component: () => import('@/view/following.vue'),
- },
- {
- path: '/search/:keyword',
- name: 'search-index-redirect',
- redirect: (to) => `/search/${to.params.keyword}/1`,
- },
- {
- path: '/search/:keyword/:p',
- name: 'search',
- component: () => import('@/view/search.vue'),
- },
- {
- path: '/ranking',
- name: 'ranking',
- component: () => import('@/view/ranking.vue'),
- },
- {
- path: '/login',
- name: 'user-login',
- component: () => import('@/view/login.vue'),
- },
- {
- path: '/about',
- name: 'about-us',
- component: () => import('@/view/about.vue'),
- },
- {
- path: '/notifications/2024-04-26',
- name: 'notification-2024-04-26',
- component: () => import('@/view/notifications/2024-04-26.vue'),
- },
- {
- path: '/:pathMatch(.*)*',
- name: 'not-found',
- component: () => import('@/view/404.vue'),
- },
-]
-
-export const router = createRouter({
- history: createWebHistory(),
- routes,
- scrollBehavior(to, from, savedPosition) {
- if (savedPosition) {
- return savedPosition
- } else {
- return {
- top: 0,
- behavior: 'smooth',
- }
- }
- },
-})
-
-router.afterEach(({ name }) => {
- document.body.setAttribute('data-route', name as string)
- // Fix route when modal opened
- document.body.style.overflow = 'visible'
-})
-
-router.onError((error, to, from) => {
- console.log(error, to, from)
- message.error(error)
-})
-
-export default router
diff --git a/src/utils/ajax.ts b/src/utils/ajax.ts
deleted file mode 100644
index 12ba3acf..00000000
--- a/src/utils/ajax.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { AxiosRequestConfig } from 'axios'
-import nprogress from 'nprogress'
-
-export const ajax = axios.create({
- timeout: 15 * 1000,
- headers: {
- 'Content-Type': 'application/json',
- },
-})
-ajax.interceptors.request.use((config) => {
- nprogress.start()
- return config
-})
-ajax.interceptors.response.use(
- (res) => {
- nprogress.done()
- return res
- },
- (err) => {
- nprogress.done()
- return Promise.reject(err)
- }
-)
-
-export const ajaxPostWithFormData = (
- url: string,
- data:
- | string
- | string[][]
- | Record
- | URLSearchParams
- | undefined,
- config?: AxiosRequestConfig
-) =>
- ajax.post(url, new URLSearchParams(data).toString(), {
- ...config,
- headers: {
- ...config?.headers,
- 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
- },
- })
diff --git a/src/utils/artworkActions.ts b/src/utils/artworkActions.ts
deleted file mode 100644
index f0bdcf11..00000000
--- a/src/utils/artworkActions.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { ajax, ajaxPostWithFormData } from '@/utils/ajax'
-import { ArtworkInfo, ArtworkInfoOrAd } from '@/types'
-
-export function sortArtList(
- obj: Record
-): T[] {
- return Object.values(obj).sort((a, b) => +b.id - +a.id)
-}
-
-export function isArtwork(item: ArtworkInfoOrAd): item is ArtworkInfo {
- return Object.keys(item).includes('id')
-}
-
-export async function addBookmark(
- illust_id: number | `${number}`
-): Promise {
- return (
- await ajax.post('/ajax/illusts/bookmarks/add', {
- illust_id,
- restrict: 0,
- comment: '',
- tags: [],
- })
- ).data
-}
-
-export async function removeBookmark(
- bookmark_id: number | `${number}`
-): Promise {
- return (
- await ajaxPostWithFormData('/ajax/illusts/bookmarks/delete', {
- bookmark_id: '' + bookmark_id,
- })
- ).data
-}
diff --git a/src/utils/index.ts b/src/utils/index.ts
deleted file mode 100644
index f1db3608..00000000
--- a/src/utils/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ArtworkInfo } from '@/types'
-
-export * from './artworkActions'
-export * from './userActions'
-
-export const defaultArtwork: ArtworkInfo = {
- id: '0',
- title: '',
- description: '',
- createDate: '',
- updateDate: '',
- illustType: 0,
- restrict: 0,
- xRestrict: 0,
- sl: 0,
- userId: '0',
- userName: '',
- alt: '',
- width: 0,
- height: 0,
- pageCount: 0,
- isBookmarkable: false,
- bookmarkData: null,
- titleCaptionTranslation: {
- workTitle: null,
- workCaption: null,
- },
- isUnlisted: false,
- url: '',
- tags: [],
- profileImageUrl: '',
- type: 'illust',
- aiType: 1,
-}
diff --git a/src/utils/setTitle.ts b/src/utils/setTitle.ts
deleted file mode 100644
index 4e9f1f16..00000000
--- a/src/utils/setTitle.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { PROJECT_NAME, PROJECT_TAGLINE } from '@/config'
-
-export function setTitle(...args: (string | number | null | undefined)[]) {
- return (document.title = [
- ...args.filter((i) => i !== null && typeof i !== 'undefined'),
- `${PROJECT_NAME} - ${PROJECT_TAGLINE}`,
- ].join(' | '))
-}
diff --git a/src/utils/userActions.ts b/src/utils/userActions.ts
deleted file mode 100644
index e3cc58a2..00000000
--- a/src/utils/userActions.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { ajaxPostWithFormData } from '@/utils/ajax'
-
-export async function addUserFollow(
- user_id: number | `${number}`
-): Promise {
- return (
- await ajaxPostWithFormData(`/bookmark_add.php`, {
- mode: 'add',
- type: 'user',
- user_id: '' + user_id,
- tag: '',
- restrict: '0',
- format: 'json',
- })
- ).data
-}
-
-export async function removeUserFollow(
- user_id: number | `${number}`
-): Promise {
- return (
- await ajaxPostWithFormData(`/rpc_group_setting.php`, {
- mode: 'del',
- type: 'bookuser',
- id: '' + user_id,
- })
- ).data
-}
diff --git a/src/view/siteCache.ts b/src/view/siteCache.ts
deleted file mode 100644
index af7fdae1..00000000
--- a/src/view/siteCache.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-const _siteCacheData = new Map()
-export function setCache(key: string | number, val: any) {
- console.log('setCache', key, val)
- _siteCacheData.set(key, val)
-}
-export function getCache(key: string | number) {
- const val = _siteCacheData.get(key)
- console.log('getCache', key, val)
- return val
-}
diff --git a/test/illustRecommend.json b/tests/illustRecommend.json
similarity index 100%
rename from test/illustRecommend.json
rename to tests/illustRecommend.json
diff --git a/test/userBookmarks.json b/tests/userBookmarks.json
similarity index 100%
rename from test/userBookmarks.json
rename to tests/userBookmarks.json
diff --git a/tsconfig.json b/tsconfig.json
index 8e350031..8939127f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,37 +1,5 @@
{
- "compilerOptions": {
- "baseUrl": ".",
- "target": "esnext",
- "module": "esnext",
- "moduleResolution": "node",
- "strict": true,
- "skipLibCheck": true,
- "jsx": "preserve",
- "sourceMap": true,
- "lib": ["esnext", "dom"],
- "plugins": [],
- "resolveJsonModule": true,
- "allowSyntheticDefaultImports": true,
- "isolatedModules": true,
- "importHelpers": true,
- "paths": {
- "@/*": ["src/*"]
- },
- "types": ["unplugin-icons/types/vue"]
- },
- "include": [
- "api/**/*.ts",
- "src/**/*.ts",
- "src/**/*.d.ts",
- "src/**/*.tsx",
- "src/**/*.vue",
- "src/**/*.json",
- "vite.config.ts",
- "auto-imports.d.ts",
- "components.d.ts",
- "middware.ts"
- ],
- "exclude": ["**/dist"],
+ "extends": ["./.nuxt/tsconfig.json"],
"vueCompilerOptions": {
"plugins": ["@vue/language-plugin-pug"]
}
diff --git a/vite.config.ts b/vite.config.ts
deleted file mode 100644
index 31ab29c1..00000000
--- a/vite.config.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { fileURLToPath } from 'url'
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import AutoImport from 'unplugin-auto-import/vite'
-import Icons from 'unplugin-icons/vite'
-import IconResolver from 'unplugin-icons/resolver'
-import Components from 'unplugin-vue-components/vite'
-import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
-
-const PROD = process.env.NODE_ENV === 'production'
-
-export default defineConfig({
- plugins: [
- vue(),
- AutoImport({
- dts: true,
- imports: [
- 'vue',
- 'vue-router',
- 'vue-i18n',
- '@vueuse/core',
- { axios: [['default', 'axios']] },
- ],
- resolvers: [
- IconResolver({
- alias: {
- fas: 'fa-solid',
- },
- }),
- ],
- dirs: ['src/components/**', 'src/composables', 'src/utils', 'src/types'],
- }),
- Components({ dts: true, resolvers: [NaiveUiResolver()] }),
- Icons({
- scale: 1,
- defaultClass: 'svg--inline',
- }),
- ],
- build: {},
- esbuild: {
- drop: PROD ? ['console'] : [],
- },
- server: { host: true },
- resolve: {
- alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url)),
- },
- extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.json'],
- },
-})