-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: WIP add anime adventure plugin
- Loading branch information
1 parent
a48bee8
commit 8340c46
Showing
10 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
wrangler.toml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"name": "chatgpt-plugin-example-anime-adventure", | ||
"version": "0.1.2", | ||
"private": true, | ||
"description": "A neverending choose-your-own-adventure Anime game.", | ||
"author": "Travis Fischer <travis@transitivebullsh.it>", | ||
"repository": "transitive-bullshit/chatgpt-plugin-ts", | ||
"license": "MIT", | ||
"type": "module", | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"scripts": { | ||
"dev": "wrangler dev", | ||
"deploy": "wrangler publish" | ||
}, | ||
"dependencies": { | ||
"@cloudflare/itty-router-openapi": "^0.0.15", | ||
"anilist-node": "^1.13.2", | ||
"chatgpt-plugin": "workspace:../../packages/chatgpt-plugin", | ||
"zod": "^3.21.4" | ||
}, | ||
"aiPlugin": { | ||
"name": "Anime Adventure", | ||
"description_for_model": "Plugin which enables the user to play a neverending choose-your-own-adventure Anime game. Use this plugin to generate a story about an anime of the user's choosing. The game starts with the \"start\" command, and the user can then use the \"continue\" command to continue the story. The user can also use the \"restart\" command to start a new story.", | ||
"logo_url": "TODO", | ||
"contact_email": "travis@transitivebullsh.it", | ||
"legal_info_url": "https://transitivebullsh.it" | ||
}, | ||
"keywords": [ | ||
"openai", | ||
"chatgpt", | ||
"plugin", | ||
"openapi", | ||
"anime", | ||
"game", | ||
"choose your own adventure" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# ChatGPT Plugin Example - Anime Adventure <!-- omit in toc --> | ||
|
||
> TODO: WIP | ||
[![Build Status](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml/badge.svg)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/blob/main/license) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](https://prettier.io) | ||
|
||
- [Intro](#intro) | ||
- [License](#license) | ||
|
||
## Intro | ||
|
||
TODO: WIP | ||
|
||
## License | ||
|
||
MIT © [Travis Fischer](https://transitivebullsh.it) | ||
|
||
If you found this project interesting, please consider [sponsoring me](https://github.com/sponsors/transitive-bullshit) or <a href="https://twitter.com/transitive_bs">following me on twitter <img src="https://storage.googleapis.com/saasify-assets/twitter-logo.svg" alt="twitter" height="24px" align="center"></a> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { OpenAPIRouter } from '@cloudflare/itty-router-openapi' | ||
import { defineAIPluginManifest } from 'chatgpt-plugin' | ||
|
||
import * as routes from './routes' | ||
import pkg from '../package.json' | ||
|
||
export interface Env { | ||
ANILIST_ACCESS_TOKEN: string | ||
ENVIRONMENT: string | ||
} | ||
|
||
const router = OpenAPIRouter({ | ||
schema: { | ||
info: { | ||
title: pkg.aiPlugin.name, | ||
version: pkg.version | ||
} | ||
} | ||
}) | ||
|
||
router.get('/start', routes.StartGame) | ||
|
||
router.get('/.well-known/ai-plugin.json', (request: Request) => { | ||
const host = request.headers.get('host') | ||
const pluginManifest = defineAIPluginManifest( | ||
{ | ||
description_for_human: pkg.description, | ||
name_for_human: pkg.aiPlugin.name, | ||
...pkg.aiPlugin | ||
}, | ||
{ openAPIUrl: `https://${host}/openapi.json` } | ||
) | ||
|
||
return new Response(JSON.stringify(pluginManifest, null, 2), { | ||
headers: { | ||
'content-type': 'application/json;charset=UTF-8' | ||
} | ||
}) | ||
}) | ||
|
||
// 404 for everything else | ||
router.all('*', () => new Response('Not Found.', { status: 404 })) | ||
|
||
export default { | ||
fetch: (request: Request, env: Env, ctx: ExecutionContext) => | ||
router.handle(request, env, ctx) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { OpenAPIRoute, Query, Str } from '@cloudflare/itty-router-openapi' | ||
import Anilist from 'anilist-node' | ||
|
||
// import { isValidChatGPTIPAddress } from 'chatgpt-plugin' | ||
import * as types from './types' | ||
import * as utils from './utils' | ||
|
||
export class StartGame extends OpenAPIRoute { | ||
static schema = { | ||
summary: | ||
'Starts a new game of Anime Adventure. The user must provide the name of an Anime that they want to base the game around. You should use the anime metadata that is returned to start a story set in the world of this anime.', | ||
parameters: { | ||
anime: Query( | ||
new Str({ | ||
description: 'Name of the anime show', | ||
example: 'cowboy bebop' | ||
}), | ||
{ | ||
required: true | ||
} | ||
) | ||
}, | ||
responses: { | ||
'200': { | ||
schema: { | ||
anime: { | ||
id: new Str(), | ||
title: new Str({ | ||
description: 'Official title of the anime' | ||
}), | ||
|
||
description: new Str({ | ||
description: "Overview of the show and it's plot" | ||
}), | ||
|
||
genres: [new Str()], | ||
tags: [new Str()], | ||
characters: [new Str()], | ||
reviews: [ | ||
{ | ||
summary: new Str(), | ||
body: new Str() | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
async handle(request: Request, env: any, _ctx, data: Record<string, any>) { | ||
const anilistAccessToken = env.ANILIST_ACCESS_TOKEN | ||
if (!anilistAccessToken) { | ||
return new Response('ANILIST_ACCESS_TOKEN not set', { status: 500 }) | ||
} | ||
|
||
const ip = request.headers.get('Cf-Connecting-Ip') | ||
if (!ip) { | ||
console.warn('search error missing IP address') | ||
return new Response('invalid source IP', { status: 500 }) | ||
} | ||
|
||
// TODO: protect against abuse in prod | ||
// if (env.ENVIRONMENT === 'production' && !isValidChatGPTIPAddress(ip)) { | ||
// // console.warn('search error invalid IP address', ip) | ||
// return new Response(`Forbidden`, { status: 403 }) | ||
// } | ||
|
||
const openaiUserLocaleInfo = request.headers.get( | ||
'openai-subdivision-1-iso-code' | ||
) | ||
const { anime: query } = data | ||
console.log() | ||
console.log() | ||
console.log('>>> search', `${query} (${openaiUserLocaleInfo}, ${ip})`) | ||
console.log() | ||
|
||
const anilist = new Anilist(anilistAccessToken) | ||
const r = (await anilist.search('anime', query)) as types.AnimeSearchResults | ||
|
||
if (!r.media?.length) { | ||
return new Response(`Anime "${query}" not found`, { status: 404 }) | ||
} | ||
|
||
const media = r.media[0] | ||
const anime = await anilist.media.anime(media.id) | ||
|
||
const animeMetadata = { | ||
...utils.pick(anime, 'id', 'description', 'genres'), | ||
title: | ||
anime.title?.userPreferred || | ||
anime.title?.english || | ||
anime.title?.native || | ||
anime.title?.romaji || | ||
query, | ||
// coverImage: anime.coverImage?.large, | ||
// bannerImage: anime.bannerImage, | ||
tags: anime.tags?.map((tag) => tag.name), | ||
characters: anime.characters?.map((character) => character.name), | ||
reviews: anime.reviews | ||
?.slice(0, 2) | ||
.map((review) => utils.pick(review, 'summary', 'body')) | ||
} | ||
|
||
console.log() | ||
console.log() | ||
console.log('<<< search', `${query} (${openaiUserLocaleInfo}, ${ip})`) | ||
|
||
return new Response(JSON.stringify({ anime: animeMetadata }, null, 2), { | ||
headers: { | ||
'content-type': 'application/json;charset=UTF-8' | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export interface AnimeSearchResults { | ||
pageInfo: PageInfo | ||
media: Media[] | ||
} | ||
|
||
export interface Media { | ||
id: number | ||
title: Title | ||
} | ||
|
||
export interface Title { | ||
romaji: string | ||
english: null | string | ||
native: string | ||
userPreferred: string | ||
} | ||
|
||
export interface PageInfo { | ||
total: number | ||
currentPage: number | ||
lastPage: number | ||
hasNextPage: boolean | ||
perPage: number | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export function pick<T extends object, U = T>(obj: T, ...keys: string[]): U { | ||
return Object.fromEntries( | ||
keys.filter((key) => key in obj).map((key) => [key, obj[key]]) | ||
) as U | ||
} | ||
|
||
export function omit<T extends object, U = T>(obj: T, ...keys: string[]): U { | ||
return Object.fromEntries<T>( | ||
Object.entries(obj).filter(([key]) => !keys.includes(key)) | ||
) as U | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"extends": "../../tsconfig.base", | ||
"compilerOptions": { | ||
"composite": true, | ||
"outDir": "build", | ||
"tsBuildInfoFile": "build/.tsbuildinfo", | ||
"emitDeclarationOnly": true, | ||
"types": ["@cloudflare/workers-types"] | ||
}, | ||
"include": ["src/**/*.ts", "package.json"], | ||
"exclude": ["node_modules"], | ||
"references": [{ "path": "../../packages/chatgpt-plugin/tsconfig.json" }] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
name = "chatgpt-plugin-anime-adventure" | ||
main = "src/index.ts" | ||
compatibility_date = "2023-04-04" | ||
|
||
[vars] | ||
ANILIST_ACCESS_TOKEN="TODO" |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.