├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── update.yml ├── .gitignore ├── .node-version ├── LICENSE ├── README.md ├── api.json ├── api_type_overrides.json ├── api_undocumented_methods.txt ├── biome.json ├── build.mjs ├── check_removed_apis.php ├── generate_api.php ├── generate_api_from_docs.php ├── generate_api_from_protos.php ├── package-lock.json ├── package.json ├── public ├── 192.png ├── 404.html ├── 512.png ├── favicon.ico ├── icons │ ├── artifact.jpg │ ├── cs2.jpg │ ├── deadlock.jpg │ ├── dota.jpg │ ├── portal2.jpg │ ├── steam.jpg │ ├── tf.jpg │ └── underlords.jpg ├── manifest.json ├── robots.txt └── serviceworker.js ├── src ├── ApiParameter.vue ├── App.ts ├── App.vue ├── HighlightedSearchMethod.ts ├── documentation.ts ├── index.html ├── index.ts ├── interfaces.ts ├── search.ts ├── ssr.ts └── style.css ├── tsconfig.json ├── update.sh ├── vue-shim.d.ts └── wrangler.jsonc /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [api.json] 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.ico binary 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | ignore: 8 | - dependency-name: "*" 9 | update-types: ["version-update:semver-minor", "version-update:semver-patch"] 10 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Daily update 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '0 5 * * *' 10 | 11 | jobs: 12 | scheduled: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Git config 18 | run: |- 19 | git config user.name "github-actions[bot]" 20 | git config user.email "github-actions[bot]@users.noreply.github.com" 21 | - name: Run script 22 | run: ./update.sh 23 | env: 24 | STEAM_PUBLIC_API_KEY: ${{ secrets.STEAM_PUBLIC_API_KEY }} 25 | STEAM_PUBLISHER_API_KEY: ${{ secrets.STEAM_PUBLISHER_API_KEY }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /vendor/ 3 | /dist/ 4 | /protobufs_repo/ 5 | /config.php 6 | /api_from_protos.json 7 | /api_from_docs.json 8 | /public/api.json 9 | /public/*.js 10 | /public/*.css 11 | /public/*.map 12 | /public/index.html 13 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Pavel Djundik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steam Web API Documentation 2 | 3 | An automatically generated list of Steam Web API interfaces, methods and parameters. Allows you to craft requests in the browser. 4 | 5 | If you specify the web api key, it will be stored in your browser, and will only be sent to Valve's API servers if you chose to do so. 6 | 7 | **⚠ Please do not email me about questions on how to use specific APIs, 8 | I provide an automatically updated and generated list, I do not personally know how to use all of them.** 9 | 10 | ## api.json 11 | 12 | `api.json` is the final file that is generated from various sources: 13 | 14 | 1. Takes existing [`api.json`](api.json) file as a base, so removed methods are persisted 15 | 2. Official list from [`GetSupportedAPIList`](https://steamapi.xpaw.me/#ISteamWebAPIUtil/GetSupportedAPIList) 16 | - Using normal API key 17 | - Using partner API key 18 | 3. [Parsed protobufs](https://github.com/SteamDatabase/Protobufs) to find service methods and tested against the API 19 | - Descriptions and fields are also parsed 20 | 4. [`api_undocumented_methods.txt`](api_undocumented_methods.txt) to insert undocumented and otherwise unknown APIs 21 | 5. [`api_type_overrides.json`](api_type_overrides.json) to fix up types of known method parameters. Such as enforcing arrays 22 | 23 | ## config.php 24 | 25 | To run generation scripts, a `config.php` file needs to be created with API keys: 26 | 27 | ```php 28 | location.reload());`, 38 | }; 39 | } 40 | 41 | const context = await esbuild.context(esbuildOptions); 42 | 43 | if (isDev) { 44 | await context.watch(); 45 | await context.serve({ 46 | host: 'localhost', 47 | servedir: 'public/', 48 | }); 49 | } else { 50 | console.log('Building'); 51 | 52 | await context.rebuild(); 53 | await context.dispose(); 54 | 55 | console.log('Running SSR...'); 56 | exec('node public/ssr.js', async (error, stdout, stderr) => { 57 | if (error) { 58 | console.error(`SSR Error: ${error}`); 59 | return; 60 | } 61 | 62 | const ssrHtml = stdout.trim(); 63 | 64 | const indexPath = 'public/index.html'; 65 | 66 | let indexHtml = await fs.readFile(indexPath, 'utf8'); 67 | indexHtml = indexHtml.replace('
', `
${ssrHtml}
`); 68 | 69 | await fs.writeFile(indexPath, indexHtml); 70 | await fs.unlink('public/ssr.js'); 71 | 72 | console.log('SSR HTML injected successfully'); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /check_removed_apis.php: -------------------------------------------------------------------------------- 1 | '', 9 | CURLOPT_RETURNTRANSFER => true, 10 | CURLOPT_TIMEOUT => 5, 11 | CURLOPT_CONNECTTIMEOUT => 5, 12 | ] ); 13 | 14 | $notFound = "\n\n404 Not Found\n\n\n

Not Found

\n\n"; 15 | $copy = $FinalList; 16 | 17 | foreach( $copy as $serviceName => $Interface ) 18 | { 19 | foreach( $Interface as $methodName => $Method ) 20 | { 21 | $path = $serviceName . '/' . $methodName . '/v' . $Method[ 'version' ]; 22 | 23 | printf( 'Checking %-70s', $path . '...' ); 24 | 25 | curl_setopt( $c, CURLOPT_URL, "https://api.steampowered.com/{$path}/" ); 26 | $response = curl_exec( $c ); 27 | 28 | $code = curl_getinfo( $c, CURLINFO_HTTP_CODE ); 29 | 30 | echo ' ' . $code; 31 | 32 | if( $code !== 404 ) 33 | { 34 | echo PHP_EOL; 35 | continue; 36 | } 37 | 38 | if( $response !== $notFound ) 39 | { 40 | echo ' different kind of error' . PHP_EOL; 41 | continue; 42 | } 43 | 44 | echo ' REMOVED!' . PHP_EOL; 45 | unset( $FinalList[ $serviceName ][ $methodName ] ); 46 | } 47 | 48 | if( empty( $FinalList[ $serviceName ] ) ) 49 | { 50 | unset( $FinalList[ $serviceName ] ); 51 | } 52 | } 53 | 54 | file_put_contents( 55 | __DIR__ . DIRECTORY_SEPARATOR . 'api.json', 56 | json_encode( $FinalList, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . PHP_EOL 57 | ); 58 | 59 | echo 'Done' . PHP_EOL; 60 | -------------------------------------------------------------------------------- /generate_api.php: -------------------------------------------------------------------------------- 1 | 10, 18 | CURLOPT_RETURNTRANSFER => true, 19 | CURLOPT_USERAGENT => '', 20 | CURLOPT_URL => 'https://community.steam-api.com/ISteamWebAPIUtil/GetSupportedAPIList/v1/?format=json&key=' . $PublicApiKey 21 | ] ); 22 | unset( $PublicApiKey ); 23 | $NonPublisher = curl_exec( $c ); 24 | 25 | echo 'Downloading partner list...' . PHP_EOL; 26 | 27 | curl_setopt( $c, CURLOPT_URL, 'https://partner.steam-api.com/ISteamWebAPIUtil/GetSupportedAPIList/v1/?format=json&key=' . $PublisherApiKey ); 28 | unset( $PublisherApiKey ); 29 | $YesPublisher = curl_exec( $c ); 30 | 31 | $Undocumented = []; 32 | 33 | if( file_exists( __DIR__ . '/api_undocumented_methods.txt' ) ) 34 | { 35 | foreach( file( __DIR__ . '/api_undocumented_methods.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ) as $Method ) 36 | { 37 | [ $UndocumentedInterface, $UndocumentedMethod, $UndocumentedVersion ] = explode( '/', $Method, 3 ); 38 | 39 | if( !isset( $Undocumented[ $UndocumentedInterface ] ) ) 40 | { 41 | $Undocumented[ $UndocumentedInterface ] = 42 | [ 43 | 'name' => $UndocumentedInterface, 44 | 'methods' => [], 45 | ]; 46 | } 47 | 48 | $Undocumented[ $UndocumentedInterface ][ 'methods' ][] = 49 | [ 50 | 'name' => $UndocumentedMethod, 51 | 'version' => (int)substr( $UndocumentedVersion, 1 ), 52 | ]; 53 | } 54 | } 55 | 56 | echo 'Generating...' . PHP_EOL; 57 | 58 | $YesPublisher = json_decode( $YesPublisher, true, 512, JSON_THROW_ON_ERROR ); 59 | $YesPublisher = $YesPublisher[ 'apilist' ][ 'interfaces' ] ?? []; 60 | 61 | $NonPublisher = json_decode( $NonPublisher, true, 512, JSON_THROW_ON_ERROR ); 62 | $NonPublisher = $NonPublisher[ 'apilist' ][ 'interfaces' ] ?? []; 63 | 64 | if( file_exists( __DIR__ . '/api_from_protos.json' ) ) 65 | { 66 | $UndocumentedFromServices = json_decode( file_get_contents( __DIR__ . '/api_from_protos.json' ), true, 512, JSON_THROW_ON_ERROR ); 67 | } 68 | else 69 | { 70 | $UndocumentedFromServices = []; 71 | } 72 | 73 | if( file_exists( __DIR__ . '/api_from_docs.json' ) ) 74 | { 75 | $UndocumentedFromPartnerDocs = json_decode( file_get_contents( __DIR__ . '/api_from_docs.json' ), true, 512, JSON_THROW_ON_ERROR ); 76 | } 77 | else 78 | { 79 | $UndocumentedFromPartnerDocs = []; 80 | } 81 | 82 | $FinalList = file_exists( __DIR__ . '/api.json' ) ? json_decode( file_get_contents( __DIR__ . '/api.json' ), true, 512, JSON_THROW_ON_ERROR ) : []; 83 | 84 | MarkAsRemoved( $FinalList ); 85 | MergeLists( $FinalList, $NonPublisher ); 86 | MergeLists( $FinalList, $YesPublisher, 'publisher_only' ); 87 | MergeLists( $FinalList, $Undocumented, 'undocumented' ); 88 | MergeLists( $FinalList, $UndocumentedFromPartnerDocs, 'undocumented' ); 89 | MergeLists( $FinalList, $UndocumentedFromServices, 'undocumented' ); 90 | 91 | $MethodKeysOrder = 92 | [ 93 | '_type' => 0, 94 | 'version' => 1, 95 | 'httpmethod' => 2, 96 | 'description' => 3, 97 | 'parameters' => 4, 98 | ]; 99 | 100 | foreach( $FinalList as $InterfaceName => $Interface ) 101 | { 102 | foreach( $Interface as $MethodName => $Method ) 103 | { 104 | if( !isset( $Method[ 'parameters' ] ) ) 105 | { 106 | $FinalList[ $InterfaceName ][ $MethodName ][ 'parameters' ] = []; 107 | } 108 | 109 | uksort( $FinalList[ $InterfaceName ][ $MethodName ], function( string $a, string $b ) use( $MethodKeysOrder ) : int 110 | { 111 | return $MethodKeysOrder[ $a ] - $MethodKeysOrder[ $b ]; 112 | } ); 113 | } 114 | } 115 | 116 | if( file_exists( __DIR__ . '/api_type_overrides.json' ) ) 117 | { 118 | $ParameterTypeOverrides = json_decode( file_get_contents( __DIR__ . '/api_type_overrides.json' ), true, 512, JSON_THROW_ON_ERROR ); 119 | 120 | foreach( $FinalList as $InterfaceName => &$Interface ) 121 | { 122 | foreach( $Interface as $MethodName => &$Method ) 123 | { 124 | foreach( $Method[ 'parameters' ] as &$Parameter ) 125 | { 126 | $Key = "{$InterfaceName}/{$MethodName}/{$Parameter[ 'name' ]}"; 127 | 128 | if( isset( $ParameterTypeOverrides[ $Key ] ) ) 129 | { 130 | $Parameter[ 'type' ] = $ParameterTypeOverrides[ $Key ]; 131 | 132 | if( str_ends_with( $Parameter[ 'type' ], '[]' ) && !str_ends_with( $Parameter[ 'name' ], '[0]' ) ) 133 | { 134 | $Parameter[ 'name' ] .= '[0]'; 135 | } 136 | } 137 | else if( str_ends_with( $Parameter[ 'name' ], '[0]' ) && !str_ends_with( $Parameter[ 'type' ], '[]' ) ) 138 | { 139 | $Parameter[ 'type' ] .= '[]'; 140 | } 141 | 142 | if( isset( $Parameter[ 'description' ] ) && stripos( $Parameter[ 'description' ], '(Optional) ' ) === 0 ) 143 | { 144 | $Parameter[ 'optional' ] = true; 145 | $Parameter[ 'description' ] = substr( $Parameter[ 'description' ], 11 ); 146 | } 147 | } 148 | 149 | unset( $Parameter ); 150 | } 151 | 152 | unset( $Method ); 153 | } 154 | 155 | unset( $Interface ); 156 | } 157 | 158 | // Remove third-party games 159 | unset( $FinalList[ 'IEconItems_221540' ] ); 160 | unset( $FinalList[ 'IEconItems_238460' ] ); 161 | 162 | ksort( $FinalList, SORT_NATURAL ); 163 | 164 | foreach( $FinalList as &$Interface ) 165 | { 166 | ksort( $Interface, SORT_NATURAL ); 167 | } 168 | 169 | unset( $Interface ); 170 | 171 | file_put_contents( 172 | __DIR__ . DIRECTORY_SEPARATOR . 'api.json', 173 | json_encode( $FinalList, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . PHP_EOL 174 | ); 175 | 176 | echo 'Done' . PHP_EOL; 177 | 178 | function MergeLists( array &$FinalList, array $Interfaces, ?string $Type = null ) : void 179 | { 180 | foreach( $Interfaces as $Interface ) 181 | { 182 | $InterfaceName = $Interface[ 'name' ]; 183 | 184 | if( !isset( $FinalList[ $InterfaceName ] ) ) 185 | { 186 | $FinalList[ $InterfaceName ] = []; 187 | } 188 | 189 | foreach( $Interface[ 'methods' ] as $Method ) 190 | { 191 | $MethodName = $Method[ 'name' ]; 192 | $CurrentVersion = $FinalList[ $InterfaceName ][ $MethodName ][ 'version' ] ?? 0; 193 | $CurrentType = $FinalList[ $InterfaceName ][ $MethodName ][ '_type' ] ?? null; 194 | 195 | if( $CurrentVersion >= $Method[ 'version' ] && $CurrentType !== 'undocumented' ) 196 | { 197 | continue; 198 | } 199 | 200 | if( $CurrentType === 'undocumented' && $CurrentType === $Type ) 201 | { 202 | if( !empty( $Method[ 'description' ] ) && empty( $FinalList[ $InterfaceName ][ $MethodName ][ 'description' ] ) ) 203 | { 204 | $FinalList[ $InterfaceName ][ $MethodName ][ 'description' ] = $Method[ 'description' ]; 205 | } 206 | 207 | if( !empty( $Method[ 'httpmethod' ] ) && empty( $FinalList[ $InterfaceName ][ $MethodName ][ 'httpmethod' ] ) ) 208 | { 209 | $FinalList[ $InterfaceName ][ $MethodName ][ 'httpmethod' ] = $Method[ 'httpmethod' ]; 210 | } 211 | 212 | if( !empty( $Method[ 'parameters' ] ) ) 213 | { 214 | foreach( $Method[ 'parameters' ] as $Parameter ) 215 | { 216 | $Found = false; 217 | 218 | foreach( $FinalList[ $InterfaceName ][ $MethodName ][ 'parameters' ] as $ParameterId => $CurrentParameter ) 219 | { 220 | if( $Parameter[ 'name' ] === $CurrentParameter[ 'name' ] ) 221 | { 222 | $Found = true; 223 | 224 | if( !empty( $Parameter[ 'description' ] ) && empty( $CurrentParameter[ 'description' ] ) ) 225 | { 226 | $FinalList[ $InterfaceName ][ $MethodName ][ 'parameters' ][ $ParameterId ][ 'description' ] = $Parameter[ 'description' ]; 227 | } 228 | 229 | if( isset( $Parameter[ 'extra' ] ) ) 230 | { 231 | $FinalList[ $InterfaceName ][ $MethodName ][ 'parameters' ][ $ParameterId ][ 'extra' ] = $Parameter[ 'extra' ]; 232 | } 233 | 234 | break; 235 | } 236 | } 237 | 238 | if( !$Found ) 239 | { 240 | $FinalList[ $InterfaceName ][ $MethodName ][ 'parameters' ][] = $Parameter; 241 | } 242 | } 243 | } 244 | 245 | continue; 246 | } 247 | 248 | unset( $Method[ 'name' ] ); 249 | 250 | if( $Type !== null ) 251 | { 252 | $Method[ '_type' ] = $Type; 253 | } 254 | 255 | $FinalList[ $InterfaceName ][ $MethodName ] = $Method; 256 | } 257 | } 258 | } 259 | 260 | function MarkAsRemoved( array &$FinalList ) : void 261 | { 262 | foreach( $FinalList as &$Interface ) 263 | { 264 | foreach( $Interface as &$Method ) 265 | { 266 | $Method[ '_type' ] = 'undocumented'; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /generate_api_from_docs.php: -------------------------------------------------------------------------------- 1 | getExtension() !== 'html' ) 19 | { 20 | continue; 21 | } 22 | 23 | $Doc = file_get_contents( $fileInfo ); 24 | 25 | if( $Doc === false ) 26 | { 27 | throw new Exception( "Failed to read $fileInfo" ); 28 | } 29 | 30 | $Doc = explode( '

', $Doc ); 31 | 32 | foreach( $Doc as $Section ) 33 | { 34 | if( preg_match( '/
(.+?)<\/div>/s', $Section, $UrlMatches ) !== 1 ) 35 | { 36 | continue; 37 | } 38 | 39 | $Url = htmlspecialchars_decode( $UrlMatches[ 1 ] ); 40 | $Url = explode( ' ', trim( preg_replace( '/[\n\r ]+/', ' ', $Url ) ) ); 41 | 42 | if( count( $Url ) < 2 ) 43 | { 44 | continue; 45 | } 46 | 47 | $UrlPath = parse_url( $Url[ 1 ], PHP_URL_PATH ); 48 | 49 | if( empty( $UrlPath ) ) 50 | { 51 | continue; 52 | } 53 | 54 | $Parameters = []; 55 | $HttpMethod = $Url[ 0 ]; 56 | $HttpPath = explode( '/', $UrlPath ); 57 | 58 | if( count( $HttpPath ) < 4 ) 59 | { 60 | continue; 61 | } 62 | 63 | $ApiService = $HttpPath[ 1 ]; 64 | $ApiMethod = $HttpPath[ 2 ]; 65 | $ApiVersion = (int)str_replace( 'v', '', $HttpPath[ 3 ] ); 66 | 67 | $Method = 68 | [ 69 | 'name' => $ApiMethod, 70 | 'version' => $ApiVersion, 71 | 'httpmethod' => $HttpMethod, 72 | 'parameters' => [], 73 | ]; 74 | 75 | $Section = explode( '', $Section, 2 ); // Can be multiple tables (like response) 76 | 77 | preg_match_all( '/(.*?)<\/tr>/s', $Section[ 0 ], $Matches ); 78 | 79 | foreach( $Matches[ 1 ] as $ParamsHtml ) 80 | { 81 | if( preg_match_all( '/(.*?)<\/td>/s', $ParamsHtml, $ParamsMatches ) === 0 ) 82 | { 83 | continue; 84 | } 85 | 86 | $Method[ 'parameters' ][] = 87 | [ 88 | 'name' => trim( strip_tags( $ParamsMatches[ 1 ][ 0 ] ) ), 89 | 'type' => trim( strip_tags( $ParamsMatches[ 1 ][ 1 ] ) ), 90 | 'optional' => $ParamsMatches[ 1 ][ 2 ] !== '✔', 91 | 'description' => trim( strip_tags( $ParamsMatches[ 1 ][ 3 ] ) ), 92 | ]; 93 | } 94 | 95 | if( !isset( $Methods[ $ApiService ] ) ) 96 | { 97 | $Methods[ $ApiService ] = []; 98 | } 99 | 100 | $Methods[ $ApiService ][] = $Method; 101 | } 102 | } 103 | 104 | $FoundServices = []; 105 | 106 | foreach( $Methods as $ServiceName => $FoundMethods ) 107 | { 108 | $FoundServices[] = 109 | [ 110 | 'name' => $ServiceName, 111 | 'methods' => $FoundMethods, 112 | ]; 113 | } 114 | 115 | file_put_contents( 116 | __DIR__ . DIRECTORY_SEPARATOR . 'api_from_docs.json', 117 | json_encode( $FoundServices, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . PHP_EOL 118 | ); 119 | -------------------------------------------------------------------------------- /generate_api_from_protos.php: -------------------------------------------------------------------------------- 1 | isFile() ) 45 | { 46 | echo $fileInfo . ' does not exist' . PHP_EOL; 47 | return ''; 48 | } 49 | 50 | $proto = file_get_contents( $fileInfo ); 51 | 52 | if( $proto === false ) 53 | { 54 | throw new Exception( "Failed to read $fileInfo" ); 55 | } 56 | 57 | return preg_replace_callback( '/^import "(?.+\.proto)";/m', function( array $matches ) use ( $fileInfo ) : string 58 | { 59 | $path = $fileInfo->getPath() . '/'; 60 | 61 | if( str_starts_with( $matches[ 'file' ], 'google/' ) ) 62 | { 63 | $path .= '../' . $matches[ 'file' ]; 64 | } 65 | else if( str_starts_with( $matches[ 'file' ], 'steammessages' ) && str_contains( $path, 'webui' ) ) 66 | { 67 | $path .= '../steam/' . $matches[ 'file' ]; 68 | } 69 | else 70 | { 71 | $path .= $matches[ 'file' ]; 72 | } 73 | 74 | return ProcessFile( new SplFileInfo( $path ) ); 75 | }, $proto ) ?? ''; 76 | } 77 | 78 | function ParseTypeToRequestParameters( string $request, string $proto, int $level = 0 ) 79 | { 80 | if( !preg_match( "/message " . $request . " {(.+?)^}/ms", $proto, $message ) ) 81 | { 82 | echo $request . ' not found' . PHP_EOL; 83 | return []; 84 | } 85 | 86 | $parameters = []; 87 | 88 | if( preg_match_all( 89 | "/^\t(?optional|repeated|required) (?[\w\.]+) (?\w+).+?(?:\(description\) = \"(?.+?)\".+?)?;$/m", 90 | $message[ 1 ] ?? '', 91 | $fields 92 | ) > 0 ) 93 | { 94 | for( $y = 0; $y < count( $fields[ 0 ] ); $y++ ) 95 | { 96 | $name = $fields[ 'name' ][ $y ]; 97 | $extra = null; 98 | 99 | if( strpos( $fields[ 'type' ][ $y ], '.' ) !== false ) 100 | { 101 | $type = substr( $fields[ 'type' ][ $y ], 1 ); 102 | // TODO: Support inner types with dots in middle 103 | 104 | if( $type !== $request && $level < 5 ) 105 | { 106 | $extra = ParseTypeToRequestParameters( $type, $proto, $level + 1 ); 107 | } 108 | } 109 | else 110 | { 111 | $type = $fields[ 'type' ][ $y ]; 112 | } 113 | 114 | if( $fields[ 'rule' ][ $y ] === 'repeated' ) 115 | { 116 | $name .= '[0]'; 117 | $type .= '[]'; 118 | } 119 | 120 | if( !empty( $parameters[ $name ] ) ) 121 | { 122 | if( empty( $parameters[ $name ][ 'description' ] ) ) 123 | { 124 | $parameters[ $name ][ 'description' ] = trim( $fields[ 'description' ][ $y ] ); 125 | } 126 | 127 | continue; 128 | } 129 | 130 | $parameters[ $name ] = 131 | [ 132 | 'name' => $name, 133 | 'type' => $type, 134 | 'optional' => true, 135 | 'description' => trim( $fields[ 'description' ][ $y ] ), 136 | ]; 137 | 138 | if( !empty( $extra ) ) 139 | { 140 | $parameters[ $name ][ 'extra' ] = array_values( $extra ); 141 | } 142 | } 143 | } 144 | 145 | return $parameters; 146 | } 147 | 148 | foreach( $allProtos as $fileInfo ) 149 | { 150 | if( strpos( $fileInfo, '.git' ) !== false || $fileInfo->getExtension() !== 'proto' ) 151 | { 152 | continue; 153 | } 154 | 155 | $proto = ProcessFile( $fileInfo ); 156 | 157 | preg_match_all( "/service (.+?)\s{/", $proto, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ); 158 | 159 | $matches[] = 160 | [ 161 | [], 162 | [ 163 | '', 164 | strlen( $proto ) 165 | ] 166 | ]; 167 | 168 | for( $i = count( $matches ) - 1; $i > 0; $i-- ) 169 | { 170 | $serviceName = $matches[ $i - 1 ][ 1 ][ 0 ]; 171 | 172 | if( $serviceName === 'RemoteClientSteamClient' ) 173 | { 174 | $serviceName = 'RemoteClient'; 175 | } 176 | 177 | $serviceName = 'I' . $serviceName . 'Service'; 178 | 179 | $service = substr( $proto, $matches[ $i - 1 ][ 1 ][ 1 ], $matches[ $i ][ 1 ][ 1 ] - $matches[ $i - 1 ][ 1 ][ 1 ] ); 180 | 181 | preg_match_all( "/rpc (.+?) \(\.?(.+?)\) returns \(\.?(?:.+?)\)\s*(?:;|{}|\{\s*option \(method_description\) = \"(.+?)\";)$/m", $service, $rpcs ); 182 | 183 | $generatedMethods = []; 184 | 185 | for( $x = 0; $x < count( $rpcs[ 0 ] ); $x++ ) 186 | { 187 | $methodName = $rpcs[ 1 ][ $x ]; 188 | $request = $rpcs[ 2 ][ $x ]; 189 | 190 | $methodPath = $serviceName . '/' . $methodName; 191 | 192 | if( empty( $methodDescriptions[ $methodPath ] ) ) 193 | { 194 | $methodDescriptions[ $methodPath ] = trim( $rpcs[ 3 ][ $x ] ); 195 | } 196 | 197 | if( empty( $methodParameters[ $methodPath ] ) ) 198 | { 199 | $methodParameters[ $methodPath ] = 200 | [ 201 | 'key' => 202 | [ 203 | "name" => "key", 204 | "type" => "string", 205 | "optional" => false, 206 | "description" => "Access key", 207 | ] 208 | ]; 209 | } 210 | 211 | $methodParameters[ $methodPath ] += ParseTypeToRequestParameters( $request, $proto ); 212 | 213 | $generatedMethods[ $methodName ] = true; 214 | } 215 | 216 | if( empty( $generatedServices[ $serviceName ] ) ) 217 | { 218 | $generatedServices[ $serviceName ] = []; 219 | } 220 | 221 | $generatedServices[ $serviceName ] += $generatedMethods; 222 | } 223 | } 224 | 225 | $c = curl_init( ); 226 | 227 | curl_setopt_array( $c, [ 228 | CURLOPT_USERAGENT => '', 229 | CURLOPT_RETURNTRANSFER => true, 230 | CURLOPT_TIMEOUT => 5, 231 | CURLOPT_CONNECTTIMEOUT => 5, 232 | CURLOPT_URL => 'https://api.steampowered.com/ISteamWebAPIUtil/GetSupportedAPIList/v1/?format=json&key=' . $PublisherApiKey, 233 | ] ); 234 | 235 | $knownServices = []; 236 | 237 | $response = curl_exec( $c ); 238 | $response = json_decode( $response, true, 512, JSON_THROW_ON_ERROR ); 239 | 240 | foreach( $response[ 'apilist' ][ 'interfaces' ] as $service ) 241 | { 242 | foreach( $service[ 'methods' ] as $method ) 243 | { 244 | $knownServices[ $service[ 'name' ] . '/' . $method[ 'name' ] ] = true; 245 | } 246 | } 247 | 248 | $notFound = "\n\n404 Not Found\n\n\n

Not Found

\n\n"; 249 | $notFoundNoInterface = "Not Found

Not Found

Interface '%s' not found"; 250 | $notFoundMethodInInterface = "Not Found

Not Found

Method '%s' not found in interface '%s'"; 251 | $mustBePost = "Method Not Allowed

Method Not Allowed

This API must be called with a HTTP POST request"; 252 | 253 | $foundServices = []; 254 | 255 | ksort( $generatedServices ); 256 | 257 | foreach( $generatedServices as $serviceName => $methods ) 258 | { 259 | ksort( $methods ); 260 | 261 | $foundMethods = []; 262 | 263 | foreach( $methods as $methodName => $trash ) 264 | { 265 | $path = $serviceName . '/' . $methodName; 266 | 267 | if( isset( $knownServices[ $path ] ) ) 268 | { 269 | continue; 270 | } 271 | 272 | echo 'Checking ' . $path . '...'; 273 | 274 | curl_setopt( $c, CURLOPT_URL, "https://api.steampowered.com/{$path}/v1/" ); 275 | $response = curl_exec( $c ); 276 | 277 | if( $response === $notFound ) 278 | { 279 | echo " \033[1;31mnothing\033[0m" . PHP_EOL; 280 | continue; 281 | } 282 | 283 | if( $response === sprintf( $notFoundNoInterface, $serviceName ) ) 284 | { 285 | echo " \033[1;33minterface not found\033[0m" . PHP_EOL; 286 | continue; 287 | } 288 | 289 | if( $response === sprintf( $notFoundMethodInInterface, $methodName, $serviceName ) ) 290 | { 291 | echo " \033[1;31mmethod not found\033[0m" . PHP_EOL; 292 | continue; 293 | } 294 | 295 | echo " \033[0;32mFOUND!\033[0m" . PHP_EOL; 296 | 297 | $method = 298 | [ 299 | 'name' => $methodName, 300 | 'version' => 1, 301 | ]; 302 | 303 | if( $response === $mustBePost ) 304 | { 305 | $method[ 'httpmethod' ] = 'POST'; 306 | } 307 | 308 | if( !empty( $methodDescriptions[ $path ] ) ) 309 | { 310 | $method[ 'description' ] = $methodDescriptions[ $path ]; 311 | } 312 | 313 | if( !empty( $methodParameters[ $path ] ) ) 314 | { 315 | $method[ 'parameters' ] = array_values( $methodParameters[ $path ] ); 316 | } 317 | 318 | $foundMethods[] = $method; 319 | } 320 | 321 | if( empty( $foundMethods ) ) 322 | { 323 | continue; 324 | } 325 | 326 | $foundServices[] = 327 | [ 328 | 'name' => $serviceName, 329 | 'methods' => $foundMethods, 330 | ]; 331 | } 332 | 333 | curl_close( $c ); 334 | 335 | file_put_contents( 336 | __DIR__ . DIRECTORY_SEPARATOR . 'api_from_protos.json', 337 | json_encode( $foundServices, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . PHP_EOL 338 | ); 339 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SteamWebAPIDocumentation", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "@biomejs/biome": "^2.0.0-beta.1", 9 | "@chialab/esbuild-plugin-html": "^0.18.2", 10 | "@vue/tsconfig": "^0.7.0", 11 | "bootstrap": "^5.2.2", 12 | "esbuild": "^0.25.0", 13 | "esbuild-plugin-vue3": "^0.4.2", 14 | "htmlnano": "^2.1.1", 15 | "typescript": "^5.2.2", 16 | "vue": "^3.2.40" 17 | } 18 | }, 19 | "node_modules/@babel/code-frame": { 20 | "version": "7.26.2", 21 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", 22 | "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", 23 | "dev": true, 24 | "license": "MIT", 25 | "dependencies": { 26 | "@babel/helper-validator-identifier": "^7.25.9", 27 | "js-tokens": "^4.0.0", 28 | "picocolors": "^1.0.0" 29 | }, 30 | "engines": { 31 | "node": ">=6.9.0" 32 | } 33 | }, 34 | "node_modules/@babel/helper-string-parser": { 35 | "version": "7.25.9", 36 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", 37 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", 38 | "dev": true, 39 | "license": "MIT", 40 | "engines": { 41 | "node": ">=6.9.0" 42 | } 43 | }, 44 | "node_modules/@babel/helper-validator-identifier": { 45 | "version": "7.25.9", 46 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", 47 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", 48 | "dev": true, 49 | "license": "MIT", 50 | "engines": { 51 | "node": ">=6.9.0" 52 | } 53 | }, 54 | "node_modules/@babel/parser": { 55 | "version": "7.26.9", 56 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", 57 | "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", 58 | "dev": true, 59 | "license": "MIT", 60 | "dependencies": { 61 | "@babel/types": "^7.26.9" 62 | }, 63 | "bin": { 64 | "parser": "bin/babel-parser.js" 65 | }, 66 | "engines": { 67 | "node": ">=6.0.0" 68 | } 69 | }, 70 | "node_modules/@babel/types": { 71 | "version": "7.26.9", 72 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", 73 | "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", 74 | "dev": true, 75 | "license": "MIT", 76 | "dependencies": { 77 | "@babel/helper-string-parser": "^7.25.9", 78 | "@babel/helper-validator-identifier": "^7.25.9" 79 | }, 80 | "engines": { 81 | "node": ">=6.9.0" 82 | } 83 | }, 84 | "node_modules/@biomejs/biome": { 85 | "version": "2.0.0-beta.1", 86 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.0.0-beta.1.tgz", 87 | "integrity": "sha512-MqRoy9CbTkrS45zW+S4u8p4kQUIFx0mGUWi789W1R3b1kXYIudEqsTKgXKtTGsI0kWOlvnjuKqwTrabjaGchhQ==", 88 | "dev": true, 89 | "license": "MIT OR Apache-2.0", 90 | "bin": { 91 | "biome": "bin/biome" 92 | }, 93 | "engines": { 94 | "node": ">=14.21.3" 95 | }, 96 | "funding": { 97 | "type": "opencollective", 98 | "url": "https://opencollective.com/biome" 99 | }, 100 | "optionalDependencies": { 101 | "@biomejs/cli-darwin-arm64": "2.0.0-beta.1", 102 | "@biomejs/cli-darwin-x64": "2.0.0-beta.1", 103 | "@biomejs/cli-linux-arm64": "2.0.0-beta.1", 104 | "@biomejs/cli-linux-arm64-musl": "2.0.0-beta.1", 105 | "@biomejs/cli-linux-x64": "2.0.0-beta.1", 106 | "@biomejs/cli-linux-x64-musl": "2.0.0-beta.1", 107 | "@biomejs/cli-win32-arm64": "2.0.0-beta.1", 108 | "@biomejs/cli-win32-x64": "2.0.0-beta.1" 109 | } 110 | }, 111 | "node_modules/@biomejs/cli-darwin-arm64": { 112 | "version": "2.0.0-beta.1", 113 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.1.tgz", 114 | "integrity": "sha512-RaGmpNLl5NFooXaoCwvgvcuU6Am/rMZ3R48pQeCVxjrCcz1BIlKLTai5UosiedazW7JbXAvgXdSNizYG7ITlAQ==", 115 | "cpu": [ 116 | "arm64" 117 | ], 118 | "dev": true, 119 | "license": "MIT OR Apache-2.0", 120 | "optional": true, 121 | "os": [ 122 | "darwin" 123 | ], 124 | "engines": { 125 | "node": ">=14.21.3" 126 | } 127 | }, 128 | "node_modules/@biomejs/cli-darwin-x64": { 129 | "version": "2.0.0-beta.1", 130 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.0.0-beta.1.tgz", 131 | "integrity": "sha512-sTzSshkne7HKZFNfiIhmAji7gjtCBXvkTujZELCZWIZC7oj1Tjw/gvAzbdFj2UyHd5/i90pND4ybFOLQZm9gpg==", 132 | "cpu": [ 133 | "x64" 134 | ], 135 | "dev": true, 136 | "license": "MIT OR Apache-2.0", 137 | "optional": true, 138 | "os": [ 139 | "darwin" 140 | ], 141 | "engines": { 142 | "node": ">=14.21.3" 143 | } 144 | }, 145 | "node_modules/@biomejs/cli-linux-arm64": { 146 | "version": "2.0.0-beta.1", 147 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.0.0-beta.1.tgz", 148 | "integrity": "sha512-bxce2O4nooBmp20Ey0+IFIZyy/b0RVnciIQk9euCfAi9evq7SvFtMBYo3YUZej0KIvrau5H7tJk5OqmRJk2l+g==", 149 | "cpu": [ 150 | "arm64" 151 | ], 152 | "dev": true, 153 | "license": "MIT OR Apache-2.0", 154 | "optional": true, 155 | "os": [ 156 | "linux" 157 | ], 158 | "engines": { 159 | "node": ">=14.21.3" 160 | } 161 | }, 162 | "node_modules/@biomejs/cli-linux-arm64-musl": { 163 | "version": "2.0.0-beta.1", 164 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-beta.1.tgz", 165 | "integrity": "sha512-0MPUKzz9uBBxAYSJ+OlFi4+yGwiRcZeFqq39H0MxXCQ9MMpKJFH2Ek72fw8sXwG7Prn7EsW/3u1b7najyn1XGQ==", 166 | "cpu": [ 167 | "arm64" 168 | ], 169 | "dev": true, 170 | "license": "MIT OR Apache-2.0", 171 | "optional": true, 172 | "os": [ 173 | "linux" 174 | ], 175 | "engines": { 176 | "node": ">=14.21.3" 177 | } 178 | }, 179 | "node_modules/@biomejs/cli-linux-x64": { 180 | "version": "2.0.0-beta.1", 181 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.0.0-beta.1.tgz", 182 | "integrity": "sha512-6P/AtJv4hOH8mu8ez0c4UInUpiet9NEoF25+O7OPyb4w6ZHJMp2qzvayJS7TKrTQzE5KUvSiNsACGRz34DzUkg==", 183 | "cpu": [ 184 | "x64" 185 | ], 186 | "dev": true, 187 | "license": "MIT OR Apache-2.0", 188 | "optional": true, 189 | "os": [ 190 | "linux" 191 | ], 192 | "engines": { 193 | "node": ">=14.21.3" 194 | } 195 | }, 196 | "node_modules/@biomejs/cli-linux-x64-musl": { 197 | "version": "2.0.0-beta.1", 198 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-beta.1.tgz", 199 | "integrity": "sha512-dFvisnP1hFpVILNw0PZfs8piBwe8+aykO04Tb/4AJDVVzKkGgJfwSefwo4jqzO/Wk/Zruvhcp1nKbjgRXM+vDg==", 200 | "cpu": [ 201 | "x64" 202 | ], 203 | "dev": true, 204 | "license": "MIT OR Apache-2.0", 205 | "optional": true, 206 | "os": [ 207 | "linux" 208 | ], 209 | "engines": { 210 | "node": ">=14.21.3" 211 | } 212 | }, 213 | "node_modules/@biomejs/cli-win32-arm64": { 214 | "version": "2.0.0-beta.1", 215 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.0.0-beta.1.tgz", 216 | "integrity": "sha512-0C9YSqWHf2cJGnjKDbLi49xv6H9IfqbDsFav7X557PqwY64O6IKWqcmZzi/PkDFHjQM9opU6uhKapeGKGDxziQ==", 217 | "cpu": [ 218 | "arm64" 219 | ], 220 | "dev": true, 221 | "license": "MIT OR Apache-2.0", 222 | "optional": true, 223 | "os": [ 224 | "win32" 225 | ], 226 | "engines": { 227 | "node": ">=14.21.3" 228 | } 229 | }, 230 | "node_modules/@biomejs/cli-win32-x64": { 231 | "version": "2.0.0-beta.1", 232 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.0.0-beta.1.tgz", 233 | "integrity": "sha512-o8W6+DX0YRjt1kS8Y3ismq6EkjwiVDv7X0TEpfnFywoVG8HoJ7G7/m9r8LM1yE46WI3maPH2A0MoVpQ1ZNG++A==", 234 | "cpu": [ 235 | "x64" 236 | ], 237 | "dev": true, 238 | "license": "MIT OR Apache-2.0", 239 | "optional": true, 240 | "os": [ 241 | "win32" 242 | ], 243 | "engines": { 244 | "node": ">=14.21.3" 245 | } 246 | }, 247 | "node_modules/@chialab/esbuild-plugin-html": { 248 | "version": "0.18.2", 249 | "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-html/-/esbuild-plugin-html-0.18.2.tgz", 250 | "integrity": "sha512-8dBqIZIL1tkLYyMko+HY8b80QoDOIGlydbIQO8ALLv4e59UO0MGQjFG3RCD0V5sMbpjeNrJ3MCKz29wGL16Wrg==", 251 | "dev": true, 252 | "license": "MIT", 253 | "dependencies": { 254 | "@chialab/esbuild-rna": "^0.18.1" 255 | }, 256 | "engines": { 257 | "node": ">=18" 258 | }, 259 | "peerDependencies": { 260 | "htmlnano": "^2.0.0" 261 | }, 262 | "peerDependenciesMeta": { 263 | "htmlnano": { 264 | "optional": true 265 | } 266 | } 267 | }, 268 | "node_modules/@chialab/esbuild-rna": { 269 | "version": "0.18.2", 270 | "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.18.2.tgz", 271 | "integrity": "sha512-ckzskez7bxstVQ4c5cxbx0DRP2teldzrcSGQl2KPh1VJGdO2ZmRrb6vNkBBD5K3dx9tgTyvskWp4dV+Fbg07Ag==", 272 | "dev": true, 273 | "license": "MIT", 274 | "dependencies": { 275 | "@chialab/estransform": "^0.18.0", 276 | "@chialab/node-resolve": "^0.18.0" 277 | }, 278 | "engines": { 279 | "node": ">=18" 280 | } 281 | }, 282 | "node_modules/@chialab/estransform": { 283 | "version": "0.18.1", 284 | "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.18.1.tgz", 285 | "integrity": "sha512-W/WmjpQL2hndD0/XfR0FcPBAUj+aLNeoAVehOjV/Q9bSnioz0GVSAXXhzp59S33ZynxJBBfn8DNiMTVNJmk4Aw==", 286 | "dev": true, 287 | "license": "MIT", 288 | "dependencies": { 289 | "@parcel/source-map": "^2.0.0" 290 | }, 291 | "engines": { 292 | "node": ">=18" 293 | } 294 | }, 295 | "node_modules/@chialab/node-resolve": { 296 | "version": "0.18.0", 297 | "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.18.0.tgz", 298 | "integrity": "sha512-eV1m70Qn9pLY9xwFmZ2FlcOzwiaUywsJ7NB/ud8VB7DouvCQtIHkQ3Om7uPX0ojXGEG1LCyO96kZkvbNTxNu0Q==", 299 | "dev": true, 300 | "license": "MIT", 301 | "engines": { 302 | "node": ">=18" 303 | } 304 | }, 305 | "node_modules/@esbuild/aix-ppc64": { 306 | "version": "0.25.1", 307 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", 308 | "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", 309 | "cpu": [ 310 | "ppc64" 311 | ], 312 | "dev": true, 313 | "license": "MIT", 314 | "optional": true, 315 | "os": [ 316 | "aix" 317 | ], 318 | "engines": { 319 | "node": ">=18" 320 | } 321 | }, 322 | "node_modules/@esbuild/android-arm": { 323 | "version": "0.25.1", 324 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", 325 | "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", 326 | "cpu": [ 327 | "arm" 328 | ], 329 | "dev": true, 330 | "license": "MIT", 331 | "optional": true, 332 | "os": [ 333 | "android" 334 | ], 335 | "engines": { 336 | "node": ">=18" 337 | } 338 | }, 339 | "node_modules/@esbuild/android-arm64": { 340 | "version": "0.25.1", 341 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", 342 | "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", 343 | "cpu": [ 344 | "arm64" 345 | ], 346 | "dev": true, 347 | "license": "MIT", 348 | "optional": true, 349 | "os": [ 350 | "android" 351 | ], 352 | "engines": { 353 | "node": ">=18" 354 | } 355 | }, 356 | "node_modules/@esbuild/android-x64": { 357 | "version": "0.25.1", 358 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", 359 | "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", 360 | "cpu": [ 361 | "x64" 362 | ], 363 | "dev": true, 364 | "license": "MIT", 365 | "optional": true, 366 | "os": [ 367 | "android" 368 | ], 369 | "engines": { 370 | "node": ">=18" 371 | } 372 | }, 373 | "node_modules/@esbuild/darwin-arm64": { 374 | "version": "0.25.1", 375 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", 376 | "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", 377 | "cpu": [ 378 | "arm64" 379 | ], 380 | "dev": true, 381 | "license": "MIT", 382 | "optional": true, 383 | "os": [ 384 | "darwin" 385 | ], 386 | "engines": { 387 | "node": ">=18" 388 | } 389 | }, 390 | "node_modules/@esbuild/darwin-x64": { 391 | "version": "0.25.1", 392 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", 393 | "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", 394 | "cpu": [ 395 | "x64" 396 | ], 397 | "dev": true, 398 | "license": "MIT", 399 | "optional": true, 400 | "os": [ 401 | "darwin" 402 | ], 403 | "engines": { 404 | "node": ">=18" 405 | } 406 | }, 407 | "node_modules/@esbuild/freebsd-arm64": { 408 | "version": "0.25.1", 409 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", 410 | "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", 411 | "cpu": [ 412 | "arm64" 413 | ], 414 | "dev": true, 415 | "license": "MIT", 416 | "optional": true, 417 | "os": [ 418 | "freebsd" 419 | ], 420 | "engines": { 421 | "node": ">=18" 422 | } 423 | }, 424 | "node_modules/@esbuild/freebsd-x64": { 425 | "version": "0.25.1", 426 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", 427 | "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", 428 | "cpu": [ 429 | "x64" 430 | ], 431 | "dev": true, 432 | "license": "MIT", 433 | "optional": true, 434 | "os": [ 435 | "freebsd" 436 | ], 437 | "engines": { 438 | "node": ">=18" 439 | } 440 | }, 441 | "node_modules/@esbuild/linux-arm": { 442 | "version": "0.25.1", 443 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", 444 | "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", 445 | "cpu": [ 446 | "arm" 447 | ], 448 | "dev": true, 449 | "license": "MIT", 450 | "optional": true, 451 | "os": [ 452 | "linux" 453 | ], 454 | "engines": { 455 | "node": ">=18" 456 | } 457 | }, 458 | "node_modules/@esbuild/linux-arm64": { 459 | "version": "0.25.1", 460 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", 461 | "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", 462 | "cpu": [ 463 | "arm64" 464 | ], 465 | "dev": true, 466 | "license": "MIT", 467 | "optional": true, 468 | "os": [ 469 | "linux" 470 | ], 471 | "engines": { 472 | "node": ">=18" 473 | } 474 | }, 475 | "node_modules/@esbuild/linux-ia32": { 476 | "version": "0.25.1", 477 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", 478 | "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", 479 | "cpu": [ 480 | "ia32" 481 | ], 482 | "dev": true, 483 | "license": "MIT", 484 | "optional": true, 485 | "os": [ 486 | "linux" 487 | ], 488 | "engines": { 489 | "node": ">=18" 490 | } 491 | }, 492 | "node_modules/@esbuild/linux-loong64": { 493 | "version": "0.25.1", 494 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", 495 | "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", 496 | "cpu": [ 497 | "loong64" 498 | ], 499 | "dev": true, 500 | "license": "MIT", 501 | "optional": true, 502 | "os": [ 503 | "linux" 504 | ], 505 | "engines": { 506 | "node": ">=18" 507 | } 508 | }, 509 | "node_modules/@esbuild/linux-mips64el": { 510 | "version": "0.25.1", 511 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", 512 | "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", 513 | "cpu": [ 514 | "mips64el" 515 | ], 516 | "dev": true, 517 | "license": "MIT", 518 | "optional": true, 519 | "os": [ 520 | "linux" 521 | ], 522 | "engines": { 523 | "node": ">=18" 524 | } 525 | }, 526 | "node_modules/@esbuild/linux-ppc64": { 527 | "version": "0.25.1", 528 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", 529 | "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", 530 | "cpu": [ 531 | "ppc64" 532 | ], 533 | "dev": true, 534 | "license": "MIT", 535 | "optional": true, 536 | "os": [ 537 | "linux" 538 | ], 539 | "engines": { 540 | "node": ">=18" 541 | } 542 | }, 543 | "node_modules/@esbuild/linux-riscv64": { 544 | "version": "0.25.1", 545 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", 546 | "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", 547 | "cpu": [ 548 | "riscv64" 549 | ], 550 | "dev": true, 551 | "license": "MIT", 552 | "optional": true, 553 | "os": [ 554 | "linux" 555 | ], 556 | "engines": { 557 | "node": ">=18" 558 | } 559 | }, 560 | "node_modules/@esbuild/linux-s390x": { 561 | "version": "0.25.1", 562 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", 563 | "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", 564 | "cpu": [ 565 | "s390x" 566 | ], 567 | "dev": true, 568 | "license": "MIT", 569 | "optional": true, 570 | "os": [ 571 | "linux" 572 | ], 573 | "engines": { 574 | "node": ">=18" 575 | } 576 | }, 577 | "node_modules/@esbuild/linux-x64": { 578 | "version": "0.25.1", 579 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", 580 | "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", 581 | "cpu": [ 582 | "x64" 583 | ], 584 | "dev": true, 585 | "license": "MIT", 586 | "optional": true, 587 | "os": [ 588 | "linux" 589 | ], 590 | "engines": { 591 | "node": ">=18" 592 | } 593 | }, 594 | "node_modules/@esbuild/netbsd-arm64": { 595 | "version": "0.25.1", 596 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", 597 | "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", 598 | "cpu": [ 599 | "arm64" 600 | ], 601 | "dev": true, 602 | "license": "MIT", 603 | "optional": true, 604 | "os": [ 605 | "netbsd" 606 | ], 607 | "engines": { 608 | "node": ">=18" 609 | } 610 | }, 611 | "node_modules/@esbuild/netbsd-x64": { 612 | "version": "0.25.1", 613 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", 614 | "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", 615 | "cpu": [ 616 | "x64" 617 | ], 618 | "dev": true, 619 | "license": "MIT", 620 | "optional": true, 621 | "os": [ 622 | "netbsd" 623 | ], 624 | "engines": { 625 | "node": ">=18" 626 | } 627 | }, 628 | "node_modules/@esbuild/openbsd-arm64": { 629 | "version": "0.25.1", 630 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", 631 | "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", 632 | "cpu": [ 633 | "arm64" 634 | ], 635 | "dev": true, 636 | "license": "MIT", 637 | "optional": true, 638 | "os": [ 639 | "openbsd" 640 | ], 641 | "engines": { 642 | "node": ">=18" 643 | } 644 | }, 645 | "node_modules/@esbuild/openbsd-x64": { 646 | "version": "0.25.1", 647 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", 648 | "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", 649 | "cpu": [ 650 | "x64" 651 | ], 652 | "dev": true, 653 | "license": "MIT", 654 | "optional": true, 655 | "os": [ 656 | "openbsd" 657 | ], 658 | "engines": { 659 | "node": ">=18" 660 | } 661 | }, 662 | "node_modules/@esbuild/sunos-x64": { 663 | "version": "0.25.1", 664 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", 665 | "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", 666 | "cpu": [ 667 | "x64" 668 | ], 669 | "dev": true, 670 | "license": "MIT", 671 | "optional": true, 672 | "os": [ 673 | "sunos" 674 | ], 675 | "engines": { 676 | "node": ">=18" 677 | } 678 | }, 679 | "node_modules/@esbuild/win32-arm64": { 680 | "version": "0.25.1", 681 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", 682 | "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", 683 | "cpu": [ 684 | "arm64" 685 | ], 686 | "dev": true, 687 | "license": "MIT", 688 | "optional": true, 689 | "os": [ 690 | "win32" 691 | ], 692 | "engines": { 693 | "node": ">=18" 694 | } 695 | }, 696 | "node_modules/@esbuild/win32-ia32": { 697 | "version": "0.25.1", 698 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", 699 | "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", 700 | "cpu": [ 701 | "ia32" 702 | ], 703 | "dev": true, 704 | "license": "MIT", 705 | "optional": true, 706 | "os": [ 707 | "win32" 708 | ], 709 | "engines": { 710 | "node": ">=18" 711 | } 712 | }, 713 | "node_modules/@esbuild/win32-x64": { 714 | "version": "0.25.1", 715 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", 716 | "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", 717 | "cpu": [ 718 | "x64" 719 | ], 720 | "dev": true, 721 | "license": "MIT", 722 | "optional": true, 723 | "os": [ 724 | "win32" 725 | ], 726 | "engines": { 727 | "node": ">=18" 728 | } 729 | }, 730 | "node_modules/@jridgewell/sourcemap-codec": { 731 | "version": "1.5.0", 732 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 733 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 734 | "dev": true, 735 | "license": "MIT" 736 | }, 737 | "node_modules/@parcel/source-map": { 738 | "version": "2.1.1", 739 | "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", 740 | "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", 741 | "dev": true, 742 | "license": "MIT", 743 | "dependencies": { 744 | "detect-libc": "^1.0.3" 745 | }, 746 | "engines": { 747 | "node": "^12.18.3 || >=14" 748 | } 749 | }, 750 | "node_modules/@popperjs/core": { 751 | "version": "2.11.8", 752 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", 753 | "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", 754 | "dev": true, 755 | "license": "MIT", 756 | "peer": true, 757 | "funding": { 758 | "type": "opencollective", 759 | "url": "https://opencollective.com/popperjs" 760 | } 761 | }, 762 | "node_modules/@vue/compiler-core": { 763 | "version": "3.5.13", 764 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", 765 | "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", 766 | "dev": true, 767 | "license": "MIT", 768 | "dependencies": { 769 | "@babel/parser": "^7.25.3", 770 | "@vue/shared": "3.5.13", 771 | "entities": "^4.5.0", 772 | "estree-walker": "^2.0.2", 773 | "source-map-js": "^1.2.0" 774 | } 775 | }, 776 | "node_modules/@vue/compiler-dom": { 777 | "version": "3.5.13", 778 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", 779 | "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", 780 | "dev": true, 781 | "license": "MIT", 782 | "dependencies": { 783 | "@vue/compiler-core": "3.5.13", 784 | "@vue/shared": "3.5.13" 785 | } 786 | }, 787 | "node_modules/@vue/compiler-sfc": { 788 | "version": "3.5.13", 789 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", 790 | "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", 791 | "dev": true, 792 | "license": "MIT", 793 | "dependencies": { 794 | "@babel/parser": "^7.25.3", 795 | "@vue/compiler-core": "3.5.13", 796 | "@vue/compiler-dom": "3.5.13", 797 | "@vue/compiler-ssr": "3.5.13", 798 | "@vue/shared": "3.5.13", 799 | "estree-walker": "^2.0.2", 800 | "magic-string": "^0.30.11", 801 | "postcss": "^8.4.48", 802 | "source-map-js": "^1.2.0" 803 | } 804 | }, 805 | "node_modules/@vue/compiler-ssr": { 806 | "version": "3.5.13", 807 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", 808 | "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", 809 | "dev": true, 810 | "license": "MIT", 811 | "dependencies": { 812 | "@vue/compiler-dom": "3.5.13", 813 | "@vue/shared": "3.5.13" 814 | } 815 | }, 816 | "node_modules/@vue/reactivity": { 817 | "version": "3.5.13", 818 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", 819 | "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", 820 | "dev": true, 821 | "license": "MIT", 822 | "dependencies": { 823 | "@vue/shared": "3.5.13" 824 | } 825 | }, 826 | "node_modules/@vue/runtime-core": { 827 | "version": "3.5.13", 828 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", 829 | "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", 830 | "dev": true, 831 | "license": "MIT", 832 | "dependencies": { 833 | "@vue/reactivity": "3.5.13", 834 | "@vue/shared": "3.5.13" 835 | } 836 | }, 837 | "node_modules/@vue/runtime-dom": { 838 | "version": "3.5.13", 839 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", 840 | "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", 841 | "dev": true, 842 | "license": "MIT", 843 | "dependencies": { 844 | "@vue/reactivity": "3.5.13", 845 | "@vue/runtime-core": "3.5.13", 846 | "@vue/shared": "3.5.13", 847 | "csstype": "^3.1.3" 848 | } 849 | }, 850 | "node_modules/@vue/server-renderer": { 851 | "version": "3.5.13", 852 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", 853 | "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", 854 | "dev": true, 855 | "license": "MIT", 856 | "dependencies": { 857 | "@vue/compiler-ssr": "3.5.13", 858 | "@vue/shared": "3.5.13" 859 | }, 860 | "peerDependencies": { 861 | "vue": "3.5.13" 862 | } 863 | }, 864 | "node_modules/@vue/shared": { 865 | "version": "3.5.13", 866 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", 867 | "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", 868 | "dev": true, 869 | "license": "MIT" 870 | }, 871 | "node_modules/@vue/tsconfig": { 872 | "version": "0.7.0", 873 | "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", 874 | "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", 875 | "dev": true, 876 | "license": "MIT", 877 | "peerDependencies": { 878 | "typescript": "5.x", 879 | "vue": "^3.4.0" 880 | }, 881 | "peerDependenciesMeta": { 882 | "typescript": { 883 | "optional": true 884 | }, 885 | "vue": { 886 | "optional": true 887 | } 888 | } 889 | }, 890 | "node_modules/argparse": { 891 | "version": "2.0.1", 892 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 893 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 894 | "dev": true, 895 | "license": "Python-2.0" 896 | }, 897 | "node_modules/bootstrap": { 898 | "version": "5.3.3", 899 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", 900 | "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", 901 | "dev": true, 902 | "funding": [ 903 | { 904 | "type": "github", 905 | "url": "https://github.com/sponsors/twbs" 906 | }, 907 | { 908 | "type": "opencollective", 909 | "url": "https://opencollective.com/bootstrap" 910 | } 911 | ], 912 | "license": "MIT", 913 | "peerDependencies": { 914 | "@popperjs/core": "^2.11.8" 915 | } 916 | }, 917 | "node_modules/callsites": { 918 | "version": "3.1.0", 919 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 920 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 921 | "dev": true, 922 | "license": "MIT", 923 | "engines": { 924 | "node": ">=6" 925 | } 926 | }, 927 | "node_modules/cosmiconfig": { 928 | "version": "9.0.0", 929 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", 930 | "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", 931 | "dev": true, 932 | "license": "MIT", 933 | "dependencies": { 934 | "env-paths": "^2.2.1", 935 | "import-fresh": "^3.3.0", 936 | "js-yaml": "^4.1.0", 937 | "parse-json": "^5.2.0" 938 | }, 939 | "engines": { 940 | "node": ">=14" 941 | }, 942 | "funding": { 943 | "url": "https://github.com/sponsors/d-fischer" 944 | }, 945 | "peerDependencies": { 946 | "typescript": ">=4.9.5" 947 | }, 948 | "peerDependenciesMeta": { 949 | "typescript": { 950 | "optional": true 951 | } 952 | } 953 | }, 954 | "node_modules/csstype": { 955 | "version": "3.1.3", 956 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 957 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 958 | "dev": true, 959 | "license": "MIT" 960 | }, 961 | "node_modules/detect-libc": { 962 | "version": "1.0.3", 963 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 964 | "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", 965 | "dev": true, 966 | "license": "Apache-2.0", 967 | "bin": { 968 | "detect-libc": "bin/detect-libc.js" 969 | }, 970 | "engines": { 971 | "node": ">=0.10" 972 | } 973 | }, 974 | "node_modules/dom-serializer": { 975 | "version": "1.4.1", 976 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", 977 | "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", 978 | "dev": true, 979 | "license": "MIT", 980 | "dependencies": { 981 | "domelementtype": "^2.0.1", 982 | "domhandler": "^4.2.0", 983 | "entities": "^2.0.0" 984 | }, 985 | "funding": { 986 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 987 | } 988 | }, 989 | "node_modules/dom-serializer/node_modules/entities": { 990 | "version": "2.2.0", 991 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", 992 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", 993 | "dev": true, 994 | "license": "BSD-2-Clause", 995 | "funding": { 996 | "url": "https://github.com/fb55/entities?sponsor=1" 997 | } 998 | }, 999 | "node_modules/domelementtype": { 1000 | "version": "2.3.0", 1001 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 1002 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 1003 | "dev": true, 1004 | "funding": [ 1005 | { 1006 | "type": "github", 1007 | "url": "https://github.com/sponsors/fb55" 1008 | } 1009 | ], 1010 | "license": "BSD-2-Clause" 1011 | }, 1012 | "node_modules/domhandler": { 1013 | "version": "4.3.1", 1014 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", 1015 | "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", 1016 | "dev": true, 1017 | "license": "BSD-2-Clause", 1018 | "dependencies": { 1019 | "domelementtype": "^2.2.0" 1020 | }, 1021 | "engines": { 1022 | "node": ">= 4" 1023 | }, 1024 | "funding": { 1025 | "url": "https://github.com/fb55/domhandler?sponsor=1" 1026 | } 1027 | }, 1028 | "node_modules/domutils": { 1029 | "version": "2.8.0", 1030 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", 1031 | "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", 1032 | "dev": true, 1033 | "license": "BSD-2-Clause", 1034 | "dependencies": { 1035 | "dom-serializer": "^1.0.1", 1036 | "domelementtype": "^2.2.0", 1037 | "domhandler": "^4.2.0" 1038 | }, 1039 | "funding": { 1040 | "url": "https://github.com/fb55/domutils?sponsor=1" 1041 | } 1042 | }, 1043 | "node_modules/entities": { 1044 | "version": "4.5.0", 1045 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1046 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1047 | "dev": true, 1048 | "license": "BSD-2-Clause", 1049 | "engines": { 1050 | "node": ">=0.12" 1051 | }, 1052 | "funding": { 1053 | "url": "https://github.com/fb55/entities?sponsor=1" 1054 | } 1055 | }, 1056 | "node_modules/env-paths": { 1057 | "version": "2.2.1", 1058 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", 1059 | "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", 1060 | "dev": true, 1061 | "license": "MIT", 1062 | "engines": { 1063 | "node": ">=6" 1064 | } 1065 | }, 1066 | "node_modules/error-ex": { 1067 | "version": "1.3.2", 1068 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 1069 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 1070 | "dev": true, 1071 | "license": "MIT", 1072 | "dependencies": { 1073 | "is-arrayish": "^0.2.1" 1074 | } 1075 | }, 1076 | "node_modules/esbuild": { 1077 | "version": "0.25.1", 1078 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", 1079 | "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", 1080 | "dev": true, 1081 | "hasInstallScript": true, 1082 | "license": "MIT", 1083 | "bin": { 1084 | "esbuild": "bin/esbuild" 1085 | }, 1086 | "engines": { 1087 | "node": ">=18" 1088 | }, 1089 | "optionalDependencies": { 1090 | "@esbuild/aix-ppc64": "0.25.1", 1091 | "@esbuild/android-arm": "0.25.1", 1092 | "@esbuild/android-arm64": "0.25.1", 1093 | "@esbuild/android-x64": "0.25.1", 1094 | "@esbuild/darwin-arm64": "0.25.1", 1095 | "@esbuild/darwin-x64": "0.25.1", 1096 | "@esbuild/freebsd-arm64": "0.25.1", 1097 | "@esbuild/freebsd-x64": "0.25.1", 1098 | "@esbuild/linux-arm": "0.25.1", 1099 | "@esbuild/linux-arm64": "0.25.1", 1100 | "@esbuild/linux-ia32": "0.25.1", 1101 | "@esbuild/linux-loong64": "0.25.1", 1102 | "@esbuild/linux-mips64el": "0.25.1", 1103 | "@esbuild/linux-ppc64": "0.25.1", 1104 | "@esbuild/linux-riscv64": "0.25.1", 1105 | "@esbuild/linux-s390x": "0.25.1", 1106 | "@esbuild/linux-x64": "0.25.1", 1107 | "@esbuild/netbsd-arm64": "0.25.1", 1108 | "@esbuild/netbsd-x64": "0.25.1", 1109 | "@esbuild/openbsd-arm64": "0.25.1", 1110 | "@esbuild/openbsd-x64": "0.25.1", 1111 | "@esbuild/sunos-x64": "0.25.1", 1112 | "@esbuild/win32-arm64": "0.25.1", 1113 | "@esbuild/win32-ia32": "0.25.1", 1114 | "@esbuild/win32-x64": "0.25.1" 1115 | } 1116 | }, 1117 | "node_modules/esbuild-android-64": { 1118 | "version": "0.14.54", 1119 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", 1120 | "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", 1121 | "cpu": [ 1122 | "x64" 1123 | ], 1124 | "dev": true, 1125 | "license": "MIT", 1126 | "optional": true, 1127 | "os": [ 1128 | "android" 1129 | ], 1130 | "engines": { 1131 | "node": ">=12" 1132 | } 1133 | }, 1134 | "node_modules/esbuild-android-arm64": { 1135 | "version": "0.14.54", 1136 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", 1137 | "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", 1138 | "cpu": [ 1139 | "arm64" 1140 | ], 1141 | "dev": true, 1142 | "license": "MIT", 1143 | "optional": true, 1144 | "os": [ 1145 | "android" 1146 | ], 1147 | "engines": { 1148 | "node": ">=12" 1149 | } 1150 | }, 1151 | "node_modules/esbuild-darwin-64": { 1152 | "version": "0.14.54", 1153 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", 1154 | "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", 1155 | "cpu": [ 1156 | "x64" 1157 | ], 1158 | "dev": true, 1159 | "license": "MIT", 1160 | "optional": true, 1161 | "os": [ 1162 | "darwin" 1163 | ], 1164 | "engines": { 1165 | "node": ">=12" 1166 | } 1167 | }, 1168 | "node_modules/esbuild-darwin-arm64": { 1169 | "version": "0.14.54", 1170 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", 1171 | "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", 1172 | "cpu": [ 1173 | "arm64" 1174 | ], 1175 | "dev": true, 1176 | "license": "MIT", 1177 | "optional": true, 1178 | "os": [ 1179 | "darwin" 1180 | ], 1181 | "engines": { 1182 | "node": ">=12" 1183 | } 1184 | }, 1185 | "node_modules/esbuild-freebsd-64": { 1186 | "version": "0.14.54", 1187 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", 1188 | "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", 1189 | "cpu": [ 1190 | "x64" 1191 | ], 1192 | "dev": true, 1193 | "license": "MIT", 1194 | "optional": true, 1195 | "os": [ 1196 | "freebsd" 1197 | ], 1198 | "engines": { 1199 | "node": ">=12" 1200 | } 1201 | }, 1202 | "node_modules/esbuild-freebsd-arm64": { 1203 | "version": "0.14.54", 1204 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", 1205 | "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", 1206 | "cpu": [ 1207 | "arm64" 1208 | ], 1209 | "dev": true, 1210 | "license": "MIT", 1211 | "optional": true, 1212 | "os": [ 1213 | "freebsd" 1214 | ], 1215 | "engines": { 1216 | "node": ">=12" 1217 | } 1218 | }, 1219 | "node_modules/esbuild-linux-32": { 1220 | "version": "0.14.54", 1221 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", 1222 | "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", 1223 | "cpu": [ 1224 | "ia32" 1225 | ], 1226 | "dev": true, 1227 | "license": "MIT", 1228 | "optional": true, 1229 | "os": [ 1230 | "linux" 1231 | ], 1232 | "engines": { 1233 | "node": ">=12" 1234 | } 1235 | }, 1236 | "node_modules/esbuild-linux-64": { 1237 | "version": "0.14.54", 1238 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", 1239 | "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", 1240 | "cpu": [ 1241 | "x64" 1242 | ], 1243 | "dev": true, 1244 | "license": "MIT", 1245 | "optional": true, 1246 | "os": [ 1247 | "linux" 1248 | ], 1249 | "engines": { 1250 | "node": ">=12" 1251 | } 1252 | }, 1253 | "node_modules/esbuild-linux-arm": { 1254 | "version": "0.14.54", 1255 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", 1256 | "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", 1257 | "cpu": [ 1258 | "arm" 1259 | ], 1260 | "dev": true, 1261 | "license": "MIT", 1262 | "optional": true, 1263 | "os": [ 1264 | "linux" 1265 | ], 1266 | "engines": { 1267 | "node": ">=12" 1268 | } 1269 | }, 1270 | "node_modules/esbuild-linux-arm64": { 1271 | "version": "0.14.54", 1272 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", 1273 | "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", 1274 | "cpu": [ 1275 | "arm64" 1276 | ], 1277 | "dev": true, 1278 | "license": "MIT", 1279 | "optional": true, 1280 | "os": [ 1281 | "linux" 1282 | ], 1283 | "engines": { 1284 | "node": ">=12" 1285 | } 1286 | }, 1287 | "node_modules/esbuild-linux-mips64le": { 1288 | "version": "0.14.54", 1289 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", 1290 | "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", 1291 | "cpu": [ 1292 | "mips64el" 1293 | ], 1294 | "dev": true, 1295 | "license": "MIT", 1296 | "optional": true, 1297 | "os": [ 1298 | "linux" 1299 | ], 1300 | "engines": { 1301 | "node": ">=12" 1302 | } 1303 | }, 1304 | "node_modules/esbuild-linux-ppc64le": { 1305 | "version": "0.14.54", 1306 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", 1307 | "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", 1308 | "cpu": [ 1309 | "ppc64" 1310 | ], 1311 | "dev": true, 1312 | "license": "MIT", 1313 | "optional": true, 1314 | "os": [ 1315 | "linux" 1316 | ], 1317 | "engines": { 1318 | "node": ">=12" 1319 | } 1320 | }, 1321 | "node_modules/esbuild-linux-riscv64": { 1322 | "version": "0.14.54", 1323 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", 1324 | "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", 1325 | "cpu": [ 1326 | "riscv64" 1327 | ], 1328 | "dev": true, 1329 | "license": "MIT", 1330 | "optional": true, 1331 | "os": [ 1332 | "linux" 1333 | ], 1334 | "engines": { 1335 | "node": ">=12" 1336 | } 1337 | }, 1338 | "node_modules/esbuild-linux-s390x": { 1339 | "version": "0.14.54", 1340 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", 1341 | "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", 1342 | "cpu": [ 1343 | "s390x" 1344 | ], 1345 | "dev": true, 1346 | "license": "MIT", 1347 | "optional": true, 1348 | "os": [ 1349 | "linux" 1350 | ], 1351 | "engines": { 1352 | "node": ">=12" 1353 | } 1354 | }, 1355 | "node_modules/esbuild-netbsd-64": { 1356 | "version": "0.14.54", 1357 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", 1358 | "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", 1359 | "cpu": [ 1360 | "x64" 1361 | ], 1362 | "dev": true, 1363 | "license": "MIT", 1364 | "optional": true, 1365 | "os": [ 1366 | "netbsd" 1367 | ], 1368 | "engines": { 1369 | "node": ">=12" 1370 | } 1371 | }, 1372 | "node_modules/esbuild-openbsd-64": { 1373 | "version": "0.14.54", 1374 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", 1375 | "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", 1376 | "cpu": [ 1377 | "x64" 1378 | ], 1379 | "dev": true, 1380 | "license": "MIT", 1381 | "optional": true, 1382 | "os": [ 1383 | "openbsd" 1384 | ], 1385 | "engines": { 1386 | "node": ">=12" 1387 | } 1388 | }, 1389 | "node_modules/esbuild-plugin-vue3": { 1390 | "version": "0.4.2", 1391 | "resolved": "https://registry.npmjs.org/esbuild-plugin-vue3/-/esbuild-plugin-vue3-0.4.2.tgz", 1392 | "integrity": "sha512-edaghOAJY+26uIJVywkT0cyUxWu/oi2+dGe2KePyHAJ9y6hAB4fqNnY5SFpZY9G6knERZo4Nykp/YOcKML06rA==", 1393 | "dev": true, 1394 | "license": "MIT", 1395 | "dependencies": { 1396 | "esbuild": "^0.14.8", 1397 | "typescript": "^4.7.4" 1398 | }, 1399 | "peerDependencies": { 1400 | "cheerio": "^1.0.0-rc.10", 1401 | "html-minifier": "^4.0.0", 1402 | "pug": "^3.0.2", 1403 | "sass": "^1.35.2", 1404 | "vue": "^3.4.15" 1405 | }, 1406 | "peerDependenciesMeta": { 1407 | "cheerio": { 1408 | "optional": true 1409 | }, 1410 | "html-minifier": { 1411 | "optional": true 1412 | }, 1413 | "pug": { 1414 | "optional": true 1415 | }, 1416 | "sass": { 1417 | "optional": true 1418 | } 1419 | } 1420 | }, 1421 | "node_modules/esbuild-plugin-vue3/node_modules/@esbuild/linux-loong64": { 1422 | "version": "0.14.54", 1423 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", 1424 | "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", 1425 | "cpu": [ 1426 | "loong64" 1427 | ], 1428 | "dev": true, 1429 | "license": "MIT", 1430 | "optional": true, 1431 | "os": [ 1432 | "linux" 1433 | ], 1434 | "engines": { 1435 | "node": ">=12" 1436 | } 1437 | }, 1438 | "node_modules/esbuild-plugin-vue3/node_modules/esbuild": { 1439 | "version": "0.14.54", 1440 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", 1441 | "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", 1442 | "dev": true, 1443 | "hasInstallScript": true, 1444 | "license": "MIT", 1445 | "bin": { 1446 | "esbuild": "bin/esbuild" 1447 | }, 1448 | "engines": { 1449 | "node": ">=12" 1450 | }, 1451 | "optionalDependencies": { 1452 | "@esbuild/linux-loong64": "0.14.54", 1453 | "esbuild-android-64": "0.14.54", 1454 | "esbuild-android-arm64": "0.14.54", 1455 | "esbuild-darwin-64": "0.14.54", 1456 | "esbuild-darwin-arm64": "0.14.54", 1457 | "esbuild-freebsd-64": "0.14.54", 1458 | "esbuild-freebsd-arm64": "0.14.54", 1459 | "esbuild-linux-32": "0.14.54", 1460 | "esbuild-linux-64": "0.14.54", 1461 | "esbuild-linux-arm": "0.14.54", 1462 | "esbuild-linux-arm64": "0.14.54", 1463 | "esbuild-linux-mips64le": "0.14.54", 1464 | "esbuild-linux-ppc64le": "0.14.54", 1465 | "esbuild-linux-riscv64": "0.14.54", 1466 | "esbuild-linux-s390x": "0.14.54", 1467 | "esbuild-netbsd-64": "0.14.54", 1468 | "esbuild-openbsd-64": "0.14.54", 1469 | "esbuild-sunos-64": "0.14.54", 1470 | "esbuild-windows-32": "0.14.54", 1471 | "esbuild-windows-64": "0.14.54", 1472 | "esbuild-windows-arm64": "0.14.54" 1473 | } 1474 | }, 1475 | "node_modules/esbuild-plugin-vue3/node_modules/typescript": { 1476 | "version": "4.9.5", 1477 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 1478 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 1479 | "dev": true, 1480 | "license": "Apache-2.0", 1481 | "bin": { 1482 | "tsc": "bin/tsc", 1483 | "tsserver": "bin/tsserver" 1484 | }, 1485 | "engines": { 1486 | "node": ">=4.2.0" 1487 | } 1488 | }, 1489 | "node_modules/esbuild-sunos-64": { 1490 | "version": "0.14.54", 1491 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", 1492 | "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", 1493 | "cpu": [ 1494 | "x64" 1495 | ], 1496 | "dev": true, 1497 | "license": "MIT", 1498 | "optional": true, 1499 | "os": [ 1500 | "sunos" 1501 | ], 1502 | "engines": { 1503 | "node": ">=12" 1504 | } 1505 | }, 1506 | "node_modules/esbuild-windows-32": { 1507 | "version": "0.14.54", 1508 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", 1509 | "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", 1510 | "cpu": [ 1511 | "ia32" 1512 | ], 1513 | "dev": true, 1514 | "license": "MIT", 1515 | "optional": true, 1516 | "os": [ 1517 | "win32" 1518 | ], 1519 | "engines": { 1520 | "node": ">=12" 1521 | } 1522 | }, 1523 | "node_modules/esbuild-windows-64": { 1524 | "version": "0.14.54", 1525 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", 1526 | "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", 1527 | "cpu": [ 1528 | "x64" 1529 | ], 1530 | "dev": true, 1531 | "license": "MIT", 1532 | "optional": true, 1533 | "os": [ 1534 | "win32" 1535 | ], 1536 | "engines": { 1537 | "node": ">=12" 1538 | } 1539 | }, 1540 | "node_modules/esbuild-windows-arm64": { 1541 | "version": "0.14.54", 1542 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", 1543 | "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", 1544 | "cpu": [ 1545 | "arm64" 1546 | ], 1547 | "dev": true, 1548 | "license": "MIT", 1549 | "optional": true, 1550 | "os": [ 1551 | "win32" 1552 | ], 1553 | "engines": { 1554 | "node": ">=12" 1555 | } 1556 | }, 1557 | "node_modules/estree-walker": { 1558 | "version": "2.0.2", 1559 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1560 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1561 | "dev": true, 1562 | "license": "MIT" 1563 | }, 1564 | "node_modules/htmlnano": { 1565 | "version": "2.1.1", 1566 | "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.1.tgz", 1567 | "integrity": "sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==", 1568 | "dev": true, 1569 | "license": "MIT", 1570 | "dependencies": { 1571 | "cosmiconfig": "^9.0.0", 1572 | "posthtml": "^0.16.5", 1573 | "timsort": "^0.3.0" 1574 | }, 1575 | "peerDependencies": { 1576 | "cssnano": "^7.0.0", 1577 | "postcss": "^8.3.11", 1578 | "purgecss": "^6.0.0", 1579 | "relateurl": "^0.2.7", 1580 | "srcset": "5.0.1", 1581 | "svgo": "^3.0.2", 1582 | "terser": "^5.10.0", 1583 | "uncss": "^0.17.3" 1584 | }, 1585 | "peerDependenciesMeta": { 1586 | "cssnano": { 1587 | "optional": true 1588 | }, 1589 | "postcss": { 1590 | "optional": true 1591 | }, 1592 | "purgecss": { 1593 | "optional": true 1594 | }, 1595 | "relateurl": { 1596 | "optional": true 1597 | }, 1598 | "srcset": { 1599 | "optional": true 1600 | }, 1601 | "svgo": { 1602 | "optional": true 1603 | }, 1604 | "terser": { 1605 | "optional": true 1606 | }, 1607 | "uncss": { 1608 | "optional": true 1609 | } 1610 | } 1611 | }, 1612 | "node_modules/htmlparser2": { 1613 | "version": "7.2.0", 1614 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", 1615 | "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", 1616 | "dev": true, 1617 | "funding": [ 1618 | "https://github.com/fb55/htmlparser2?sponsor=1", 1619 | { 1620 | "type": "github", 1621 | "url": "https://github.com/sponsors/fb55" 1622 | } 1623 | ], 1624 | "license": "MIT", 1625 | "dependencies": { 1626 | "domelementtype": "^2.0.1", 1627 | "domhandler": "^4.2.2", 1628 | "domutils": "^2.8.0", 1629 | "entities": "^3.0.1" 1630 | } 1631 | }, 1632 | "node_modules/htmlparser2/node_modules/entities": { 1633 | "version": "3.0.1", 1634 | "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", 1635 | "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", 1636 | "dev": true, 1637 | "license": "BSD-2-Clause", 1638 | "engines": { 1639 | "node": ">=0.12" 1640 | }, 1641 | "funding": { 1642 | "url": "https://github.com/fb55/entities?sponsor=1" 1643 | } 1644 | }, 1645 | "node_modules/import-fresh": { 1646 | "version": "3.3.1", 1647 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 1648 | "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 1649 | "dev": true, 1650 | "license": "MIT", 1651 | "dependencies": { 1652 | "parent-module": "^1.0.0", 1653 | "resolve-from": "^4.0.0" 1654 | }, 1655 | "engines": { 1656 | "node": ">=6" 1657 | }, 1658 | "funding": { 1659 | "url": "https://github.com/sponsors/sindresorhus" 1660 | } 1661 | }, 1662 | "node_modules/is-arrayish": { 1663 | "version": "0.2.1", 1664 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1665 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", 1666 | "dev": true, 1667 | "license": "MIT" 1668 | }, 1669 | "node_modules/is-json": { 1670 | "version": "2.0.1", 1671 | "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", 1672 | "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", 1673 | "dev": true, 1674 | "license": "ISC" 1675 | }, 1676 | "node_modules/js-tokens": { 1677 | "version": "4.0.0", 1678 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1679 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1680 | "dev": true, 1681 | "license": "MIT" 1682 | }, 1683 | "node_modules/js-yaml": { 1684 | "version": "4.1.0", 1685 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1686 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1687 | "dev": true, 1688 | "license": "MIT", 1689 | "dependencies": { 1690 | "argparse": "^2.0.1" 1691 | }, 1692 | "bin": { 1693 | "js-yaml": "bin/js-yaml.js" 1694 | } 1695 | }, 1696 | "node_modules/json-parse-even-better-errors": { 1697 | "version": "2.3.1", 1698 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 1699 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 1700 | "dev": true, 1701 | "license": "MIT" 1702 | }, 1703 | "node_modules/lines-and-columns": { 1704 | "version": "1.2.4", 1705 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 1706 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 1707 | "dev": true, 1708 | "license": "MIT" 1709 | }, 1710 | "node_modules/magic-string": { 1711 | "version": "0.30.17", 1712 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1713 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1714 | "dev": true, 1715 | "license": "MIT", 1716 | "dependencies": { 1717 | "@jridgewell/sourcemap-codec": "^1.5.0" 1718 | } 1719 | }, 1720 | "node_modules/nanoid": { 1721 | "version": "3.3.9", 1722 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", 1723 | "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", 1724 | "dev": true, 1725 | "funding": [ 1726 | { 1727 | "type": "github", 1728 | "url": "https://github.com/sponsors/ai" 1729 | } 1730 | ], 1731 | "license": "MIT", 1732 | "bin": { 1733 | "nanoid": "bin/nanoid.cjs" 1734 | }, 1735 | "engines": { 1736 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1737 | } 1738 | }, 1739 | "node_modules/parent-module": { 1740 | "version": "1.0.1", 1741 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1742 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1743 | "dev": true, 1744 | "license": "MIT", 1745 | "dependencies": { 1746 | "callsites": "^3.0.0" 1747 | }, 1748 | "engines": { 1749 | "node": ">=6" 1750 | } 1751 | }, 1752 | "node_modules/parse-json": { 1753 | "version": "5.2.0", 1754 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 1755 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 1756 | "dev": true, 1757 | "license": "MIT", 1758 | "dependencies": { 1759 | "@babel/code-frame": "^7.0.0", 1760 | "error-ex": "^1.3.1", 1761 | "json-parse-even-better-errors": "^2.3.0", 1762 | "lines-and-columns": "^1.1.6" 1763 | }, 1764 | "engines": { 1765 | "node": ">=8" 1766 | }, 1767 | "funding": { 1768 | "url": "https://github.com/sponsors/sindresorhus" 1769 | } 1770 | }, 1771 | "node_modules/picocolors": { 1772 | "version": "1.1.1", 1773 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1774 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1775 | "dev": true, 1776 | "license": "ISC" 1777 | }, 1778 | "node_modules/postcss": { 1779 | "version": "8.5.3", 1780 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", 1781 | "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", 1782 | "dev": true, 1783 | "funding": [ 1784 | { 1785 | "type": "opencollective", 1786 | "url": "https://opencollective.com/postcss/" 1787 | }, 1788 | { 1789 | "type": "tidelift", 1790 | "url": "https://tidelift.com/funding/github/npm/postcss" 1791 | }, 1792 | { 1793 | "type": "github", 1794 | "url": "https://github.com/sponsors/ai" 1795 | } 1796 | ], 1797 | "license": "MIT", 1798 | "dependencies": { 1799 | "nanoid": "^3.3.8", 1800 | "picocolors": "^1.1.1", 1801 | "source-map-js": "^1.2.1" 1802 | }, 1803 | "engines": { 1804 | "node": "^10 || ^12 || >=14" 1805 | } 1806 | }, 1807 | "node_modules/posthtml": { 1808 | "version": "0.16.6", 1809 | "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", 1810 | "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", 1811 | "dev": true, 1812 | "license": "MIT", 1813 | "dependencies": { 1814 | "posthtml-parser": "^0.11.0", 1815 | "posthtml-render": "^3.0.0" 1816 | }, 1817 | "engines": { 1818 | "node": ">=12.0.0" 1819 | } 1820 | }, 1821 | "node_modules/posthtml-parser": { 1822 | "version": "0.11.0", 1823 | "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", 1824 | "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", 1825 | "dev": true, 1826 | "license": "MIT", 1827 | "dependencies": { 1828 | "htmlparser2": "^7.1.1" 1829 | }, 1830 | "engines": { 1831 | "node": ">=12" 1832 | } 1833 | }, 1834 | "node_modules/posthtml-render": { 1835 | "version": "3.0.0", 1836 | "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", 1837 | "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", 1838 | "dev": true, 1839 | "license": "MIT", 1840 | "dependencies": { 1841 | "is-json": "^2.0.1" 1842 | }, 1843 | "engines": { 1844 | "node": ">=12" 1845 | } 1846 | }, 1847 | "node_modules/resolve-from": { 1848 | "version": "4.0.0", 1849 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1850 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1851 | "dev": true, 1852 | "license": "MIT", 1853 | "engines": { 1854 | "node": ">=4" 1855 | } 1856 | }, 1857 | "node_modules/source-map-js": { 1858 | "version": "1.2.1", 1859 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1860 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1861 | "dev": true, 1862 | "license": "BSD-3-Clause", 1863 | "engines": { 1864 | "node": ">=0.10.0" 1865 | } 1866 | }, 1867 | "node_modules/timsort": { 1868 | "version": "0.3.0", 1869 | "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", 1870 | "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", 1871 | "dev": true, 1872 | "license": "MIT" 1873 | }, 1874 | "node_modules/typescript": { 1875 | "version": "5.8.2", 1876 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 1877 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 1878 | "dev": true, 1879 | "license": "Apache-2.0", 1880 | "bin": { 1881 | "tsc": "bin/tsc", 1882 | "tsserver": "bin/tsserver" 1883 | }, 1884 | "engines": { 1885 | "node": ">=14.17" 1886 | } 1887 | }, 1888 | "node_modules/vue": { 1889 | "version": "3.5.13", 1890 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", 1891 | "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", 1892 | "dev": true, 1893 | "license": "MIT", 1894 | "dependencies": { 1895 | "@vue/compiler-dom": "3.5.13", 1896 | "@vue/compiler-sfc": "3.5.13", 1897 | "@vue/runtime-dom": "3.5.13", 1898 | "@vue/server-renderer": "3.5.13", 1899 | "@vue/shared": "3.5.13" 1900 | }, 1901 | "peerDependencies": { 1902 | "typescript": "*" 1903 | }, 1904 | "peerDependenciesMeta": { 1905 | "typescript": { 1906 | "optional": true 1907 | } 1908 | } 1909 | } 1910 | } 1911 | } 1912 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "node build.mjs", 4 | "dev": "node build.mjs --dev", 5 | "test": "tsc && biome check", 6 | "fix": "biome check --write" 7 | }, 8 | "devDependencies": { 9 | "@biomejs/biome": "^2.0.0-beta.1", 10 | "@chialab/esbuild-plugin-html": "^0.18.2", 11 | "@vue/tsconfig": "^0.7.0", 12 | "bootstrap": "^5.2.2", 13 | "esbuild": "^0.25.0", 14 | "esbuild-plugin-vue3": "^0.4.2", 15 | "htmlnano": "^2.1.1", 16 | "typescript": "^5.2.2", 17 | "vue": "^3.2.40" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/192.png -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 404 · Page not found 10 | 11 | 12 |

404 Not Found

13 | 14 | 15 | -------------------------------------------------------------------------------- /public/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/512.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/artifact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/artifact.jpg -------------------------------------------------------------------------------- /public/icons/cs2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/cs2.jpg -------------------------------------------------------------------------------- /public/icons/deadlock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/deadlock.jpg -------------------------------------------------------------------------------- /public/icons/dota.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/dota.jpg -------------------------------------------------------------------------------- /public/icons/portal2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/portal2.jpg -------------------------------------------------------------------------------- /public/icons/steam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/steam.jpg -------------------------------------------------------------------------------- /public/icons/tf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/tf.jpg -------------------------------------------------------------------------------- /public/icons/underlords.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7d148892ef314367ab66965687f8d3576e964d0a/public/icons/underlords.jpg -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Steam Web API Documentation", 3 | "short_name": "Steam Web API", 4 | "description": "An automatically generated list of Steam Web API interfaces, methods and parameters. Allows you to craft requests in the browser.", 5 | "start_url": "/", 6 | "display": "standalone", 7 | "theme_color": "#0d0d0d", 8 | "background_color": "#0d0d0d", 9 | "icons": [ 10 | { 11 | "src": "favicon.ico", 12 | "sizes": "16x16 32x32 64x64" 13 | }, 14 | { 15 | "src": "192.png", 16 | "sizes": "192x192" 17 | }, 18 | { 19 | "src": "512.png", 20 | "sizes": "512x512" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /public/serviceworker.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('install', () => self.skipWaiting()); 2 | self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim())); 3 | 4 | self.addEventListener('fetch', (event) => { 5 | if (event.request.method !== 'GET') { 6 | return; 7 | } 8 | 9 | event.respondWith(networkOrCache(event)); 10 | }); 11 | 12 | async function putInCache(event, response) { 13 | const cache = await caches.open('steamwebapi-cache'); 14 | await cache.put(event.request, response); 15 | } 16 | 17 | async function networkOrCache(event) { 18 | try { 19 | const response = await fetch(event.request, { cache: 'no-cache' }); 20 | 21 | if (response.ok) { 22 | event.waitUntil(putInCache(event, response)); 23 | 24 | return response.clone(); 25 | } 26 | 27 | return response; 28 | } catch (e) { 29 | const cache = await caches.open('steamwebapi-cache'); 30 | const matching = await cache.match(event.request); 31 | 32 | return matching || Promise.reject(e); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ApiParameter.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 76 | -------------------------------------------------------------------------------- /src/App.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, markRaw, ref } from 'vue'; 2 | import interfacesJson from '../api.json'; 3 | import ApiParameter from './ApiParameter.vue'; 4 | import HighlightedSearchMethod from './HighlightedSearchMethod'; 5 | import type { ApiInterface, ApiMethod, ApiMethodParameter, ApiServices, SidebarGroupData } from './interfaces'; 6 | import { ApiSearcher } from './search'; 7 | 8 | const sidebar = ref(null); 9 | const inputSearch = ref(null); 10 | const inputApiKey = ref(null); 11 | const inputAccessToken = ref(null); 12 | 13 | export default defineComponent({ 14 | components: { 15 | ApiParameter, 16 | HighlightedSearchMethod, 17 | }, 18 | data() { 19 | // @ts-ignore 20 | const interfaces = interfacesJson as ApiServices; 21 | 22 | const groupsMap = new Map(); 23 | const groupsData = new Map( 24 | // biome-ignore format: too verbose 25 | [ 26 | // Order of apps here defines the order in the sidebar 27 | [0, { name: 'Steam', icon: 'steam.jpg', open: true, methods: {} }], 28 | [730, { name: 'Counter-Strike 2', icon: 'cs2.jpg', open: true, methods: {} }], 29 | [570, { name: 'Dota 2', icon: 'dota.jpg', open: true, methods: {} }], 30 | [440, { name: 'Team Fortress 2', icon: 'tf.jpg', open: true, methods: {} }], 31 | [1422450, { name: 'Deadlock', icon: 'deadlock.jpg', open: true, methods: {} }], 32 | [620, { name: 'Portal 2', icon: 'portal2.jpg', open: false, methods: {} }], 33 | [1046930, { name: 'Dota Underlords', icon: 'underlords.jpg', open: false, methods: {} }], 34 | [583950, { name: 'Artifact Classic', icon: 'artifact.jpg', open: false, methods: {} }], 35 | [1269260, { name: 'Artifact Foundry', icon: 'artifact.jpg', open: false, methods: {} }], 36 | 37 | // Beta apps 38 | [247040, { name: 'Dota 2 Experimental', icon: 'dota.jpg', open: false, methods: {} }], 39 | [2305270, { name: 'Dota 2 Staging', icon: 'dota.jpg', open: false, methods: {} }], 40 | ], 41 | ); 42 | 43 | const steamGroup = groupsData.get(0)!; 44 | 45 | for (const interfaceName in interfaces) { 46 | const interfaceAppid = interfaceName.match(/_(?[0-9]+)$/); 47 | 48 | if (interfaceAppid) { 49 | const appid = parseInt(interfaceAppid.groups!.appid, 10); 50 | 51 | groupsMap.set(interfaceName, appid); 52 | 53 | let group = groupsData.get(appid); 54 | 55 | if (!group) { 56 | group = { 57 | name: `App ${appid}`, 58 | icon: 'steam.jpg', 59 | open: false, 60 | methods: {}, 61 | }; 62 | 63 | groupsData.set(appid, group); 64 | } 65 | 66 | group.methods[interfaceName] = interfaces[interfaceName]; 67 | } else { 68 | steamGroup.methods[interfaceName] = interfaces[interfaceName]; 69 | } 70 | 71 | for (const methodName in interfaces[interfaceName]) { 72 | const method = interfaces[interfaceName][methodName]; 73 | 74 | for (const parameter of method.parameters) { 75 | parameter._value = ''; 76 | 77 | if (parameter.type === 'bool') { 78 | parameter.manuallyToggled = false; 79 | } 80 | } 81 | } 82 | } 83 | 84 | return { 85 | userData: { 86 | webapi_key: '', 87 | access_token: '', 88 | steamid: '', 89 | format: 'json', 90 | favorites: new Set(), 91 | }, 92 | skipNextHashChange: false, 93 | keyInputType: 'password', 94 | hasValidWebApiKey: false, 95 | hasValidAccessToken: false, 96 | accessTokenExpiration: 0, 97 | accessTokenSteamId: null, 98 | accessTokenAudience: [], 99 | accessTokenVisible: false, 100 | currentFilter: '', 101 | currentInterface: '', 102 | search: markRaw(new ApiSearcher(interfaces)), 103 | interfaces, 104 | groupsMap, 105 | groupsData, 106 | }; 107 | }, 108 | setup() { 109 | return { 110 | sidebar, 111 | inputSearch, 112 | inputApiKey, 113 | inputAccessToken, 114 | }; 115 | }, 116 | watch: { 117 | 'userData.format'(value: string): void { 118 | localStorage.setItem('format', value); 119 | }, 120 | 'userData.webapi_key'(value: string): void { 121 | if (this.isFieldValid('webapi_key')) { 122 | localStorage.setItem('webapi_key', value); 123 | } else { 124 | localStorage.removeItem('webapi_key'); 125 | } 126 | }, 127 | 'userData.access_token'(value: string): void { 128 | try { 129 | if (value.length > 2 && value[0] === '{' && value[value.length - 1] === '}') { 130 | const obj = JSON.parse(value); 131 | 132 | if (obj.data?.webapi_token) { 133 | this.userData.access_token = obj.data.webapi_token; 134 | return; 135 | } 136 | } 137 | if (/^[\w-]+\.[\w-]+\.[\w-]+$/.test(value)) { 138 | const jwt = value.split('.'); 139 | const token = JSON.parse(atob(jwt[1])); 140 | 141 | this.accessTokenExpiration = token.exp * 1000; 142 | this.accessTokenAudience = token.aud; 143 | this.accessTokenSteamId = token.sub; 144 | 145 | if (token.sub && !this.userData.steamid) { 146 | this.userData.steamid = token.sub; 147 | } 148 | } else { 149 | throw new Error('Invalid token format (or empty)'); 150 | } 151 | } catch (e) { 152 | console.log((e as Error).message); 153 | this.accessTokenExpiration = 0; 154 | this.accessTokenSteamId = null; 155 | this.accessTokenAudience = []; 156 | } 157 | 158 | if (this.isFieldValid('access_token')) { 159 | localStorage.setItem('access_token', value); 160 | } else { 161 | localStorage.removeItem('access_token'); 162 | } 163 | }, 164 | 'userData.steamid'(value: string): void { 165 | if (this.isFieldValid('steamid')) { 166 | localStorage.setItem('steamid', value); 167 | 168 | this.fillSteamidParameter(); 169 | } else { 170 | localStorage.removeItem('steamid'); 171 | } 172 | }, 173 | currentFilter(newFilter: string, oldFilter: string): void { 174 | if (!newFilter) { 175 | this.$nextTick(this.scrollInterfaceIntoView); 176 | 177 | if (oldFilter) { 178 | this.sidebar!.scrollTop = 0; 179 | } 180 | } else { 181 | this.setInterface(''); 182 | 183 | if (!oldFilter) { 184 | this.sidebar!.scrollTop = 0; 185 | } 186 | } 187 | }, 188 | }, 189 | mounted(): void { 190 | try { 191 | this.userData.format = localStorage.getItem('format') || 'json'; 192 | this.userData.steamid = localStorage.getItem('steamid') || ''; 193 | this.userData.webapi_key = localStorage.getItem('webapi_key') || ''; 194 | this.userData.access_token = localStorage.getItem('access_token') || ''; 195 | 196 | const favoriteStrings = JSON.parse(localStorage.getItem('favorites') || '[]'); 197 | 198 | for (const favorite of favoriteStrings) { 199 | const [favoriteInterface, favoriteMethod] = favorite.split('/', 2); 200 | 201 | if ( 202 | Object.hasOwn(this.interfaces, favoriteInterface) && 203 | Object.hasOwn(this.interfaces[favoriteInterface], favoriteMethod) 204 | ) { 205 | this.interfaces[favoriteInterface][favoriteMethod].isFavorite = true; 206 | 207 | this.userData.favorites.add(favorite); 208 | } 209 | } 210 | } catch (e) { 211 | console.error(e); 212 | } 213 | 214 | if (location.hash.startsWith('#')) { 215 | this.setInterface(location.hash.substring(1), true); 216 | } 217 | 218 | window.addEventListener( 219 | 'hashchange', 220 | () => { 221 | if (this.skipNextHashChange) { 222 | this.skipNextHashChange = false; 223 | return; 224 | } 225 | 226 | this.setInterface(location.hash.substring(1)); 227 | }, 228 | false, 229 | ); 230 | 231 | this.bindGlobalKeybind(); 232 | }, 233 | computed: { 234 | sidebarInterfaces(): Map { 235 | const interfaces = this.filteredInterfaces; 236 | 237 | if (this.currentFilter) { 238 | return new Map([ 239 | [ 240 | -1, 241 | { 242 | name: 'Search results', 243 | icon: '', 244 | open: true, 245 | methods: interfaces, 246 | }, 247 | ], 248 | ]); 249 | } 250 | 251 | return this.groupsData; 252 | }, 253 | filteredInterfaces(): ApiServices { 254 | if (!this.currentFilter) { 255 | return this.interfaces; 256 | } 257 | 258 | const matchedInterfaces: ApiServices = {}; 259 | const hits = this.search.search(this.currentFilter); 260 | 261 | for (const match of hits) { 262 | if (!matchedInterfaces[match.interface]) { 263 | matchedInterfaces[match.interface] = {}; 264 | } 265 | 266 | const method = this.interfaces[match.interface][match.method]; 267 | method.highlight = match.indices; 268 | matchedInterfaces[match.interface][match.method] = method; 269 | } 270 | 271 | return matchedInterfaces; 272 | }, 273 | currentInterfaceMethods(): ApiInterface { 274 | return this.interfaces[this.currentInterface]; 275 | }, 276 | uriDelimeterBeforeKey() { 277 | return this.hasValidAccessToken || this.hasValidWebApiKey ? '?' : ''; 278 | }, 279 | formatAccessTokenExpirationDate(): string { 280 | const formatter = new Intl.DateTimeFormat('en-US', { 281 | hourCycle: 'h23', 282 | dateStyle: 'medium', 283 | timeStyle: 'short', 284 | }); 285 | 286 | return formatter.format(this.accessTokenExpiration); 287 | }, 288 | }, 289 | methods: { 290 | setInterface(interfaceAndMethod: string, setFromUrl = false): void { 291 | const split = interfaceAndMethod.split('/', 2); 292 | let currentInterface: string | null = split[0]; 293 | let currentMethod: string | null = split.length > 1 ? split[1] : null; 294 | 295 | if (!Object.hasOwn(this.interfaces, currentInterface)) { 296 | currentInterface = null; 297 | currentMethod = null; 298 | } else if (currentMethod !== null && !Object.hasOwn(this.interfaces[currentInterface], currentMethod)) { 299 | currentMethod = null; 300 | } 301 | 302 | this.currentInterface = currentInterface || ''; 303 | 304 | if (currentInterface) { 305 | document.title = `${currentInterface} – Steam Web API Documentation`; 306 | } else { 307 | document.title = `Steam Web API Documentation`; 308 | } 309 | 310 | // Since we won't scroll to a method, scroll to top (as there is no element with just interface id) 311 | if (document.scrollingElement && !currentMethod) { 312 | document.scrollingElement.scrollTop = 0; 313 | } 314 | 315 | if (setFromUrl) { 316 | return; 317 | } 318 | 319 | this.$nextTick(() => { 320 | this.skipNextHashChange = true; 321 | 322 | if (currentMethod) { 323 | location.hash = `#${currentInterface}/${currentMethod}`; 324 | } else if (currentInterface) { 325 | location.hash = `#${currentInterface}`; 326 | } else { 327 | location.hash = ''; 328 | } 329 | }); 330 | }, 331 | fillSteamidParameter(): void { 332 | if (!this.userData.steamid) { 333 | return; 334 | } 335 | 336 | for (const interfaceName in this.interfaces) { 337 | for (const methodName in this.interfaces[interfaceName]) { 338 | for (const parameter of this.interfaces[interfaceName][methodName].parameters) { 339 | if (!parameter._value && parameter.name.includes('steamid')) { 340 | parameter._value = this.userData.steamid; 341 | } 342 | } 343 | } 344 | } 345 | }, 346 | isFieldValid(field: string): boolean { 347 | switch (field) { 348 | case 'access_token': 349 | this.hasValidAccessToken = this.accessTokenExpiration > Date.now(); 350 | return this.hasValidAccessToken; 351 | 352 | case 'webapi_key': 353 | this.hasValidWebApiKey = /^[0-9a-f]{32}$/i.test(this.userData[field]); 354 | return this.hasValidWebApiKey; 355 | 356 | case 'steamid': 357 | return /^[0-9]{17}$/.test(this.userData[field]); 358 | } 359 | 360 | return false; 361 | }, 362 | renderUri(methodName: string, method: ApiMethod): string { 363 | let host = 'https://api.steampowered.com/'; 364 | 365 | if (method._type === 'publisher_only') { 366 | host = 'https://partner.steam-api.com/'; 367 | } 368 | 369 | return `${host}${this.currentInterface}/${methodName}/v${method.version}/`; 370 | }, 371 | renderApiKey(): string { 372 | const parameters = new URLSearchParams(); 373 | 374 | if (this.hasValidAccessToken) { 375 | parameters.set('access_token', this.userData.access_token); 376 | } else if (this.hasValidWebApiKey) { 377 | parameters.set('key', this.userData.webapi_key); 378 | } 379 | 380 | return parameters.toString(); 381 | }, 382 | renderParameters(method: ApiMethod): string { 383 | const parameters = new URLSearchParams(); 384 | 385 | if (this.userData.format !== 'json') { 386 | parameters.set('format', this.userData.format); 387 | } 388 | 389 | let hasArrays = false; 390 | const inputJson = {} as any; 391 | 392 | for (const parameter of method.parameters) { 393 | if (parameter.extra) { 394 | const arr = this.getInnerParameters(parameter); 395 | 396 | if (Object.keys(arr).length > 0) { 397 | hasArrays = true; 398 | 399 | if (parameter.type?.endsWith('[]')) { 400 | const paramName = parameter.name.substring(0, parameter.name.length - 3); 401 | 402 | if (!Object.hasOwn(inputJson, paramName)) { 403 | inputJson[paramName] = []; 404 | } 405 | 406 | inputJson[paramName].push(arr); 407 | } else { 408 | inputJson[parameter.name] = arr; 409 | } 410 | } else if (parameter._value) { 411 | parameters.set(parameter.name, parameter._value); 412 | } 413 | 414 | continue; 415 | } 416 | 417 | if (!parameter._value && !parameter.manuallyToggled) { 418 | continue; 419 | } 420 | 421 | parameters.set(parameter.name, parameter._value ?? ''); 422 | } 423 | 424 | if (hasArrays) { 425 | method.hasArrays = true; 426 | parameters.set('input_json', JSON.stringify(inputJson)); 427 | } 428 | 429 | const str = parameters.toString(); 430 | 431 | if (str.length === 0) { 432 | return ''; 433 | } 434 | 435 | if (this.uriDelimeterBeforeKey) { 436 | return `&${str}`; 437 | } 438 | 439 | return `?${str}`; 440 | }, 441 | getInnerParameters(parameterParent: ApiMethodParameter) { 442 | const arr = {} as any; 443 | 444 | for (const parameter of parameterParent.extra!) { 445 | if (parameter.extra) { 446 | const result = this.getInnerParameters(parameter); 447 | 448 | if (Object.keys(result).length > 0) { 449 | if (parameter.type?.endsWith('[]')) { 450 | const paramName = parameter.name.substring(0, parameter.name.length - 3); 451 | 452 | if (!Object.hasOwn(arr, paramName)) { 453 | arr[paramName] = []; 454 | } 455 | 456 | arr[paramName].push(result); 457 | } else { 458 | arr[parameter.name] = result; 459 | } 460 | } 461 | 462 | continue; 463 | } 464 | 465 | if (!parameter._value && !parameter.manuallyToggled) { 466 | continue; 467 | } 468 | 469 | if (parameter.type?.endsWith('[]')) { 470 | const paramName = parameter.name.substring(0, parameter.name.length - 3); 471 | 472 | if (!Object.hasOwn(arr, paramName)) { 473 | arr[paramName] = []; 474 | } 475 | 476 | arr[paramName].push(parameter._value || ''); 477 | } else { 478 | arr[parameter.name] = parameter._value || ''; 479 | } 480 | } 481 | 482 | return arr; 483 | }, 484 | useThisMethod(event: SubmitEvent, method: ApiMethod): void { 485 | const form = event.target as HTMLFormElement; 486 | 487 | if (method.hasArrays) { 488 | event.preventDefault(); 489 | 490 | if (method.httpmethod === 'POST') { 491 | alert('Executing POST requests with input_json is not yet supported.'); 492 | return; 493 | } 494 | 495 | const url = [ 496 | form.action, 497 | this.uriDelimeterBeforeKey, 498 | this.renderApiKey(), 499 | this.renderParameters(method), 500 | ].join(''); 501 | 502 | try { 503 | window.open(url, '_blank'); 504 | } catch { 505 | alert('Failed to open window'); 506 | } 507 | 508 | return; 509 | } 510 | 511 | if ( 512 | method.httpmethod === 'POST' && 513 | !confirm( 514 | 'Executing POST requests could be potentially disastrous.\n\n' + 515 | 'Author is not responsible for any damage done.\n\n' + 516 | 'Are you sure you want to continue?', 517 | ) 518 | ) { 519 | event.preventDefault(); 520 | } 521 | 522 | for (const field of form.elements) { 523 | if (!(field instanceof HTMLInputElement)) { 524 | continue; 525 | } 526 | 527 | if (!field.value && !field.disabled && field.tagName === 'INPUT') { 528 | field.disabled = true; 529 | 530 | setTimeout(() => { 531 | field.disabled = false; 532 | }, 0); 533 | } 534 | } 535 | }, 536 | addParamArray(method: ApiMethod, parameter: ApiMethodParameter): void { 537 | if (!parameter._counter) { 538 | parameter._counter = 1; 539 | } else { 540 | parameter._counter++; 541 | } 542 | 543 | const newParameter: ApiMethodParameter = { 544 | name: `${parameter.name.substring(0, parameter.name.length - 3)}[${parameter._counter}]`, 545 | type: parameter.type, 546 | optional: true, 547 | }; 548 | 549 | if (parameter.extra) { 550 | newParameter.extra = []; 551 | 552 | for (const parameter2 of parameter.extra!) { 553 | newParameter.extra.push({ 554 | name: parameter2.name, 555 | type: parameter2.type, 556 | optional: true, 557 | }); 558 | } 559 | } 560 | 561 | const parameterIndex = method.parameters.findIndex((param) => param.name === parameter.name); 562 | method.parameters.splice(parameterIndex + parameter._counter, 0, newParameter); 563 | }, 564 | scrollInterfaceIntoView(): void { 565 | const element = document.querySelector(`.interface-list a[href="#${this.currentInterface}"]`); 566 | 567 | if (element instanceof HTMLElement) { 568 | element.scrollIntoView(); 569 | } 570 | }, 571 | copyUrl(event: MouseEvent): void { 572 | const button = event.target as Element; 573 | const element = button.closest('.input-group')!.querySelector('.form-control')!; 574 | 575 | navigator.clipboard.writeText(element.textContent || '').then( 576 | () => { 577 | button.classList.add('bg-success'); 578 | 579 | setTimeout(() => button.classList.remove('bg-success'), 500); 580 | }, 581 | () => { 582 | // write fail 583 | }, 584 | ); 585 | }, 586 | favoriteMethod(method: ApiMethod, methodName: string): void { 587 | const name = `${this.currentInterface}/${methodName}`; 588 | 589 | method.isFavorite = !method.isFavorite; 590 | 591 | if (method.isFavorite) { 592 | this.userData.favorites.add(name); 593 | } else { 594 | this.userData.favorites.delete(name); 595 | } 596 | 597 | localStorage.setItem('favorites', JSON.stringify([...this.userData.favorites])); 598 | }, 599 | navigateSidebar(direction: number): void { 600 | const entries = Object.entries(this.filteredInterfaces); 601 | const index = entries.findIndex((x) => x[0] === this.currentInterface) + direction; 602 | const size = entries.length; 603 | const [interfaceName, methods] = entries[((index % size) + size) % size]; 604 | const firstMethodName = Object.keys(methods)[0]; 605 | 606 | this.setInterface(`${interfaceName}/${firstMethodName}`); 607 | this.scrollInterfaceIntoView(); 608 | 609 | // This is trash, but the focus gets lost because of location.hash change 610 | this.$nextTick(() => { 611 | this.inputSearch?.focus(); 612 | }); 613 | }, 614 | focusApiKey(): void { 615 | this.currentFilter = ''; 616 | this.setInterface(''); 617 | 618 | this.$nextTick(() => { 619 | const element = this.hasValidAccessToken ? this.inputAccessToken : this.inputApiKey; 620 | 621 | if (element) { 622 | element.focus(); 623 | } 624 | }); 625 | }, 626 | onSearchInput(e: Event) { 627 | requestAnimationFrame(() => { 628 | this.currentFilter = (e.target as HTMLInputElement).value; 629 | }); 630 | }, 631 | bindGlobalKeybind() { 632 | document.addEventListener('keydown', (e: KeyboardEvent) => { 633 | if (e.ctrlKey || e.metaKey) { 634 | return; 635 | } 636 | 637 | const target = e.target as HTMLElement; 638 | 639 | if (['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(target.tagName)) { 640 | return; 641 | } 642 | 643 | if (e.key === '/' || e.key === 's') { 644 | e.preventDefault(); 645 | this.inputSearch?.focus(); 646 | } 647 | }); 648 | }, 649 | }, 650 | }); 651 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /src/HighlightedSearchMethod.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h, ref, type VNode } from 'vue'; 2 | 3 | export default defineComponent({ 4 | props: { 5 | method: { 6 | type: String, 7 | default: '', 8 | }, 9 | indices: { 10 | type: Array as () => number[], 11 | default: () => [], 12 | }, 13 | }, 14 | setup(props) { 15 | return () => highlightMethod(props.method, props.indices); 16 | }, 17 | }); 18 | 19 | function highlightMethod(text: string, indices: number[]) { 20 | // Group consecutive indices for proper highlighting spans 21 | const spans: { start: number; end: number }[] = []; 22 | let currentSpan: { start: number; end: number } | null = null; 23 | 24 | for (let i = 0; i < indices.length; i++) { 25 | const index = indices[i]; 26 | 27 | if (currentSpan === null) { 28 | currentSpan = { start: index, end: index }; 29 | } else if (index === currentSpan.end + 1) { 30 | currentSpan.end = index; 31 | } else { 32 | spans.push(currentSpan); 33 | currentSpan = { start: index, end: index }; 34 | } 35 | } 36 | 37 | if (currentSpan !== null) { 38 | spans.push(currentSpan); 39 | } 40 | 41 | // Build the highlighted text with VNodes 42 | const nodes: (VNode | string)[] = []; 43 | let lastIndex = 0; 44 | 45 | for (const span of spans) { 46 | // Add text before the span 47 | if (span.start > lastIndex) { 48 | nodes.push(text.substring(lastIndex, span.start)); 49 | } 50 | 51 | // Add highlighted span 52 | nodes.push(h('b', {}, text.substring(span.start, span.end + 1))); 53 | 54 | lastIndex = span.end + 1; 55 | } 56 | 57 | // Add remaining text 58 | if (lastIndex < text.length) { 59 | nodes.push(text.substring(lastIndex)); 60 | } 61 | 62 | return nodes; 63 | } 64 | -------------------------------------------------------------------------------- /src/documentation.ts: -------------------------------------------------------------------------------- 1 | import './index.ts'; 2 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Steam Web API Documentation and Tester 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createSSRApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | if ('serviceWorker' in navigator && !('DEV_MODE' in window)) { 5 | navigator.serviceWorker.register('serviceworker.js', { scope: './' }); 6 | } 7 | 8 | createSSRApp(App).mount('#app'); 9 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface SidebarGroupData { 2 | name: string; 3 | icon: string; 4 | open: boolean; 5 | methods: ApiServices; 6 | } 7 | 8 | export interface ApiServices { 9 | [x: string]: ApiInterface; 10 | } 11 | 12 | export interface ApiInterface { 13 | [x: string]: ApiMethod; 14 | } 15 | 16 | export interface ApiMethod { 17 | _type: 'protobufs' | 'publisher_only' | 'undocumented' | null; 18 | httpmethod: 'GET' | 'POST' | null; 19 | version: number; 20 | description?: string; 21 | highlight?: number[]; 22 | parameters: ApiMethodParameter[]; 23 | isFavorite: boolean; 24 | hasArrays: boolean; 25 | } 26 | 27 | export interface ApiMethodParameter { 28 | _value?: string; 29 | _counter?: number; 30 | manuallyToggled?: boolean; 31 | 32 | name: string; 33 | type?: string; 34 | description?: string; 35 | optional: boolean; 36 | extra?: ApiMethodParameter[]; 37 | } 38 | -------------------------------------------------------------------------------- /src/search.ts: -------------------------------------------------------------------------------- 1 | import type { ApiServices } from './interfaces'; 2 | 3 | export interface SearchResult { 4 | interface: string; 5 | method: string; 6 | indices: number[]; 7 | score: number; 8 | } 9 | 10 | interface ScoreResult { 11 | score: number; 12 | indices: number[]; 13 | } 14 | 15 | /** 16 | * A high-performance fuzzy search implementation for API methods 17 | * 18 | * Design Goals: 19 | * 1. Performance: Optimized for handling thousands of methods with minimal latency 20 | * 2. Relevance: 21 | * - Method names weighted higher than interface names 22 | * - Results sorted by calculated relevance score 23 | * - Exact substring matches prioritized over subsequence matches 24 | * - Start-of-word matches given higher scores 25 | * 3. Match Types: 26 | * - Substring matching (e.g. "tag" finds "GetTagList") 27 | * - Subsequence matching (e.g. "getcurrent" finds "GetNumberOfCurrentPlayers") 28 | * 4. Usability Features: 29 | * - Matched portions highlighted with tags 30 | * - Interface names with numeric IDs receive lower scores 31 | * - Adaptive thresholds based on query length 32 | * 5. Implementation: 33 | * - Prefix and trigram indexes for efficient candidate selection 34 | * - Pre-cached lowercase versions of strings for performance 35 | * - No regex usage for critical path operations 36 | * - Clean, maintainable code structure 37 | * - Limited to top 100 most relevant results 38 | */ 39 | export class ApiSearcher { 40 | private methodsList: { 41 | interface: string; 42 | method: string; 43 | interfaceLower: string; 44 | methodLower: string; 45 | hasAppIdSuffix: boolean; 46 | }[] = []; 47 | private prefixMap: Map = new Map(); 48 | private trigrams: Map = new Map(); 49 | private interfacePrefixMap: Map = new Map(); 50 | private interfaceTrigrams: Map = new Map(); 51 | 52 | constructor(apiServices: ApiServices) { 53 | // Build flat list of methods with pre-cached lowercase versions 54 | for (const interfaceName in apiServices) { 55 | const apiInterface = apiServices[interfaceName]; 56 | const interfaceLower = interfaceName.toLowerCase(); 57 | 58 | // Pre-check if interface matches the app ID pattern 59 | const hasAppIdSuffix = /_(?[0-9]+)$/.test(interfaceName); 60 | 61 | for (const methodName in apiInterface) { 62 | const methodLower = methodName.toLowerCase(); 63 | this.methodsList.push({ 64 | interface: interfaceName, 65 | method: methodName, 66 | interfaceLower, 67 | methodLower, 68 | hasAppIdSuffix, 69 | }); 70 | } 71 | } 72 | 73 | // Build indexes for both methods and interfaces 74 | this.methodsList.forEach((item, index) => { 75 | // Method indexes 76 | this.indexString(item.methodLower, index, this.prefixMap, this.trigrams); 77 | 78 | // Interface indexes 79 | this.indexString(item.interfaceLower, index, this.interfacePrefixMap, this.interfaceTrigrams); 80 | }); 81 | } 82 | 83 | private indexString( 84 | str: string, 85 | index: number, 86 | prefixMap: Map, 87 | trigramMap: Map, 88 | ): void { 89 | // Add each prefix of the string to the map 90 | for (let i = 1; i <= Math.min(str.length, 4); i++) { 91 | const prefix = str.substring(0, i); 92 | let arr = prefixMap.get(prefix); 93 | if (!arr) { 94 | arr = []; 95 | prefixMap.set(prefix, arr); 96 | } 97 | arr.push(index); 98 | } 99 | 100 | // Add trigrams for substring matching acceleration 101 | if (str.length >= 3) { 102 | for (let i = 0; i <= str.length - 3; i++) { 103 | const trigram = str.substring(i, i + 3); 104 | let arr = trigramMap.get(trigram); 105 | if (!arr) { 106 | arr = []; 107 | trigramMap.set(trigram, arr); 108 | } 109 | arr.push(index); 110 | } 111 | } 112 | } 113 | 114 | public search(query: string): SearchResult[] { 115 | if (!query || query.length === 0) { 116 | return []; 117 | } 118 | 119 | const normalizedQuery = query.toLowerCase().replace(/\s/g, ''); 120 | 121 | // Get method candidates 122 | const methodCandidates = this.getMethodCandidates(normalizedQuery); 123 | 124 | // Find matching interfaces 125 | const matchingInterfaces = this.getMatchingInterfaces(normalizedQuery); 126 | 127 | // Process candidates and build results 128 | const results: SearchResult[] = []; 129 | const processedItems = new Set(); 130 | 131 | // First process direct method matches 132 | for (const index of methodCandidates) { 133 | const item = this.methodsList[index]; 134 | 135 | // Only process if it's an actual match 136 | if (!this.hasSubstringOrSubsequence(item.methodLower, normalizedQuery)) { 137 | continue; 138 | } 139 | 140 | processedItems.add(index); 141 | 142 | const methodResult = this.getScore(item.methodLower, normalizedQuery, 3); 143 | let totalScore = methodResult.score; 144 | 145 | // Apply penalty for interfaces with app ID suffix 146 | if (item.hasAppIdSuffix) { 147 | totalScore *= 0.5; 148 | } 149 | 150 | // For very short queries, apply a stricter threshold 151 | const minScoreThreshold = normalizedQuery.length < 3 ? 0.7 : 0.3; 152 | 153 | // Apply stricter scoring for common terms 154 | if (totalScore > minScoreThreshold) { 155 | results.push({ 156 | interface: item.interface, 157 | method: item.method, 158 | indices: methodResult.indices, 159 | score: totalScore, 160 | }); 161 | } 162 | } 163 | 164 | // Then add methods from matching interfaces (if not already added) 165 | for (const interfaceName of matchingInterfaces) { 166 | // Find all methods belonging to this interface 167 | for (let i = 0; i < this.methodsList.length; i++) { 168 | if (this.methodsList[i].interfaceLower === interfaceName) { 169 | // Skip if already processed 170 | if (processedItems.has(i)) continue; 171 | 172 | const item = this.methodsList[i]; 173 | processedItems.add(i); 174 | 175 | // Check if method itself also matches (for highlighting) 176 | const methodResult = this.hasSubstringOrSubsequence(item.methodLower, normalizedQuery) 177 | ? this.getScore(item.methodLower, normalizedQuery, 3) 178 | : { score: 0, indices: [] }; 179 | 180 | // Interface matches get a modest base score 181 | let interfaceScore = 0.5; 182 | 183 | // Apply penalty for interfaces with app ID suffix 184 | if (item.hasAppIdSuffix) { 185 | interfaceScore *= 0.9; 186 | } 187 | 188 | const totalScore = interfaceScore + methodResult.score; 189 | 190 | // For very short queries, we need a higher threshold for interface matches 191 | const minScoreThreshold = normalizedQuery.length < 3 ? 0.5 : 0.25; 192 | 193 | if (totalScore > minScoreThreshold) { 194 | results.push({ 195 | interface: item.interface, 196 | method: item.method, 197 | indices: methodResult.indices, 198 | score: totalScore, 199 | }); 200 | } 201 | } 202 | } 203 | } 204 | 205 | // Sort by score in descending order 206 | results.sort((a, b) => b.score - a.score); 207 | 208 | return results.slice(0, 100); 209 | } 210 | 211 | private getMethodCandidates(query: string): Set { 212 | const candidateSet = new Set(); 213 | 214 | // Try prefix matching for methods (fastest) 215 | this.addCandidatesByPrefix(query, this.prefixMap, candidateSet); 216 | 217 | // Return early if we have enough candidates 218 | if (candidateSet.size >= 100) { 219 | return candidateSet; 220 | } 221 | 222 | // If query is long enough, try trigram matching 223 | if (query.length >= 3) { 224 | this.addCandidatesByTrigram(query, this.trigrams, candidateSet); 225 | 226 | // Return early if we have enough candidates 227 | if (candidateSet.size >= 100) { 228 | return candidateSet; 229 | } 230 | } 231 | 232 | // If still no candidates, include methods with first letter match 233 | if (candidateSet.size === 0 && query.length > 0) { 234 | const firstChar = query[0]; 235 | for (let i = 0; i < this.methodsList.length; i++) { 236 | if (this.methodsList[i].methodLower.includes(firstChar)) { 237 | candidateSet.add(i); 238 | } 239 | } 240 | } 241 | 242 | return candidateSet; 243 | } 244 | 245 | private getMatchingInterfaces(query: string): Set { 246 | const matchingInterfaces = new Set(); 247 | 248 | // Find matching interfaces using the candidate approach 249 | const candidateSet = new Set(); 250 | 251 | // Try prefix matching for interfaces 252 | this.addCandidatesByPrefix(query, this.interfacePrefixMap, candidateSet); 253 | 254 | // If needed, try trigram matching 255 | if (candidateSet.size < 50 && query.length >= 3) { 256 | this.addCandidatesByTrigram(query, this.interfaceTrigrams, candidateSet); 257 | } 258 | 259 | // Check candidates 260 | for (const index of candidateSet) { 261 | const interfaceName = this.methodsList[index].interfaceLower; 262 | if (this.hasSubstringOrSubsequence(interfaceName, query)) { 263 | matchingInterfaces.add(interfaceName); 264 | } 265 | } 266 | 267 | return matchingInterfaces; 268 | } 269 | 270 | private addCandidatesByPrefix(query: string, prefixMap: Map, candidateSet: Set): void { 271 | const prefix = query.substring(0, Math.min(query.length, 4)); 272 | const indices = prefixMap.get(prefix); 273 | 274 | if (indices) { 275 | indices.forEach((index) => candidateSet.add(index)); 276 | } 277 | } 278 | 279 | private addCandidatesByTrigram(query: string, trigramMap: Map, candidateSet: Set): void { 280 | for (let i = 0; i <= query.length - 3; i++) { 281 | const trigram = query.substring(i, i + 3); 282 | const indices = trigramMap.get(trigram); 283 | 284 | if (indices) { 285 | indices.forEach((index) => candidateSet.add(index)); 286 | } 287 | } 288 | } 289 | 290 | private hasSubstringOrSubsequence(text: string, query: string): boolean { 291 | // Quick substring check 292 | if (text.includes(query)) { 293 | return true; 294 | } 295 | 296 | // Quick subsequence check 297 | let j = 0; 298 | for (let i = 0; i < text.length && j < query.length; i++) { 299 | if (text[i] === query[j]) { 300 | j++; 301 | } 302 | } 303 | return j === query.length; 304 | } 305 | 306 | private getScore(text: string, query: string, weight: number): ScoreResult { 307 | // Substring match (highest score) 308 | const substringIndex = text.indexOf(query); 309 | if (substringIndex !== -1) { 310 | // Score based on position (earlier matches get higher scores) 311 | let positionScore = 1 - substringIndex / text.length; 312 | 313 | // Bonus for matches at start of words (camelCase detection) 314 | if ( 315 | substringIndex === 0 || 316 | (text[substringIndex - 1] >= 'a' && 317 | text[substringIndex - 1] <= 'z' && 318 | text[substringIndex] >= 'A' && 319 | text[substringIndex] <= 'Z') 320 | ) { 321 | positionScore += 0.5; 322 | } 323 | 324 | // Apply length-based adjustment for short queries 325 | const lengthFactor = Math.min(1, query.length / 5); 326 | 327 | const indices = Array.from({ length: query.length }, (_, i) => substringIndex + i); 328 | return { 329 | score: weight * (1.0 + positionScore * 0.5) * lengthFactor, 330 | indices, 331 | }; 332 | } 333 | 334 | // Subsequence match - only compute if length is manageable and query isn't too short 335 | if (query.length >= 2 && query.length <= text.length) { 336 | return this.getSubsequenceScore(text, query, weight); 337 | } 338 | 339 | return { score: 0, indices: [] }; 340 | } 341 | 342 | private getSubsequenceScore(text: string, query: string, weight: number): ScoreResult { 343 | let matchIndices: number[] = []; 344 | let i = 0; 345 | let j = 0; 346 | 347 | // Try to find a subsequence match 348 | while (i < text.length && j < query.length) { 349 | if (text[i] === query[j]) { 350 | matchIndices.push(i); 351 | j++; 352 | } 353 | i++; 354 | } 355 | 356 | // If we didn't match all query characters 357 | if (j < query.length) { 358 | return { score: 0, indices: [] }; 359 | } 360 | 361 | // Calculate score based on matches and gaps 362 | const textLength = text.length; 363 | 364 | // Calculate gap penalties 365 | let gapPenalty = 0; 366 | for (let k = 1; k < matchIndices.length; k++) { 367 | const gap = matchIndices[k] - matchIndices[k - 1] - 1; 368 | gapPenalty += gap / textLength; 369 | } 370 | 371 | // Normalize gap penalty 372 | gapPenalty = matchIndices.length > 1 ? gapPenalty / (matchIndices.length - 1) : 0; 373 | 374 | // Position bonus (earlier matches are better) 375 | const positionBonus = 1 - matchIndices[0] / textLength; 376 | 377 | // Calculate density bonus (denser matches are better) 378 | const densityRange = matchIndices[matchIndices.length - 1] - matchIndices[0] + 1; 379 | const density = densityRange > 0 ? matchIndices.length / densityRange : 1; 380 | 381 | // Length factor (longer queries are better) 382 | const lengthFactor = Math.min(1, query.length / 5); 383 | 384 | const score = weight * 0.4 * (1 - gapPenalty) * (1 + 0.3 * positionBonus + 0.3 * density) * lengthFactor; 385 | 386 | return { score, indices: matchIndices }; 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/ssr.ts: -------------------------------------------------------------------------------- 1 | import { createSSRApp } from 'vue'; 2 | import { renderToString } from 'vue/server-renderer'; 3 | import App from './App.vue'; 4 | 5 | const app = createSSRApp(App); 6 | 7 | renderToString(app).then((html) => { 8 | console.log(html); 9 | }); 10 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | @import "bootstrap/dist/css/bootstrap.css"; 2 | 3 | a { 4 | color: #1f8bff; 5 | text-decoration: none; 6 | } 7 | 8 | a:hover { 9 | color: #99caff; 10 | text-decoration: underline; 11 | } 12 | 13 | code { 14 | color: #ec5fa1; 15 | } 16 | 17 | .card-body { 18 | padding-bottom: 0; 19 | } 20 | 21 | .card .table { 22 | font-size: 14px; 23 | margin-bottom: 0; 24 | } 25 | 26 | .card .table td:first-child, 27 | .card .table th:first-child { 28 | border-left: 0; 29 | } 30 | 31 | .card .table td:last-child, 32 | .card .table th:last-child { 33 | border-right: 0; 34 | } 35 | 36 | .card .btn-sm { 37 | padding-top: 0; 38 | padding-bottom: 0; 39 | line-height: 1.4; 40 | } 41 | 42 | .interface-list { 43 | padding: 0; 44 | list-style: none; 45 | } 46 | 47 | .method-list { 48 | padding: 0; 49 | list-style: none; 50 | font-size: 0.9rem; 51 | background-color: #2b3647; 52 | } 53 | 54 | .method-list a > b { 55 | color: #fff; 56 | } 57 | 58 | .interface-list li { 59 | text-overflow: ellipsis; 60 | overflow: hidden; 61 | white-space: nowrap; 62 | direction: rtl; 63 | } 64 | 65 | .header h1, 66 | .header h2, 67 | .header .separator { 68 | display: inline; 69 | font-size: 1.4rem; 70 | line-height: 38px; 71 | } 72 | 73 | .badge { 74 | transition: none; 75 | } 76 | 77 | a.badge { 78 | text-decoration: underline; 79 | } 80 | 81 | .no-select { 82 | user-select: none; 83 | } 84 | 85 | body { 86 | background-color: #161920; 87 | color: #acacac; 88 | } 89 | 90 | .header { 91 | background-color: #096295; 92 | border-bottom: 2px solid #67c1f5; 93 | color: #fff; 94 | } 95 | 96 | .search-input:focus, 97 | .search-input { 98 | border: 1px solid rgba(103, 193, 245, 0.7); 99 | background-color: #171b22; 100 | color: #fff; 101 | } 102 | 103 | .search-input:focus { 104 | box-shadow: 0 0 20px rgba(147, 211, 245, 0.5); 105 | } 106 | 107 | .search-input::placeholder { 108 | color: #888; 109 | } 110 | 111 | .header a { 112 | color: inherit; 113 | } 114 | 115 | .card { 116 | background: #22252b; 117 | color: #e5e5e5; 118 | border: 0; 119 | border-radius: 0; 120 | transition: box-shadow 0.2s; 121 | } 122 | 123 | .card:target { 124 | box-shadow: 0 0 0 5px #66c0f4; 125 | } 126 | 127 | .card-header { 128 | background: #4e5155; 129 | border: 0; 130 | top: 0; 131 | z-index: 10; 132 | } 133 | 134 | .card-header .badge { 135 | margin-right: 0.3rem; 136 | } 137 | 138 | .card-header .badge-version { 139 | margin-left: 0.3rem; 140 | } 141 | 142 | .card-inner-header { 143 | padding: 0.75rem 1.25rem; 144 | } 145 | 146 | .card-method-name { 147 | color: inherit; 148 | word-break: break-all; 149 | } 150 | 151 | .card hr { 152 | border-top: 1px solid #4e5155; 153 | } 154 | 155 | .card .table { 156 | color: #acacac; 157 | border-color: #161920; 158 | } 159 | 160 | .card .table > :not(caption) > * > * { 161 | /* bootstrap 5.3 fix */ 162 | color: unset; 163 | background: unset; 164 | } 165 | 166 | .card .table .custom-control-label { 167 | color: #ddd; 168 | } 169 | 170 | .card .table th, 171 | .card .table td { 172 | border-color: #161920; 173 | } 174 | 175 | .card .table tr:nth-child(odd) td { 176 | background: #1d2027; 177 | } 178 | 179 | .attribute:hover { 180 | border-bottom-color: rgba(255, 255, 255, 0.4); 181 | } 182 | 183 | .card .btn-outline-primary, 184 | .card .form-select, 185 | .card .form-control { 186 | background-color: #232323; 187 | border: 1px solid #535354; 188 | color: #cda869; 189 | fill: currentColor; 190 | word-break: break-all; 191 | } 192 | 193 | .card .btn-outline-primary:hover { 194 | background-color: #535354; 195 | } 196 | 197 | .card .form-check-input:not(:checked) { 198 | background-color: #acacac; 199 | } 200 | 201 | .card .table .form-control { 202 | background-color: transparent; 203 | } 204 | 205 | .card .table .form-control:focus { 206 | background-color: rgba(13, 110, 253, 0.25); 207 | box-shadow: none; 208 | } 209 | 210 | .card .table .level-1 td:first-child { 211 | border-left: 10px solid #22252b; 212 | } 213 | 214 | .card .table .level-2 td:first-child { 215 | border-left: 20px solid #22252b; 216 | } 217 | 218 | .card .table .level-3 td:first-child { 219 | border-left: 30px solid #22252b; 220 | } 221 | 222 | .card .table .level-4 td:first-child { 223 | border-left: 40px solid #22252b; 224 | } 225 | 226 | .card .table .level-5 td:first-child { 227 | border-left: 50px solid #22252b; 228 | } 229 | 230 | .prefilled-key { 231 | color: #acacac; 232 | font-style: italic; 233 | } 234 | 235 | .interface-list-container + .interface-list-container { 236 | margin-top: 1rem; 237 | } 238 | 239 | .interface-group-name { 240 | margin-bottom: 0.1rem; 241 | color: #ddd; 242 | font-weight: bold; 243 | font-size: 1.2rem; 244 | } 245 | 246 | .interface-group-name::marker { 247 | color: #535354; 248 | } 249 | 250 | .interface-group-name:hover::marker, 251 | .interface-group-name:hover { 252 | color: #fff; 253 | } 254 | 255 | .interface-group-name img { 256 | border-radius: 3px; 257 | vertical-align: -5px; 258 | } 259 | 260 | .interface-list a { 261 | color: #98caff; 262 | } 263 | 264 | .interface-list a:hover { 265 | color: #fff; 266 | } 267 | 268 | .custom-control-label::before { 269 | background-color: transparent; 270 | } 271 | 272 | .no-email { 273 | color: hsl(45, 94%, 40%); 274 | background-color: hsl(46, 100%, 10%); 275 | border: 0; 276 | font-size: 13px; 277 | padding: 5px 10px; 278 | } 279 | 280 | .hidden-token, 281 | .hidden-key { 282 | font-size: 0; 283 | } 284 | 285 | .hidden-key:not(:empty):before { 286 | content: "key=is_here_but_hidden"; 287 | font-size: 1rem; 288 | color: #997433; 289 | } 290 | 291 | .hidden-token:not(:empty):before { 292 | content: "access_token=is_here_but_hidden"; 293 | font-size: 1rem; 294 | color: #997433; 295 | } 296 | 297 | .add-param-array { 298 | padding: 0; 299 | border-radius: 50%; 300 | height: 26px; 301 | width: 26px; 302 | line-height: 26px; 303 | float: right; 304 | border: 0; 305 | } 306 | 307 | .sidebar { 308 | scrollbar-gutter: stable; 309 | overflow-y: auto; 310 | scrollbar-width: thin; 311 | overscroll-behavior: contain; 312 | -webkit-overflow-scrolling: touch; 313 | } 314 | 315 | @media (max-width: 991px) { 316 | .sidebar { 317 | height: 200px; 318 | margin-top: 1.5rem; 319 | padding-top: 0 !important; 320 | padding-bottom: 0 !important; 321 | } 322 | 323 | .interface-list a { 324 | display: block; 325 | padding: 5px 0; 326 | } 327 | 328 | .header div[role="search"] { 329 | order: 2; 330 | margin-top: 0.5rem; 331 | } 332 | 333 | .card-method-name { 334 | display: block; 335 | } 336 | 337 | .value-column { 338 | min-width: 13em; 339 | } 340 | } 341 | 342 | @media (max-width: 575px) { 343 | .header .separator { 344 | display: none; 345 | } 346 | 347 | .header h1, 348 | .header h2 { 349 | display: block; 350 | font-size: 16px; 351 | line-height: 16px; 352 | } 353 | } 354 | 355 | @media (min-width: 992px) { 356 | html { 357 | scrollbar-gutter: stable; 358 | scroll-padding-top: 69px; 359 | } 360 | 361 | .header { 362 | height: 54px; 363 | position: sticky; 364 | top: 0; 365 | z-index: 100; 366 | } 367 | 368 | .sidebar { 369 | top: 54px; 370 | height: calc(100vh - 54px); 371 | position: sticky; 372 | } 373 | 374 | .card-header { 375 | position: sticky; 376 | top: 54px; 377 | } 378 | 379 | .value-column { 380 | width: 13em; 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" 4 | 5 | #disabled because it needs better check for valid apis (due to server errors?) 6 | #php generate_api_from_protos.php 7 | 8 | php generate_api.php 9 | 10 | git add -A 11 | git commit -a -m "Update Steam Web API reference" || exit 0 12 | git push 13 | -------------------------------------------------------------------------------- /vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue'; 3 | 4 | const component: ReturnType; 5 | export default component; 6 | } 7 | -------------------------------------------------------------------------------- /wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steamapi", 3 | "compatibility_date": "2025-04-01", 4 | "assets": { 5 | "directory": "./public/", 6 | "not_found_handling": "404-page" 7 | } 8 | } --------------------------------------------------------------------------------