( baseUrl: string, path: string, )
| 134 | } |
| 135 | |
| 136 | async function checkRobotsTxt( |
| 137 | baseUrl: string, |
| 138 | path: string, |
| 139 | ): Promise<'allowed' | 'blocked' | 'error'> { |
| 140 | try { |
| 141 | const robotsUrl = new URL('/robots.txt', baseUrl).toString(); |
| 142 | const controller = new AbortController(); |
| 143 | const timeout = setTimeout(() => controller.abort(), 5000); |
| 144 | |
| 145 | const response = await fetch(robotsUrl, { |
| 146 | signal: controller.signal, |
| 147 | headers: { |
| 148 | 'User-Agent': 'OpenPanel-SiteChecker/1.0', |
| 149 | }, |
| 150 | }); |
| 151 | |
| 152 | clearTimeout(timeout); |
| 153 | |
| 154 | if (!response.ok) { |
| 155 | return 'error'; |
| 156 | } |
| 157 | |
| 158 | const text = await response.text(); |
| 159 | const rules = text.split('\n').filter((line) => { |
| 160 | const trimmed = line.trim(); |
| 161 | return trimmed && !trimmed.startsWith('#'); |
| 162 | }); |
| 163 | |
| 164 | for (const rule of rules) { |
| 165 | if (rule.toLowerCase().startsWith('disallow:')) { |
| 166 | const pattern = rule.substring(9).trim(); |
| 167 | if (pattern && path.includes(pattern.replace('*', ''))) { |
| 168 | return 'blocked'; |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | return 'allowed'; |
| 174 | } catch { |
| 175 | return 'error'; |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | async function checkSitemap(baseUrl: string): Promise<boolean> { |
| 180 | try { |
no test coverage detected