├── .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) => { 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.27.1", 21 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 22 | "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 23 | "dev": true, 24 | "license": "MIT", 25 | "dependencies": { 26 | "@babel/helper-validator-identifier": "^7.27.1", 27 | "js-tokens": "^4.0.0", 28 | "picocolors": "^1.1.1" 29 | }, 30 | "engines": { 31 | "node": ">=6.9.0" 32 | } 33 | }, 34 | "node_modules/@babel/helper-string-parser": { 35 | "version": "7.27.1", 36 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 37 | "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 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.27.1", 46 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 47 | "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 48 | "dev": true, 49 | "license": "MIT", 50 | "engines": { 51 | "node": ">=6.9.0" 52 | } 53 | }, 54 | "node_modules/@babel/parser": { 55 | "version": "7.27.5", 56 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", 57 | "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", 58 | "dev": true, 59 | "license": "MIT", 60 | "dependencies": { 61 | "@babel/types": "^7.27.3" 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.27.6", 72 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", 73 | "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", 74 | "dev": true, 75 | "license": "MIT", 76 | "dependencies": { 77 | "@babel/helper-string-parser": "^7.27.1", 78 | "@babel/helper-validator-identifier": "^7.27.1" 79 | }, 80 | "engines": { 81 | "node": ">=6.9.0" 82 | } 83 | }, 84 | "node_modules/@biomejs/biome": { 85 | "version": "2.0.4", 86 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.0.4.tgz", 87 | "integrity": "sha512-DNA++xe+E7UugTvI/HhzSFl6OwrVgU8SIV0Mb2fPtWPk2/oTr4eOSA5xy1JECrvgJeYxurmUBOS49qxv/OUkrQ==", 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.4", 102 | "@biomejs/cli-darwin-x64": "2.0.4", 103 | "@biomejs/cli-linux-arm64": "2.0.4", 104 | "@biomejs/cli-linux-arm64-musl": "2.0.4", 105 | "@biomejs/cli-linux-x64": "2.0.4", 106 | "@biomejs/cli-linux-x64-musl": "2.0.4", 107 | "@biomejs/cli-win32-arm64": "2.0.4", 108 | "@biomejs/cli-win32-x64": "2.0.4" 109 | } 110 | }, 111 | "node_modules/@biomejs/cli-darwin-arm64": { 112 | "version": "2.0.4", 113 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.0.4.tgz", 114 | "integrity": "sha512-r5McIUMMiedwJ2rltuXhj0+w0W7IJLpkOS+OGCVZQQOOcrGY9gUSUmOo7O6Z7P0vlv5YYZkPbi+qR9MDDWRBSw==", 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.4", 130 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.0.4.tgz", 131 | "integrity": "sha512-aV5Zc/3E3aXFbrjK1IgCMEQc+6PCkBL+NS+vtjoNM2VPFeM5OL5Q82BI4YZyPnebj+k42BPIoYtz0jJ95PGRRg==", 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.4", 147 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.0.4.tgz", 148 | "integrity": "sha512-nlJhf7DyuajMj+S7Ygum59cbrHvI/nSRvedfJcEIx4X7SsiZjpRUiC5XtEn77kg7NIKq/KqG5roQIHkmjuFHCw==", 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.4", 164 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.4.tgz", 165 | "integrity": "sha512-cNukq2PthoOa7quqaKoEFz4Zd1pDPJGfTR5jVyk9Z9iFHEm6TI7+7eeIs3aYcEuuJPNFR9xhJ4Uj3E2iUWkV3A==", 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.4", 181 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.0.4.tgz", 182 | "integrity": "sha512-jlzrNZ+OzN9wvp2RL3cl5Y4NiV7xSU+QV5A8bWXke1on3jKy7QbXajybSjVQ6aFw1gdrqkO/W8xV5HODhIMT4g==", 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.4", 198 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.4.tgz", 199 | "integrity": "sha512-oWQALSbp8xF0t/wiHU2zdkZOpIHyaI9QxQv0Ytty9GAKsCGP6pczp8qyKD/P49iGJdDozHp5KiuQPxs33APhyA==", 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.4", 215 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.0.4.tgz", 216 | "integrity": "sha512-/PbNhMJo9ONja7hOxLlifM/qgeHpRD9bF2flTz5KIrXnQqpuegaRuwP/HYdJ9TFkTKFjHkPLoE4onOz3HIT5CQ==", 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.4", 232 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.0.4.tgz", 233 | "integrity": "sha512-dIM4SgO4/Rmsb4X7fwKtciQ682SZDSC1lm42uSM9gt8zNqBIeTaqsMc6eO1DpxYWMlAb/n2SML9+HUHmCib7NA==", 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.5", 307 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", 308 | "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", 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.5", 324 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", 325 | "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", 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.5", 341 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", 342 | "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", 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.5", 358 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", 359 | "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", 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.5", 375 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", 376 | "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", 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.5", 392 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", 393 | "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", 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.5", 409 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", 410 | "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", 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.5", 426 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", 427 | "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", 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.5", 443 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", 444 | "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 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.5", 460 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", 461 | "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", 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.5", 477 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", 478 | "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", 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.5", 494 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", 495 | "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 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.5", 511 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", 512 | "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", 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.5", 528 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", 529 | "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", 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.5", 545 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", 546 | "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", 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.5", 562 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", 563 | "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", 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.5", 579 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", 580 | "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", 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.5", 596 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", 597 | "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", 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.5", 613 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", 614 | "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", 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.5", 630 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", 631 | "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", 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.5", 647 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", 648 | "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", 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.5", 664 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", 665 | "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", 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.5", 681 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", 682 | "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", 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.5", 698 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", 699 | "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", 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.5", 715 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", 716 | "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", 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.17", 764 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", 765 | "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", 766 | "dev": true, 767 | "license": "MIT", 768 | "dependencies": { 769 | "@babel/parser": "^7.27.5", 770 | "@vue/shared": "3.5.17", 771 | "entities": "^4.5.0", 772 | "estree-walker": "^2.0.2", 773 | "source-map-js": "^1.2.1" 774 | } 775 | }, 776 | "node_modules/@vue/compiler-core/node_modules/entities": { 777 | "version": "4.5.0", 778 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 779 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 780 | "dev": true, 781 | "license": "BSD-2-Clause", 782 | "engines": { 783 | "node": ">=0.12" 784 | }, 785 | "funding": { 786 | "url": "https://github.com/fb55/entities?sponsor=1" 787 | } 788 | }, 789 | "node_modules/@vue/compiler-dom": { 790 | "version": "3.5.17", 791 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", 792 | "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", 793 | "dev": true, 794 | "license": "MIT", 795 | "dependencies": { 796 | "@vue/compiler-core": "3.5.17", 797 | "@vue/shared": "3.5.17" 798 | } 799 | }, 800 | "node_modules/@vue/compiler-sfc": { 801 | "version": "3.5.17", 802 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", 803 | "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", 804 | "dev": true, 805 | "license": "MIT", 806 | "dependencies": { 807 | "@babel/parser": "^7.27.5", 808 | "@vue/compiler-core": "3.5.17", 809 | "@vue/compiler-dom": "3.5.17", 810 | "@vue/compiler-ssr": "3.5.17", 811 | "@vue/shared": "3.5.17", 812 | "estree-walker": "^2.0.2", 813 | "magic-string": "^0.30.17", 814 | "postcss": "^8.5.6", 815 | "source-map-js": "^1.2.1" 816 | } 817 | }, 818 | "node_modules/@vue/compiler-ssr": { 819 | "version": "3.5.17", 820 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", 821 | "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", 822 | "dev": true, 823 | "license": "MIT", 824 | "dependencies": { 825 | "@vue/compiler-dom": "3.5.17", 826 | "@vue/shared": "3.5.17" 827 | } 828 | }, 829 | "node_modules/@vue/reactivity": { 830 | "version": "3.5.17", 831 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", 832 | "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", 833 | "dev": true, 834 | "license": "MIT", 835 | "dependencies": { 836 | "@vue/shared": "3.5.17" 837 | } 838 | }, 839 | "node_modules/@vue/runtime-core": { 840 | "version": "3.5.17", 841 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", 842 | "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", 843 | "dev": true, 844 | "license": "MIT", 845 | "dependencies": { 846 | "@vue/reactivity": "3.5.17", 847 | "@vue/shared": "3.5.17" 848 | } 849 | }, 850 | "node_modules/@vue/runtime-dom": { 851 | "version": "3.5.17", 852 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", 853 | "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", 854 | "dev": true, 855 | "license": "MIT", 856 | "dependencies": { 857 | "@vue/reactivity": "3.5.17", 858 | "@vue/runtime-core": "3.5.17", 859 | "@vue/shared": "3.5.17", 860 | "csstype": "^3.1.3" 861 | } 862 | }, 863 | "node_modules/@vue/server-renderer": { 864 | "version": "3.5.17", 865 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", 866 | "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", 867 | "dev": true, 868 | "license": "MIT", 869 | "dependencies": { 870 | "@vue/compiler-ssr": "3.5.17", 871 | "@vue/shared": "3.5.17" 872 | }, 873 | "peerDependencies": { 874 | "vue": "3.5.17" 875 | } 876 | }, 877 | "node_modules/@vue/shared": { 878 | "version": "3.5.17", 879 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", 880 | "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", 881 | "dev": true, 882 | "license": "MIT" 883 | }, 884 | "node_modules/@vue/tsconfig": { 885 | "version": "0.7.0", 886 | "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", 887 | "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", 888 | "dev": true, 889 | "license": "MIT", 890 | "peerDependencies": { 891 | "typescript": "5.x", 892 | "vue": "^3.4.0" 893 | }, 894 | "peerDependenciesMeta": { 895 | "typescript": { 896 | "optional": true 897 | }, 898 | "vue": { 899 | "optional": true 900 | } 901 | } 902 | }, 903 | "node_modules/argparse": { 904 | "version": "2.0.1", 905 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 906 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 907 | "dev": true, 908 | "license": "Python-2.0" 909 | }, 910 | "node_modules/bootstrap": { 911 | "version": "5.3.7", 912 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz", 913 | "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==", 914 | "dev": true, 915 | "funding": [ 916 | { 917 | "type": "github", 918 | "url": "https://github.com/sponsors/twbs" 919 | }, 920 | { 921 | "type": "opencollective", 922 | "url": "https://opencollective.com/bootstrap" 923 | } 924 | ], 925 | "license": "MIT", 926 | "peerDependencies": { 927 | "@popperjs/core": "^2.11.8" 928 | } 929 | }, 930 | "node_modules/callsites": { 931 | "version": "3.1.0", 932 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 933 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 934 | "dev": true, 935 | "license": "MIT", 936 | "engines": { 937 | "node": ">=6" 938 | } 939 | }, 940 | "node_modules/cosmiconfig": { 941 | "version": "9.0.0", 942 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", 943 | "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", 944 | "dev": true, 945 | "license": "MIT", 946 | "dependencies": { 947 | "env-paths": "^2.2.1", 948 | "import-fresh": "^3.3.0", 949 | "js-yaml": "^4.1.0", 950 | "parse-json": "^5.2.0" 951 | }, 952 | "engines": { 953 | "node": ">=14" 954 | }, 955 | "funding": { 956 | "url": "https://github.com/sponsors/d-fischer" 957 | }, 958 | "peerDependencies": { 959 | "typescript": ">=4.9.5" 960 | }, 961 | "peerDependenciesMeta": { 962 | "typescript": { 963 | "optional": true 964 | } 965 | } 966 | }, 967 | "node_modules/csstype": { 968 | "version": "3.1.3", 969 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 970 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 971 | "dev": true, 972 | "license": "MIT" 973 | }, 974 | "node_modules/detect-libc": { 975 | "version": "1.0.3", 976 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 977 | "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", 978 | "dev": true, 979 | "license": "Apache-2.0", 980 | "bin": { 981 | "detect-libc": "bin/detect-libc.js" 982 | }, 983 | "engines": { 984 | "node": ">=0.10" 985 | } 986 | }, 987 | "node_modules/dom-serializer": { 988 | "version": "1.4.1", 989 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", 990 | "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", 991 | "dev": true, 992 | "license": "MIT", 993 | "dependencies": { 994 | "domelementtype": "^2.0.1", 995 | "domhandler": "^4.2.0", 996 | "entities": "^2.0.0" 997 | }, 998 | "funding": { 999 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 1000 | } 1001 | }, 1002 | "node_modules/dom-serializer/node_modules/entities": { 1003 | "version": "2.2.0", 1004 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", 1005 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", 1006 | "dev": true, 1007 | "license": "BSD-2-Clause", 1008 | "funding": { 1009 | "url": "https://github.com/fb55/entities?sponsor=1" 1010 | } 1011 | }, 1012 | "node_modules/domelementtype": { 1013 | "version": "2.3.0", 1014 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 1015 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 1016 | "dev": true, 1017 | "funding": [ 1018 | { 1019 | "type": "github", 1020 | "url": "https://github.com/sponsors/fb55" 1021 | } 1022 | ], 1023 | "license": "BSD-2-Clause" 1024 | }, 1025 | "node_modules/domhandler": { 1026 | "version": "4.3.1", 1027 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", 1028 | "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", 1029 | "dev": true, 1030 | "license": "BSD-2-Clause", 1031 | "dependencies": { 1032 | "domelementtype": "^2.2.0" 1033 | }, 1034 | "engines": { 1035 | "node": ">= 4" 1036 | }, 1037 | "funding": { 1038 | "url": "https://github.com/fb55/domhandler?sponsor=1" 1039 | } 1040 | }, 1041 | "node_modules/domutils": { 1042 | "version": "2.8.0", 1043 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", 1044 | "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", 1045 | "dev": true, 1046 | "license": "BSD-2-Clause", 1047 | "dependencies": { 1048 | "dom-serializer": "^1.0.1", 1049 | "domelementtype": "^2.2.0", 1050 | "domhandler": "^4.2.0" 1051 | }, 1052 | "funding": { 1053 | "url": "https://github.com/fb55/domutils?sponsor=1" 1054 | } 1055 | }, 1056 | "node_modules/entities": { 1057 | "version": "3.0.1", 1058 | "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", 1059 | "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", 1060 | "dev": true, 1061 | "license": "BSD-2-Clause", 1062 | "engines": { 1063 | "node": ">=0.12" 1064 | }, 1065 | "funding": { 1066 | "url": "https://github.com/fb55/entities?sponsor=1" 1067 | } 1068 | }, 1069 | "node_modules/env-paths": { 1070 | "version": "2.2.1", 1071 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", 1072 | "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", 1073 | "dev": true, 1074 | "license": "MIT", 1075 | "engines": { 1076 | "node": ">=6" 1077 | } 1078 | }, 1079 | "node_modules/error-ex": { 1080 | "version": "1.3.2", 1081 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 1082 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 1083 | "dev": true, 1084 | "license": "MIT", 1085 | "dependencies": { 1086 | "is-arrayish": "^0.2.1" 1087 | } 1088 | }, 1089 | "node_modules/esbuild": { 1090 | "version": "0.25.5", 1091 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", 1092 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 1093 | "dev": true, 1094 | "hasInstallScript": true, 1095 | "license": "MIT", 1096 | "bin": { 1097 | "esbuild": "bin/esbuild" 1098 | }, 1099 | "engines": { 1100 | "node": ">=18" 1101 | }, 1102 | "optionalDependencies": { 1103 | "@esbuild/aix-ppc64": "0.25.5", 1104 | "@esbuild/android-arm": "0.25.5", 1105 | "@esbuild/android-arm64": "0.25.5", 1106 | "@esbuild/android-x64": "0.25.5", 1107 | "@esbuild/darwin-arm64": "0.25.5", 1108 | "@esbuild/darwin-x64": "0.25.5", 1109 | "@esbuild/freebsd-arm64": "0.25.5", 1110 | "@esbuild/freebsd-x64": "0.25.5", 1111 | "@esbuild/linux-arm": "0.25.5", 1112 | "@esbuild/linux-arm64": "0.25.5", 1113 | "@esbuild/linux-ia32": "0.25.5", 1114 | "@esbuild/linux-loong64": "0.25.5", 1115 | "@esbuild/linux-mips64el": "0.25.5", 1116 | "@esbuild/linux-ppc64": "0.25.5", 1117 | "@esbuild/linux-riscv64": "0.25.5", 1118 | "@esbuild/linux-s390x": "0.25.5", 1119 | "@esbuild/linux-x64": "0.25.5", 1120 | "@esbuild/netbsd-arm64": "0.25.5", 1121 | "@esbuild/netbsd-x64": "0.25.5", 1122 | "@esbuild/openbsd-arm64": "0.25.5", 1123 | "@esbuild/openbsd-x64": "0.25.5", 1124 | "@esbuild/sunos-x64": "0.25.5", 1125 | "@esbuild/win32-arm64": "0.25.5", 1126 | "@esbuild/win32-ia32": "0.25.5", 1127 | "@esbuild/win32-x64": "0.25.5" 1128 | } 1129 | }, 1130 | "node_modules/esbuild-android-64": { 1131 | "version": "0.14.54", 1132 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", 1133 | "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", 1134 | "cpu": [ 1135 | "x64" 1136 | ], 1137 | "dev": true, 1138 | "license": "MIT", 1139 | "optional": true, 1140 | "os": [ 1141 | "android" 1142 | ], 1143 | "engines": { 1144 | "node": ">=12" 1145 | } 1146 | }, 1147 | "node_modules/esbuild-android-arm64": { 1148 | "version": "0.14.54", 1149 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", 1150 | "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", 1151 | "cpu": [ 1152 | "arm64" 1153 | ], 1154 | "dev": true, 1155 | "license": "MIT", 1156 | "optional": true, 1157 | "os": [ 1158 | "android" 1159 | ], 1160 | "engines": { 1161 | "node": ">=12" 1162 | } 1163 | }, 1164 | "node_modules/esbuild-darwin-64": { 1165 | "version": "0.14.54", 1166 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", 1167 | "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", 1168 | "cpu": [ 1169 | "x64" 1170 | ], 1171 | "dev": true, 1172 | "license": "MIT", 1173 | "optional": true, 1174 | "os": [ 1175 | "darwin" 1176 | ], 1177 | "engines": { 1178 | "node": ">=12" 1179 | } 1180 | }, 1181 | "node_modules/esbuild-darwin-arm64": { 1182 | "version": "0.14.54", 1183 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", 1184 | "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", 1185 | "cpu": [ 1186 | "arm64" 1187 | ], 1188 | "dev": true, 1189 | "license": "MIT", 1190 | "optional": true, 1191 | "os": [ 1192 | "darwin" 1193 | ], 1194 | "engines": { 1195 | "node": ">=12" 1196 | } 1197 | }, 1198 | "node_modules/esbuild-freebsd-64": { 1199 | "version": "0.14.54", 1200 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", 1201 | "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", 1202 | "cpu": [ 1203 | "x64" 1204 | ], 1205 | "dev": true, 1206 | "license": "MIT", 1207 | "optional": true, 1208 | "os": [ 1209 | "freebsd" 1210 | ], 1211 | "engines": { 1212 | "node": ">=12" 1213 | } 1214 | }, 1215 | "node_modules/esbuild-freebsd-arm64": { 1216 | "version": "0.14.54", 1217 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", 1218 | "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", 1219 | "cpu": [ 1220 | "arm64" 1221 | ], 1222 | "dev": true, 1223 | "license": "MIT", 1224 | "optional": true, 1225 | "os": [ 1226 | "freebsd" 1227 | ], 1228 | "engines": { 1229 | "node": ">=12" 1230 | } 1231 | }, 1232 | "node_modules/esbuild-linux-32": { 1233 | "version": "0.14.54", 1234 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", 1235 | "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", 1236 | "cpu": [ 1237 | "ia32" 1238 | ], 1239 | "dev": true, 1240 | "license": "MIT", 1241 | "optional": true, 1242 | "os": [ 1243 | "linux" 1244 | ], 1245 | "engines": { 1246 | "node": ">=12" 1247 | } 1248 | }, 1249 | "node_modules/esbuild-linux-64": { 1250 | "version": "0.14.54", 1251 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", 1252 | "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", 1253 | "cpu": [ 1254 | "x64" 1255 | ], 1256 | "dev": true, 1257 | "license": "MIT", 1258 | "optional": true, 1259 | "os": [ 1260 | "linux" 1261 | ], 1262 | "engines": { 1263 | "node": ">=12" 1264 | } 1265 | }, 1266 | "node_modules/esbuild-linux-arm": { 1267 | "version": "0.14.54", 1268 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", 1269 | "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", 1270 | "cpu": [ 1271 | "arm" 1272 | ], 1273 | "dev": true, 1274 | "license": "MIT", 1275 | "optional": true, 1276 | "os": [ 1277 | "linux" 1278 | ], 1279 | "engines": { 1280 | "node": ">=12" 1281 | } 1282 | }, 1283 | "node_modules/esbuild-linux-arm64": { 1284 | "version": "0.14.54", 1285 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", 1286 | "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", 1287 | "cpu": [ 1288 | "arm64" 1289 | ], 1290 | "dev": true, 1291 | "license": "MIT", 1292 | "optional": true, 1293 | "os": [ 1294 | "linux" 1295 | ], 1296 | "engines": { 1297 | "node": ">=12" 1298 | } 1299 | }, 1300 | "node_modules/esbuild-linux-mips64le": { 1301 | "version": "0.14.54", 1302 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", 1303 | "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", 1304 | "cpu": [ 1305 | "mips64el" 1306 | ], 1307 | "dev": true, 1308 | "license": "MIT", 1309 | "optional": true, 1310 | "os": [ 1311 | "linux" 1312 | ], 1313 | "engines": { 1314 | "node": ">=12" 1315 | } 1316 | }, 1317 | "node_modules/esbuild-linux-ppc64le": { 1318 | "version": "0.14.54", 1319 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", 1320 | "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", 1321 | "cpu": [ 1322 | "ppc64" 1323 | ], 1324 | "dev": true, 1325 | "license": "MIT", 1326 | "optional": true, 1327 | "os": [ 1328 | "linux" 1329 | ], 1330 | "engines": { 1331 | "node": ">=12" 1332 | } 1333 | }, 1334 | "node_modules/esbuild-linux-riscv64": { 1335 | "version": "0.14.54", 1336 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", 1337 | "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", 1338 | "cpu": [ 1339 | "riscv64" 1340 | ], 1341 | "dev": true, 1342 | "license": "MIT", 1343 | "optional": true, 1344 | "os": [ 1345 | "linux" 1346 | ], 1347 | "engines": { 1348 | "node": ">=12" 1349 | } 1350 | }, 1351 | "node_modules/esbuild-linux-s390x": { 1352 | "version": "0.14.54", 1353 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", 1354 | "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", 1355 | "cpu": [ 1356 | "s390x" 1357 | ], 1358 | "dev": true, 1359 | "license": "MIT", 1360 | "optional": true, 1361 | "os": [ 1362 | "linux" 1363 | ], 1364 | "engines": { 1365 | "node": ">=12" 1366 | } 1367 | }, 1368 | "node_modules/esbuild-netbsd-64": { 1369 | "version": "0.14.54", 1370 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", 1371 | "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", 1372 | "cpu": [ 1373 | "x64" 1374 | ], 1375 | "dev": true, 1376 | "license": "MIT", 1377 | "optional": true, 1378 | "os": [ 1379 | "netbsd" 1380 | ], 1381 | "engines": { 1382 | "node": ">=12" 1383 | } 1384 | }, 1385 | "node_modules/esbuild-openbsd-64": { 1386 | "version": "0.14.54", 1387 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", 1388 | "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", 1389 | "cpu": [ 1390 | "x64" 1391 | ], 1392 | "dev": true, 1393 | "license": "MIT", 1394 | "optional": true, 1395 | "os": [ 1396 | "openbsd" 1397 | ], 1398 | "engines": { 1399 | "node": ">=12" 1400 | } 1401 | }, 1402 | "node_modules/esbuild-plugin-vue3": { 1403 | "version": "0.4.2", 1404 | "resolved": "https://registry.npmjs.org/esbuild-plugin-vue3/-/esbuild-plugin-vue3-0.4.2.tgz", 1405 | "integrity": "sha512-edaghOAJY+26uIJVywkT0cyUxWu/oi2+dGe2KePyHAJ9y6hAB4fqNnY5SFpZY9G6knERZo4Nykp/YOcKML06rA==", 1406 | "dev": true, 1407 | "license": "MIT", 1408 | "dependencies": { 1409 | "esbuild": "^0.14.8", 1410 | "typescript": "^4.7.4" 1411 | }, 1412 | "peerDependencies": { 1413 | "cheerio": "^1.0.0-rc.10", 1414 | "html-minifier": "^4.0.0", 1415 | "pug": "^3.0.2", 1416 | "sass": "^1.35.2", 1417 | "vue": "^3.4.15" 1418 | }, 1419 | "peerDependenciesMeta": { 1420 | "cheerio": { 1421 | "optional": true 1422 | }, 1423 | "html-minifier": { 1424 | "optional": true 1425 | }, 1426 | "pug": { 1427 | "optional": true 1428 | }, 1429 | "sass": { 1430 | "optional": true 1431 | } 1432 | } 1433 | }, 1434 | "node_modules/esbuild-plugin-vue3/node_modules/@esbuild/linux-loong64": { 1435 | "version": "0.14.54", 1436 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", 1437 | "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", 1438 | "cpu": [ 1439 | "loong64" 1440 | ], 1441 | "dev": true, 1442 | "license": "MIT", 1443 | "optional": true, 1444 | "os": [ 1445 | "linux" 1446 | ], 1447 | "engines": { 1448 | "node": ">=12" 1449 | } 1450 | }, 1451 | "node_modules/esbuild-plugin-vue3/node_modules/esbuild": { 1452 | "version": "0.14.54", 1453 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", 1454 | "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", 1455 | "dev": true, 1456 | "hasInstallScript": true, 1457 | "license": "MIT", 1458 | "bin": { 1459 | "esbuild": "bin/esbuild" 1460 | }, 1461 | "engines": { 1462 | "node": ">=12" 1463 | }, 1464 | "optionalDependencies": { 1465 | "@esbuild/linux-loong64": "0.14.54", 1466 | "esbuild-android-64": "0.14.54", 1467 | "esbuild-android-arm64": "0.14.54", 1468 | "esbuild-darwin-64": "0.14.54", 1469 | "esbuild-darwin-arm64": "0.14.54", 1470 | "esbuild-freebsd-64": "0.14.54", 1471 | "esbuild-freebsd-arm64": "0.14.54", 1472 | "esbuild-linux-32": "0.14.54", 1473 | "esbuild-linux-64": "0.14.54", 1474 | "esbuild-linux-arm": "0.14.54", 1475 | "esbuild-linux-arm64": "0.14.54", 1476 | "esbuild-linux-mips64le": "0.14.54", 1477 | "esbuild-linux-ppc64le": "0.14.54", 1478 | "esbuild-linux-riscv64": "0.14.54", 1479 | "esbuild-linux-s390x": "0.14.54", 1480 | "esbuild-netbsd-64": "0.14.54", 1481 | "esbuild-openbsd-64": "0.14.54", 1482 | "esbuild-sunos-64": "0.14.54", 1483 | "esbuild-windows-32": "0.14.54", 1484 | "esbuild-windows-64": "0.14.54", 1485 | "esbuild-windows-arm64": "0.14.54" 1486 | } 1487 | }, 1488 | "node_modules/esbuild-plugin-vue3/node_modules/typescript": { 1489 | "version": "4.9.5", 1490 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 1491 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 1492 | "dev": true, 1493 | "license": "Apache-2.0", 1494 | "bin": { 1495 | "tsc": "bin/tsc", 1496 | "tsserver": "bin/tsserver" 1497 | }, 1498 | "engines": { 1499 | "node": ">=4.2.0" 1500 | } 1501 | }, 1502 | "node_modules/esbuild-sunos-64": { 1503 | "version": "0.14.54", 1504 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", 1505 | "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", 1506 | "cpu": [ 1507 | "x64" 1508 | ], 1509 | "dev": true, 1510 | "license": "MIT", 1511 | "optional": true, 1512 | "os": [ 1513 | "sunos" 1514 | ], 1515 | "engines": { 1516 | "node": ">=12" 1517 | } 1518 | }, 1519 | "node_modules/esbuild-windows-32": { 1520 | "version": "0.14.54", 1521 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", 1522 | "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", 1523 | "cpu": [ 1524 | "ia32" 1525 | ], 1526 | "dev": true, 1527 | "license": "MIT", 1528 | "optional": true, 1529 | "os": [ 1530 | "win32" 1531 | ], 1532 | "engines": { 1533 | "node": ">=12" 1534 | } 1535 | }, 1536 | "node_modules/esbuild-windows-64": { 1537 | "version": "0.14.54", 1538 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", 1539 | "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", 1540 | "cpu": [ 1541 | "x64" 1542 | ], 1543 | "dev": true, 1544 | "license": "MIT", 1545 | "optional": true, 1546 | "os": [ 1547 | "win32" 1548 | ], 1549 | "engines": { 1550 | "node": ">=12" 1551 | } 1552 | }, 1553 | "node_modules/esbuild-windows-arm64": { 1554 | "version": "0.14.54", 1555 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", 1556 | "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", 1557 | "cpu": [ 1558 | "arm64" 1559 | ], 1560 | "dev": true, 1561 | "license": "MIT", 1562 | "optional": true, 1563 | "os": [ 1564 | "win32" 1565 | ], 1566 | "engines": { 1567 | "node": ">=12" 1568 | } 1569 | }, 1570 | "node_modules/estree-walker": { 1571 | "version": "2.0.2", 1572 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1573 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1574 | "dev": true, 1575 | "license": "MIT" 1576 | }, 1577 | "node_modules/htmlnano": { 1578 | "version": "2.1.2", 1579 | "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz", 1580 | "integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==", 1581 | "dev": true, 1582 | "license": "MIT", 1583 | "dependencies": { 1584 | "cosmiconfig": "^9.0.0", 1585 | "posthtml": "^0.16.5" 1586 | }, 1587 | "peerDependencies": { 1588 | "cssnano": "^7.0.0", 1589 | "postcss": "^8.3.11", 1590 | "purgecss": "^7.0.2", 1591 | "relateurl": "^0.2.7", 1592 | "srcset": "5.0.1", 1593 | "svgo": "^3.0.2", 1594 | "terser": "^5.10.0", 1595 | "uncss": "^0.17.3" 1596 | }, 1597 | "peerDependenciesMeta": { 1598 | "cssnano": { 1599 | "optional": true 1600 | }, 1601 | "postcss": { 1602 | "optional": true 1603 | }, 1604 | "purgecss": { 1605 | "optional": true 1606 | }, 1607 | "relateurl": { 1608 | "optional": true 1609 | }, 1610 | "srcset": { 1611 | "optional": true 1612 | }, 1613 | "svgo": { 1614 | "optional": true 1615 | }, 1616 | "terser": { 1617 | "optional": true 1618 | }, 1619 | "uncss": { 1620 | "optional": true 1621 | } 1622 | } 1623 | }, 1624 | "node_modules/htmlparser2": { 1625 | "version": "7.2.0", 1626 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", 1627 | "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", 1628 | "dev": true, 1629 | "funding": [ 1630 | "https://github.com/fb55/htmlparser2?sponsor=1", 1631 | { 1632 | "type": "github", 1633 | "url": "https://github.com/sponsors/fb55" 1634 | } 1635 | ], 1636 | "license": "MIT", 1637 | "dependencies": { 1638 | "domelementtype": "^2.0.1", 1639 | "domhandler": "^4.2.2", 1640 | "domutils": "^2.8.0", 1641 | "entities": "^3.0.1" 1642 | } 1643 | }, 1644 | "node_modules/import-fresh": { 1645 | "version": "3.3.1", 1646 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 1647 | "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 1648 | "dev": true, 1649 | "license": "MIT", 1650 | "dependencies": { 1651 | "parent-module": "^1.0.0", 1652 | "resolve-from": "^4.0.0" 1653 | }, 1654 | "engines": { 1655 | "node": ">=6" 1656 | }, 1657 | "funding": { 1658 | "url": "https://github.com/sponsors/sindresorhus" 1659 | } 1660 | }, 1661 | "node_modules/is-arrayish": { 1662 | "version": "0.2.1", 1663 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1664 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", 1665 | "dev": true, 1666 | "license": "MIT" 1667 | }, 1668 | "node_modules/is-json": { 1669 | "version": "2.0.1", 1670 | "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", 1671 | "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", 1672 | "dev": true, 1673 | "license": "ISC" 1674 | }, 1675 | "node_modules/js-tokens": { 1676 | "version": "4.0.0", 1677 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1678 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1679 | "dev": true, 1680 | "license": "MIT" 1681 | }, 1682 | "node_modules/js-yaml": { 1683 | "version": "4.1.0", 1684 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1685 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1686 | "dev": true, 1687 | "license": "MIT", 1688 | "dependencies": { 1689 | "argparse": "^2.0.1" 1690 | }, 1691 | "bin": { 1692 | "js-yaml": "bin/js-yaml.js" 1693 | } 1694 | }, 1695 | "node_modules/json-parse-even-better-errors": { 1696 | "version": "2.3.1", 1697 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 1698 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 1699 | "dev": true, 1700 | "license": "MIT" 1701 | }, 1702 | "node_modules/lines-and-columns": { 1703 | "version": "1.2.4", 1704 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 1705 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 1706 | "dev": true, 1707 | "license": "MIT" 1708 | }, 1709 | "node_modules/magic-string": { 1710 | "version": "0.30.17", 1711 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1712 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1713 | "dev": true, 1714 | "license": "MIT", 1715 | "dependencies": { 1716 | "@jridgewell/sourcemap-codec": "^1.5.0" 1717 | } 1718 | }, 1719 | "node_modules/nanoid": { 1720 | "version": "3.3.11", 1721 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1722 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1723 | "dev": true, 1724 | "funding": [ 1725 | { 1726 | "type": "github", 1727 | "url": "https://github.com/sponsors/ai" 1728 | } 1729 | ], 1730 | "license": "MIT", 1731 | "bin": { 1732 | "nanoid": "bin/nanoid.cjs" 1733 | }, 1734 | "engines": { 1735 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1736 | } 1737 | }, 1738 | "node_modules/parent-module": { 1739 | "version": "1.0.1", 1740 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1741 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1742 | "dev": true, 1743 | "license": "MIT", 1744 | "dependencies": { 1745 | "callsites": "^3.0.0" 1746 | }, 1747 | "engines": { 1748 | "node": ">=6" 1749 | } 1750 | }, 1751 | "node_modules/parse-json": { 1752 | "version": "5.2.0", 1753 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 1754 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 1755 | "dev": true, 1756 | "license": "MIT", 1757 | "dependencies": { 1758 | "@babel/code-frame": "^7.0.0", 1759 | "error-ex": "^1.3.1", 1760 | "json-parse-even-better-errors": "^2.3.0", 1761 | "lines-and-columns": "^1.1.6" 1762 | }, 1763 | "engines": { 1764 | "node": ">=8" 1765 | }, 1766 | "funding": { 1767 | "url": "https://github.com/sponsors/sindresorhus" 1768 | } 1769 | }, 1770 | "node_modules/picocolors": { 1771 | "version": "1.1.1", 1772 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1773 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1774 | "dev": true, 1775 | "license": "ISC" 1776 | }, 1777 | "node_modules/postcss": { 1778 | "version": "8.5.6", 1779 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 1780 | "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 1781 | "dev": true, 1782 | "funding": [ 1783 | { 1784 | "type": "opencollective", 1785 | "url": "https://opencollective.com/postcss/" 1786 | }, 1787 | { 1788 | "type": "tidelift", 1789 | "url": "https://tidelift.com/funding/github/npm/postcss" 1790 | }, 1791 | { 1792 | "type": "github", 1793 | "url": "https://github.com/sponsors/ai" 1794 | } 1795 | ], 1796 | "license": "MIT", 1797 | "dependencies": { 1798 | "nanoid": "^3.3.11", 1799 | "picocolors": "^1.1.1", 1800 | "source-map-js": "^1.2.1" 1801 | }, 1802 | "engines": { 1803 | "node": "^10 || ^12 || >=14" 1804 | } 1805 | }, 1806 | "node_modules/posthtml": { 1807 | "version": "0.16.6", 1808 | "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", 1809 | "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", 1810 | "dev": true, 1811 | "license": "MIT", 1812 | "dependencies": { 1813 | "posthtml-parser": "^0.11.0", 1814 | "posthtml-render": "^3.0.0" 1815 | }, 1816 | "engines": { 1817 | "node": ">=12.0.0" 1818 | } 1819 | }, 1820 | "node_modules/posthtml-parser": { 1821 | "version": "0.11.0", 1822 | "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", 1823 | "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", 1824 | "dev": true, 1825 | "license": "MIT", 1826 | "dependencies": { 1827 | "htmlparser2": "^7.1.1" 1828 | }, 1829 | "engines": { 1830 | "node": ">=12" 1831 | } 1832 | }, 1833 | "node_modules/posthtml-render": { 1834 | "version": "3.0.0", 1835 | "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", 1836 | "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", 1837 | "dev": true, 1838 | "license": "MIT", 1839 | "dependencies": { 1840 | "is-json": "^2.0.1" 1841 | }, 1842 | "engines": { 1843 | "node": ">=12" 1844 | } 1845 | }, 1846 | "node_modules/resolve-from": { 1847 | "version": "4.0.0", 1848 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1849 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1850 | "dev": true, 1851 | "license": "MIT", 1852 | "engines": { 1853 | "node": ">=4" 1854 | } 1855 | }, 1856 | "node_modules/source-map-js": { 1857 | "version": "1.2.1", 1858 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1859 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1860 | "dev": true, 1861 | "license": "BSD-3-Clause", 1862 | "engines": { 1863 | "node": ">=0.10.0" 1864 | } 1865 | }, 1866 | "node_modules/typescript": { 1867 | "version": "5.8.3", 1868 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 1869 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 1870 | "dev": true, 1871 | "license": "Apache-2.0", 1872 | "bin": { 1873 | "tsc": "bin/tsc", 1874 | "tsserver": "bin/tsserver" 1875 | }, 1876 | "engines": { 1877 | "node": ">=14.17" 1878 | } 1879 | }, 1880 | "node_modules/vue": { 1881 | "version": "3.5.17", 1882 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", 1883 | "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", 1884 | "dev": true, 1885 | "license": "MIT", 1886 | "dependencies": { 1887 | "@vue/compiler-dom": "3.5.17", 1888 | "@vue/compiler-sfc": "3.5.17", 1889 | "@vue/runtime-dom": "3.5.17", 1890 | "@vue/server-renderer": "3.5.17", 1891 | "@vue/shared": "3.5.17" 1892 | }, 1893 | "peerDependencies": { 1894 | "typescript": "*" 1895 | }, 1896 | "peerDependenciesMeta": { 1897 | "typescript": { 1898 | "optional": true 1899 | } 1900 | } 1901 | } 1902 | } 1903 | } 1904 | -------------------------------------------------------------------------------- /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/7f9352c3b34d1a200f54eb298369477d0a2e701b/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/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/512.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/artifact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/artifact.jpg -------------------------------------------------------------------------------- /public/icons/cs2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/cs2.jpg -------------------------------------------------------------------------------- /public/icons/deadlock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/deadlock.jpg -------------------------------------------------------------------------------- /public/icons/dota.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/dota.jpg -------------------------------------------------------------------------------- /public/icons/portal2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/portal2.jpg -------------------------------------------------------------------------------- /public/icons/steam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/steam.jpg -------------------------------------------------------------------------------- /public/icons/tf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/public/icons/tf.jpg -------------------------------------------------------------------------------- /public/icons/underlords.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xPaw/SteamWebAPIDocumentation/7f9352c3b34d1a200f54eb298369477d0a2e701b/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 | [3488080, { name: 'Deadlock Experimental', icon: 'deadlock.jpg', open: false, methods: {} }], 41 | [3781850, { name: 'Deadlock Unknown', icon: 'deadlock.jpg', open: false, methods: {} }], 42 | ], 43 | ); 44 | 45 | const steamGroup = groupsData.get(0)!; 46 | 47 | for (const interfaceName in interfaces) { 48 | const interfaceAppid = interfaceName.match(/_(?[0-9]+)$/); 49 | 50 | if (interfaceAppid) { 51 | const appid = parseInt(interfaceAppid.groups!.appid, 10); 52 | 53 | groupsMap.set(interfaceName, appid); 54 | 55 | let group = groupsData.get(appid); 56 | 57 | if (!group) { 58 | group = { 59 | name: `App ${appid}`, 60 | icon: 'steam.jpg', 61 | open: false, 62 | methods: {}, 63 | }; 64 | 65 | groupsData.set(appid, group); 66 | } 67 | 68 | group.methods[interfaceName] = interfaces[interfaceName]; 69 | } else { 70 | steamGroup.methods[interfaceName] = interfaces[interfaceName]; 71 | } 72 | 73 | for (const methodName in interfaces[interfaceName]) { 74 | const method = interfaces[interfaceName][methodName]; 75 | 76 | for (const parameter of method.parameters) { 77 | parameter._value = ''; 78 | 79 | if (parameter.type === 'bool') { 80 | parameter.manuallyToggled = false; 81 | } 82 | } 83 | } 84 | } 85 | 86 | return { 87 | userData: { 88 | webapi_key: '', 89 | access_token: '', 90 | steamid: '', 91 | format: 'json', 92 | favorites: new Set(), 93 | }, 94 | skipNextHashChange: false, 95 | keyInputType: 'password', 96 | hasValidWebApiKey: false, 97 | hasValidAccessToken: false, 98 | accessTokenExpiration: 0, 99 | accessTokenSteamId: null, 100 | accessTokenAudience: [], 101 | accessTokenVisible: false, 102 | currentFilter: '', 103 | currentInterface: '', 104 | search: markRaw(new ApiSearcher(interfaces)), 105 | interfaces, 106 | groupsMap, 107 | groupsData, 108 | }; 109 | }, 110 | setup() { 111 | return { 112 | sidebar, 113 | inputSearch, 114 | inputApiKey, 115 | inputAccessToken, 116 | }; 117 | }, 118 | watch: { 119 | 'userData.format'(value: string): void { 120 | localStorage.setItem('format', value); 121 | }, 122 | 'userData.webapi_key'(value: string): void { 123 | if (this.isFieldValid('webapi_key')) { 124 | localStorage.setItem('webapi_key', value); 125 | } else { 126 | localStorage.removeItem('webapi_key'); 127 | } 128 | }, 129 | 'userData.access_token'(value: string): void { 130 | try { 131 | if (value.length > 2 && value[0] === '{' && value[value.length - 1] === '}') { 132 | const obj = JSON.parse(value); 133 | 134 | if (obj.data?.webapi_token) { 135 | this.userData.access_token = obj.data.webapi_token; 136 | return; 137 | } 138 | } 139 | if (/^[\w-]+\.[\w-]+\.[\w-]+$/.test(value)) { 140 | const jwt = value.split('.'); 141 | const token = JSON.parse(atob(jwt[1])); 142 | 143 | this.accessTokenExpiration = token.exp * 1000; 144 | this.accessTokenAudience = token.aud; 145 | this.accessTokenSteamId = token.sub; 146 | 147 | if (token.sub && !this.userData.steamid) { 148 | this.userData.steamid = token.sub; 149 | } 150 | } else { 151 | throw new Error('Invalid token format (or empty)'); 152 | } 153 | } catch (e) { 154 | console.log((e as Error).message); 155 | this.accessTokenExpiration = 0; 156 | this.accessTokenSteamId = null; 157 | this.accessTokenAudience = []; 158 | } 159 | 160 | if (this.isFieldValid('access_token')) { 161 | localStorage.setItem('access_token', value); 162 | } else { 163 | localStorage.removeItem('access_token'); 164 | } 165 | }, 166 | 'userData.steamid'(value: string): void { 167 | if (this.isFieldValid('steamid')) { 168 | localStorage.setItem('steamid', value); 169 | 170 | this.fillSteamidParameter(); 171 | } else { 172 | localStorage.removeItem('steamid'); 173 | } 174 | }, 175 | currentFilter(newFilter: string, oldFilter: string): void { 176 | if (!newFilter) { 177 | this.$nextTick(this.scrollInterfaceIntoView); 178 | 179 | if (oldFilter) { 180 | this.sidebar!.scrollTop = 0; 181 | } 182 | } else { 183 | this.setInterface(''); 184 | 185 | if (!oldFilter) { 186 | this.sidebar!.scrollTop = 0; 187 | } 188 | } 189 | }, 190 | }, 191 | mounted(): void { 192 | try { 193 | this.userData.format = localStorage.getItem('format') || 'json'; 194 | this.userData.steamid = localStorage.getItem('steamid') || ''; 195 | this.userData.webapi_key = localStorage.getItem('webapi_key') || ''; 196 | this.userData.access_token = localStorage.getItem('access_token') || ''; 197 | 198 | const favoriteStrings = JSON.parse(localStorage.getItem('favorites') || '[]'); 199 | 200 | for (const favorite of favoriteStrings) { 201 | const [favoriteInterface, favoriteMethod] = favorite.split('/', 2); 202 | 203 | if ( 204 | Object.hasOwn(this.interfaces, favoriteInterface) && 205 | Object.hasOwn(this.interfaces[favoriteInterface], favoriteMethod) 206 | ) { 207 | this.interfaces[favoriteInterface][favoriteMethod].isFavorite = true; 208 | 209 | this.userData.favorites.add(favorite); 210 | } 211 | } 212 | } catch (e) { 213 | console.error(e); 214 | } 215 | 216 | if (location.hash.startsWith('#')) { 217 | this.setInterface(location.hash.substring(1), true); 218 | } 219 | 220 | window.addEventListener( 221 | 'hashchange', 222 | () => { 223 | if (this.skipNextHashChange) { 224 | this.skipNextHashChange = false; 225 | return; 226 | } 227 | 228 | this.setInterface(location.hash.substring(1)); 229 | }, 230 | false, 231 | ); 232 | 233 | this.bindGlobalKeybind(); 234 | }, 235 | computed: { 236 | sidebarInterfaces(): Map { 237 | const interfaces = this.filteredInterfaces; 238 | 239 | if (this.currentFilter) { 240 | return new Map([ 241 | [ 242 | -1, 243 | { 244 | name: 'Search results', 245 | icon: '', 246 | open: true, 247 | methods: interfaces, 248 | }, 249 | ], 250 | ]); 251 | } 252 | 253 | return this.groupsData; 254 | }, 255 | filteredInterfaces(): ApiServices { 256 | if (!this.currentFilter) { 257 | return this.interfaces; 258 | } 259 | 260 | const matchedInterfaces: ApiServices = {}; 261 | const hits = this.search.search(this.currentFilter); 262 | 263 | for (const match of hits) { 264 | if (!matchedInterfaces[match.interface]) { 265 | matchedInterfaces[match.interface] = {}; 266 | } 267 | 268 | const method = this.interfaces[match.interface][match.method]; 269 | method.highlight = match.indices; 270 | matchedInterfaces[match.interface][match.method] = method; 271 | } 272 | 273 | return matchedInterfaces; 274 | }, 275 | currentInterfaceMethods(): ApiInterface { 276 | return this.interfaces[this.currentInterface]; 277 | }, 278 | uriDelimeterBeforeKey() { 279 | return this.hasValidAccessToken || this.hasValidWebApiKey ? '?' : ''; 280 | }, 281 | formatAccessTokenExpirationDate(): string { 282 | const formatter = new Intl.DateTimeFormat('en-US', { 283 | hourCycle: 'h23', 284 | dateStyle: 'medium', 285 | timeStyle: 'short', 286 | }); 287 | 288 | return formatter.format(this.accessTokenExpiration); 289 | }, 290 | }, 291 | methods: { 292 | setInterface(interfaceAndMethod: string, setFromUrl = false): void { 293 | const split = interfaceAndMethod.split('/', 2); 294 | let currentInterface: string | null = split[0]; 295 | let currentMethod: string | null = split.length > 1 ? split[1] : null; 296 | 297 | if (!Object.hasOwn(this.interfaces, currentInterface)) { 298 | currentInterface = null; 299 | currentMethod = null; 300 | } else if (currentMethod !== null && !Object.hasOwn(this.interfaces[currentInterface], currentMethod)) { 301 | currentMethod = null; 302 | } 303 | 304 | this.currentInterface = currentInterface || ''; 305 | 306 | if (currentInterface) { 307 | document.title = `${currentInterface} – Steam Web API Documentation`; 308 | } else { 309 | document.title = `Steam Web API Documentation`; 310 | } 311 | 312 | // Since we won't scroll to a method, scroll to top (as there is no element with just interface id) 313 | if (document.scrollingElement && !currentMethod) { 314 | document.scrollingElement.scrollTop = 0; 315 | } 316 | 317 | if (setFromUrl) { 318 | return; 319 | } 320 | 321 | this.$nextTick(() => { 322 | this.skipNextHashChange = true; 323 | 324 | if (currentMethod) { 325 | location.hash = `#${currentInterface}/${currentMethod}`; 326 | } else if (currentInterface) { 327 | location.hash = `#${currentInterface}`; 328 | } else { 329 | location.hash = ''; 330 | } 331 | }); 332 | }, 333 | fillSteamidParameter(): void { 334 | if (!this.userData.steamid) { 335 | return; 336 | } 337 | 338 | for (const interfaceName in this.interfaces) { 339 | for (const methodName in this.interfaces[interfaceName]) { 340 | for (const parameter of this.interfaces[interfaceName][methodName].parameters) { 341 | if (!parameter._value && parameter.name.includes('steamid')) { 342 | parameter._value = this.userData.steamid; 343 | } 344 | } 345 | } 346 | } 347 | }, 348 | isFieldValid(field: string): boolean { 349 | switch (field) { 350 | case 'access_token': 351 | this.hasValidAccessToken = this.accessTokenExpiration > Date.now(); 352 | return this.hasValidAccessToken; 353 | 354 | case 'webapi_key': 355 | this.hasValidWebApiKey = /^[0-9a-f]{32}$/i.test(this.userData[field]); 356 | return this.hasValidWebApiKey; 357 | 358 | case 'steamid': 359 | return /^[0-9]{17}$/.test(this.userData[field]); 360 | } 361 | 362 | return false; 363 | }, 364 | renderUri(methodName: string, method: ApiMethod): string { 365 | let host = 'https://api.steampowered.com/'; 366 | 367 | if (method._type === 'publisher_only') { 368 | host = 'https://partner.steam-api.com/'; 369 | } 370 | 371 | return `${host}${this.currentInterface}/${methodName}/v${method.version}/`; 372 | }, 373 | renderApiKey(): string { 374 | const parameters = new URLSearchParams(); 375 | 376 | if (this.hasValidAccessToken) { 377 | parameters.set('access_token', this.userData.access_token); 378 | } else if (this.hasValidWebApiKey) { 379 | parameters.set('key', this.userData.webapi_key); 380 | } 381 | 382 | return parameters.toString(); 383 | }, 384 | renderParameters(method: ApiMethod): string { 385 | const parameters = new URLSearchParams(); 386 | 387 | if (this.userData.format !== 'json') { 388 | parameters.set('format', this.userData.format); 389 | } 390 | 391 | let hasArrays = false; 392 | const inputJson = {} as any; 393 | 394 | for (const parameter of method.parameters) { 395 | if (parameter.extra) { 396 | const arr = this.getInnerParameters(parameter); 397 | 398 | if (Object.keys(arr).length > 0) { 399 | hasArrays = true; 400 | 401 | if (parameter.type?.endsWith('[]')) { 402 | const paramName = parameter.name.substring(0, parameter.name.length - 3); 403 | 404 | if (!Object.hasOwn(inputJson, paramName)) { 405 | inputJson[paramName] = []; 406 | } 407 | 408 | inputJson[paramName].push(arr); 409 | } else { 410 | inputJson[parameter.name] = arr; 411 | } 412 | } else if (parameter._value) { 413 | parameters.set(parameter.name, parameter._value); 414 | } 415 | 416 | continue; 417 | } 418 | 419 | if (!parameter._value && !parameter.manuallyToggled) { 420 | continue; 421 | } 422 | 423 | parameters.set(parameter.name, parameter._value ?? ''); 424 | } 425 | 426 | if (hasArrays) { 427 | method.hasArrays = true; 428 | parameters.set('input_json', JSON.stringify(inputJson)); 429 | } 430 | 431 | const str = parameters.toString(); 432 | 433 | if (str.length === 0) { 434 | return ''; 435 | } 436 | 437 | if (this.uriDelimeterBeforeKey) { 438 | return `&${str}`; 439 | } 440 | 441 | return `?${str}`; 442 | }, 443 | getInnerParameters(parameterParent: ApiMethodParameter) { 444 | const arr = {} as any; 445 | 446 | for (const parameter of parameterParent.extra!) { 447 | if (parameter.extra) { 448 | const result = this.getInnerParameters(parameter); 449 | 450 | if (Object.keys(result).length > 0) { 451 | if (parameter.type?.endsWith('[]')) { 452 | const paramName = parameter.name.substring(0, parameter.name.length - 3); 453 | 454 | if (!Object.hasOwn(arr, paramName)) { 455 | arr[paramName] = []; 456 | } 457 | 458 | arr[paramName].push(result); 459 | } else { 460 | arr[parameter.name] = result; 461 | } 462 | } 463 | 464 | continue; 465 | } 466 | 467 | if (!parameter._value && !parameter.manuallyToggled) { 468 | continue; 469 | } 470 | 471 | if (parameter.type?.endsWith('[]')) { 472 | const paramName = parameter.name.substring(0, parameter.name.length - 3); 473 | 474 | if (!Object.hasOwn(arr, paramName)) { 475 | arr[paramName] = []; 476 | } 477 | 478 | arr[paramName].push(parameter._value || ''); 479 | } else { 480 | arr[parameter.name] = parameter._value || ''; 481 | } 482 | } 483 | 484 | return arr; 485 | }, 486 | useThisMethod(event: SubmitEvent, method: ApiMethod): void { 487 | const form = event.target as HTMLFormElement; 488 | 489 | if (method.hasArrays) { 490 | event.preventDefault(); 491 | 492 | if (method.httpmethod === 'POST') { 493 | alert('Executing POST requests with input_json is not yet supported.'); 494 | return; 495 | } 496 | 497 | const url = [form.action, this.uriDelimeterBeforeKey, this.renderApiKey(), this.renderParameters(method)].join( 498 | '', 499 | ); 500 | 501 | try { 502 | window.open(url, '_blank'); 503 | } catch { 504 | alert('Failed to open window'); 505 | } 506 | 507 | return; 508 | } 509 | 510 | if ( 511 | method.httpmethod === 'POST' && 512 | !confirm( 513 | 'Executing POST requests could be potentially disastrous.\n\n' + 514 | 'Author is not responsible for any damage done.\n\n' + 515 | 'Are you sure you want to continue?', 516 | ) 517 | ) { 518 | event.preventDefault(); 519 | } 520 | 521 | for (const field of form.elements) { 522 | if (!(field instanceof HTMLInputElement)) { 523 | continue; 524 | } 525 | 526 | if (!field.value && !field.disabled && field.tagName === 'INPUT') { 527 | field.disabled = true; 528 | 529 | setTimeout(() => { 530 | field.disabled = false; 531 | }, 0); 532 | } 533 | } 534 | }, 535 | addParamArray(method: ApiMethod, parameter: ApiMethodParameter): void { 536 | if (!parameter._counter) { 537 | parameter._counter = 1; 538 | } else { 539 | parameter._counter++; 540 | } 541 | 542 | const newParameter: ApiMethodParameter = { 543 | name: `${parameter.name.substring(0, parameter.name.length - 3)}[${parameter._counter}]`, 544 | type: parameter.type, 545 | optional: true, 546 | }; 547 | 548 | if (parameter.extra) { 549 | newParameter.extra = []; 550 | 551 | for (const parameter2 of parameter.extra!) { 552 | newParameter.extra.push({ 553 | name: parameter2.name, 554 | type: parameter2.type, 555 | optional: true, 556 | }); 557 | } 558 | } 559 | 560 | const parameterIndex = method.parameters.findIndex((param) => param.name === parameter.name); 561 | method.parameters.splice(parameterIndex + parameter._counter, 0, newParameter); 562 | }, 563 | scrollInterfaceIntoView(): void { 564 | const element = document.querySelector(`.interface-list a[href="#${this.currentInterface}"]`); 565 | 566 | if (element instanceof HTMLElement) { 567 | element.scrollIntoView(); 568 | } 569 | }, 570 | copyUrl(event: MouseEvent): void { 571 | const button = event.target as Element; 572 | const element = button.closest('.input-group')!.querySelector('.form-control')!; 573 | 574 | navigator.clipboard.writeText(element.textContent || '').then( 575 | () => { 576 | button.classList.add('bg-success'); 577 | 578 | setTimeout(() => button.classList.remove('bg-success'), 500); 579 | }, 580 | () => { 581 | // write fail 582 | }, 583 | ); 584 | }, 585 | favoriteMethod(method: ApiMethod, methodName: string): void { 586 | const name = `${this.currentInterface}/${methodName}`; 587 | 588 | method.isFavorite = !method.isFavorite; 589 | 590 | if (method.isFavorite) { 591 | this.userData.favorites.add(name); 592 | } else { 593 | this.userData.favorites.delete(name); 594 | } 595 | 596 | localStorage.setItem('favorites', JSON.stringify([...this.userData.favorites])); 597 | }, 598 | navigateSidebar(direction: number): void { 599 | const entries = Object.entries(this.filteredInterfaces); 600 | const index = entries.findIndex((x) => x[0] === this.currentInterface) + direction; 601 | const size = entries.length; 602 | const [interfaceName, methods] = entries[((index % size) + size) % size]; 603 | const firstMethodName = Object.keys(methods)[0]; 604 | 605 | this.setInterface(`${interfaceName}/${firstMethodName}`); 606 | this.scrollInterfaceIntoView(); 607 | 608 | // This is trash, but the focus gets lost because of location.hash change 609 | this.$nextTick(() => { 610 | this.inputSearch?.focus(); 611 | }); 612 | }, 613 | focusApiKey(): void { 614 | this.currentFilter = ''; 615 | this.setInterface(''); 616 | 617 | this.$nextTick(() => { 618 | const element = this.hasValidAccessToken ? this.inputAccessToken : this.inputApiKey; 619 | 620 | if (element) { 621 | element.focus(); 622 | } 623 | }); 624 | }, 625 | onSearchInput(e: Event) { 626 | requestAnimationFrame(() => { 627 | this.currentFilter = (e.target as HTMLInputElement).value; 628 | }); 629 | }, 630 | bindGlobalKeybind() { 631 | document.addEventListener('keydown', (e: KeyboardEvent) => { 632 | if (e.ctrlKey || e.metaKey) { 633 | return; 634 | } 635 | 636 | const target = e.target as HTMLElement; 637 | 638 | if (['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(target.tagName)) { 639 | return; 640 | } 641 | 642 | if (e.key === '/' || e.key === 's') { 643 | e.preventDefault(); 644 | this.inputSearch?.focus(); 645 | } 646 | }); 647 | }, 648 | }, 649 | }); 650 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /src/HighlightedSearchMethod.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h, 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 | const 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 | } --------------------------------------------------------------------------------