diff --git a/README.md b/README.md index cae6539..755db10 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,199 @@ -# valaxy-addon-template +

valaxy-addon-hitokoto

-## Usage +
+一言指的是一句话,可以是动漫中的台词,也可以是网络上的各种小段子。或是感动,或是开心,有或是单纯的回忆...
+
+ +

+NPM version +

+ +## 安装插件 + +### 基础使用 + +插件直接可以安装使用,通常不需要过多繁琐配置: ```bash -npm i valaxy-addon-template +pnpm add valaxy-addon-hitokoto ``` -### 加载插件 +```ts +import { defineValaxyConfig } from 'valaxy' +import { addonHitokoto } from 'valaxy-addon-hitokoto' + +export default defineValaxyConfig({ + addons: [ + addonHitokoto(), + ], +}) +``` + +### 简单配置 + +如果您想获取指定类型的句子,或者是特定长度的句子,可以参考以下配置示例: ```ts import { defineValaxyConfig } from 'valaxy' -import { addonTemplate } from 'valaxy-addon-template' +import { HitokotoType, addonHitokoto } from 'valaxy-addon-hitokoto' export default defineValaxyConfig({ addons: [ - addonTemplate(), + addonHitokoto({ + args: [HitokotoType.Animation, HitokotoType.Comic], // 句子类型 + minLength: 0, // 最短句子长度 + maxLength: 30 // 最长句子长度 + }), ], }) ``` + +> [!TIP] +> 关于 `HitokotoType` 类别的更多信息,请参见 [HitokotoType](https://github.com/valaxyjs/valaxy-addon-hitokoto/tree/master/client/enum.ts) + +## 使用插件 + +以下是简洁快捷的使用例子: + +```vue + + + +``` + +### 自动刷新 + +如果您想在网站上定时自动刷新获取一言句子,可以参考如下例子。每六秒调用 `fetchHitokoto` 实现刷新: + +```vue + + + +``` + +### 手动刷新 + +如果您希望在点击某个按钮时手动刷新 hitokoto 句子,可以参考如下例子: + +```vue + + + +``` + +> [!IMPORTANT] +> 为降低运算服务器的负载,hitokoto 国际站目前由于持续的流量、负载和攻击问题,启用了缓存机制,缓存时间为2秒。这意味着在同一地区、同一路线且使用相同参数的访问者在短时间内将获得相同的句子, 进多信息请参阅 [一言开发者中心](https://developer.hitokoto.cn/sentence/#请求地址) + +## 多页面 + +如果您有多个页面,想在不同页面设置不同的配置,可以在 `useAddonHitokoto` 中传入您想要设置的配置: + +```vue + + + +``` + +## 动态切换 + +你也可以在 `fetchHitokoto` 函数中动态添加配置,以实现不同类型句子的切换: + +```vue + + + +``` + +## 配置 / Options + +| 属性名 | 类型 | 默认值 | 说明 | +| ---- | ---- | ---- | ---- | +| api | `string` | --- | `'intl'` 为海外线路,默认为国际线路。填入其他为自定义 API, 返回 JSON 则为一言 API,数组类型取第一个元素,字符串类型直接取值 | +| args | `string` | `HitokotoType[]` | 获取指定句子类型, 类型详见 [HitokotoType](https://github.com/valaxyjs/valaxy-addon-hitokoto/tree/master/client/enum.ts)| +| minLength | `number` | `0` | API 获取的最短句子长度 | +| maxLength | `number` | `30` | API 获取的最长句子长度 | + +> [!NOTE] +> 关于自定义 API,这里有一个示例:将 `api` 填入 `https://el-bot-api.elpsy.cn/api/words/young`,由于返回值是一个数组,因此取其中的一个元素作为一句话 + +## 其他 + +自行部署: +语句库: + +请求地址: + +| 地址 | 协议 | 方法 | QPS 限制 | 线路 | +| ---- | ---- | ---- | ---- | ---- | +| `v1.hitokoto.cn` | HTTPS | Any | 2 | 全球 | +| `international.v1.hitokoto.cn` | HTTPS | Any | 20(含缓存*) | 海外 | + +## 致谢 + +- [Hitokoto](https://hitokoto.cn/) diff --git a/client/composable.ts b/client/composable.ts new file mode 100644 index 0000000..2bc04ef --- /dev/null +++ b/client/composable.ts @@ -0,0 +1,57 @@ +import { ref } from 'vue' +import type { Hitokoto, HitokotoOptions } from '../types' +import { generateUrl, isHitokotoOptions, toCamelCase } from '../utils' +import { useAddonHitokotoConfig } from './options' + +export function useAddonHitokoto(options: HitokotoOptions | null = null) { + const hitokotoOptions = useAddonHitokotoConfig() + + const intlUrl = 'https://international.v1.hitokoto.cn' + const defaultUrl = 'https://v1.hitokoto.cn' + + const hitokoto = ref({ + id: 0, + uuid: '', + hitokoto: '', + type: '', + from: '', + fromWho: '', + creator: '', + creatorUid: 0, + reviewer: 0, + commitFrom: '', + createdAt: '', + length: 0, + } as any) + + const fetchHitokoto = async (fetchOptions: HitokotoOptions = options || hitokotoOptions.value) => { + if (!isHitokotoOptions(fetchOptions)) + console.error('Invalid argument: fetchOptions must be of type HitokotoOptions', fetchOptions) + + const baseUrl = fetchOptions.api === 'intl' ? intlUrl : fetchOptions.api || defaultUrl + + const finalUrl = generateUrl(baseUrl, fetchOptions) + + try { + const response = await fetch(finalUrl) + if (!response.ok) + throw new Error(`Network response was not ok.`) + + const data = await response.json() + + if (Array.isArray(data)) + hitokoto.value.hitokoto = data[0] + else if (typeof data === 'object') + hitokoto.value = toCamelCase(data) + else if (typeof data === 'string') + hitokoto.value.hitokoto = data + } + catch (error) { + console.error('Error fetching visitor count:', error) + } + } + + fetchHitokoto() + + return { hitokoto, fetchHitokoto } +} diff --git a/client/enum.ts b/client/enum.ts new file mode 100644 index 0000000..eca0236 --- /dev/null +++ b/client/enum.ts @@ -0,0 +1,26 @@ +export enum HitokotoType { + /** 动画 */ + Animation = 'a', + /** 漫画 */ + Comic = 'b', + /** 游戏 */ + Game = 'c', + /** 文学 */ + Literature = 'd', + /** 原创 */ + Original = 'e', + /** 来自网络 */ + FromInternet = 'f', + /** 其他 */ + Others = 'g', + /** 影视 */ + Movie = 'h', + /** 诗词 */ + Poetry = 'i', + /** 网易云 */ + NetEaseCloud = 'j', + /** 哲学 */ + Philosophy = 'k', + /** 抖机灵 */ + Wit = 'l', +} diff --git a/client/index.ts b/client/index.ts new file mode 100644 index 0000000..aabe4b4 --- /dev/null +++ b/client/index.ts @@ -0,0 +1,3 @@ +export * from './composable' +export * from './options' +export * from './enum' diff --git a/client/options.ts b/client/options.ts new file mode 100644 index 0000000..120b0c3 --- /dev/null +++ b/client/options.ts @@ -0,0 +1,15 @@ +import { computed } from 'vue' +import type { ValaxyAddon } from 'valaxy' +import { useRuntimeConfig } from 'valaxy' +import type { HitokotoOptions } from '../types' + +export function useAddonHitokotoConfig() { + const runtimeConfig = useRuntimeConfig() + return computed(() => { + const options = (runtimeConfig.value.addons['valaxy-addon-hitokoto'] as ValaxyAddon).options + + return { + ...options, + } + }) +} diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..0ffce97 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,3 @@ +export * from './client' +export * from './types' +export * from './node' diff --git a/index.ts b/index.ts index 520f0e6..6b81a4e 100644 --- a/index.ts +++ b/index.ts @@ -1 +1,2 @@ export * from './node' +export * from './client' diff --git a/node/index.ts b/node/index.ts index 2b022bd..17c9b42 100644 --- a/node/index.ts +++ b/node/index.ts @@ -1,7 +1,8 @@ import { defineValaxyAddon } from 'valaxy' import pkg from '../package.json' +import type { HitokotoOptions } from '../types' -export const addonHitokoto = defineValaxyAddon(options => ({ +export const addonHitokoto = defineValaxyAddon(options => ({ name: pkg.name, enable: true, options, diff --git a/package.json b/package.json index 4fd2598..3badb47 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "valaxy-addon-hitokoto", "version": "0.0.1", - "description": "Template for Valaxy Addon.", + "description": "Hitokoto Composition API for Valaxy", "repository": "https://github.com/valaxyjs/valaxy-addon-hitokoto", "keywords": [ "valaxy", @@ -9,5 +9,6 @@ "valaxy-addon", "hitokoto" ], - "main": "index.ts" + "main": "index.ts", + "types": "index.d.ts" } diff --git a/types/index.ts b/types/index.ts new file mode 100644 index 0000000..8f0fac2 --- /dev/null +++ b/types/index.ts @@ -0,0 +1,23 @@ +import type { HitokotoType } from '../client/enum' + +export interface HitokotoOptions { + api?: string + args?: HitokotoType[] + minLength?: number + maxLength?: number +} + +export interface Hitokoto { + id: number + uuid: string + hitokoto: string + type: HitokotoType + from: string + fromWho: string + creator: string + creatorUid: number + reviewer: number + commitFrom: string + createdAt: string + length: number +} diff --git a/utils/index.ts b/utils/index.ts new file mode 100644 index 0000000..5265586 --- /dev/null +++ b/utils/index.ts @@ -0,0 +1,37 @@ +import type { HitokotoOptions } from '../types' + +export function toCamelCase(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(v => toCamelCase(v)) + } + else if (obj !== null && typeof obj === 'object') { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => { + const camelKey = key.replace(/_([a-z])/g, (_, char) => char.toUpperCase()) + return [camelKey, toCamelCase(value)] + }), + ) + } + return obj +} + +export function generateUrl(url: string, options: HitokotoOptions): string { + let queryParams = '' + + if (options?.args?.length !== 0) { + const uniqueArgs = [...new Set(options.args)] + queryParams = uniqueArgs.map(arg => `c=${arg}`).join('&') + } + + const minLengthParam = options.minLength !== undefined ? `&min_length=${options.minLength}` : '' + const maxLengthParam = options.maxLength !== undefined ? `&max_length=${options.maxLength}` : '' + + const params = `${queryParams}${minLengthParam}${maxLengthParam}` + const finalParams = params.startsWith('&') ? params.substring(1) : params + + return `${url}/?${finalParams}` +} + +export function isHitokotoOptions(obj: any): obj is HitokotoOptions { + return obj && typeof obj === 'object' +}