From 4e2867cf5b7ccd5d25aa14c28879a3eb9424e3cd Mon Sep 17 00:00:00 2001
From: WRXinYue <3322543587@qq.com>
Date: Fri, 5 Jul 2024 12:08:39 +0800
Subject: [PATCH] feat: complete basic hitokoto functionality
---
README.md | 191 +++++++++++++++++++++++++++++++++++++++++--
client/composable.ts | 57 +++++++++++++
client/enum.ts | 26 ++++++
client/index.ts | 3 +
client/options.ts | 15 ++++
index.d.ts | 3 +
index.ts | 1 +
node/index.ts | 3 +-
package.json | 5 +-
types/index.ts | 23 ++++++
utils/index.ts | 37 +++++++++
11 files changed, 355 insertions(+), 9 deletions(-)
create mode 100644 client/composable.ts
create mode 100644 client/enum.ts
create mode 100644 client/index.ts
create mode 100644 client/options.ts
create mode 100644 index.d.ts
create mode 100644 types/index.ts
create mode 100644 utils/index.ts
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
+
+一言指的是一句话,可以是动漫中的台词,也可以是网络上的各种小段子。或是感动,或是开心,有或是单纯的回忆...
+
+
+
+
+
+
+## 安装插件
+
+### 基础使用
+
+插件直接可以安装使用,通常不需要过多繁琐配置:
```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
+
+
+
+ 一言: {{ hitokoto.hitokoto }}
+ 来自: {{ hitokoto.from }}
+
+```
+
+### 自动刷新
+
+如果您想在网站上定时自动刷新获取一言句子,可以参考如下例子。每六秒调用 `fetchHitokoto` 实现刷新:
+
+```vue
+
+
+
+ 一言: {{ hitokoto.hitokoto }}
+ 来自: {{ hitokoto.from }}
+
+```
+
+### 手动刷新
+
+如果您希望在点击某个按钮时手动刷新 hitokoto 句子,可以参考如下例子:
+
+```vue
+
+
+
+ 一言: {{ hitokoto.hitokoto }}
+ 来自: {{ hitokoto.from }}
+
+
+```
+
+> [!IMPORTANT]
+> 为降低运算服务器的负载,hitokoto 国际站目前由于持续的流量、负载和攻击问题,启用了缓存机制,缓存时间为2秒。这意味着在同一地区、同一路线且使用相同参数的访问者在短时间内将获得相同的句子, 进多信息请参阅 [一言开发者中心](https://developer.hitokoto.cn/sentence/#请求地址)
+
+## 多页面
+
+如果您有多个页面,想在不同页面设置不同的配置,可以在 `useAddonHitokoto` 中传入您想要设置的配置:
+
+```vue
+
+
+
+ 一言: {{ hitokoto.hitokoto }}
+ 来自: {{ hitokoto.from }}
+
+```
+
+## 动态切换
+
+你也可以在 `fetchHitokoto` 函数中动态添加配置,以实现不同类型句子的切换:
+
+```vue
+
+
+
+ Hitokoto: {{ hitokoto.hitokoto }}
+ From: {{ hitokoto.from }}
+
+
+
+```
+
+## 配置 / 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'
+}