mirror of
https://github.com/rooyca/obsidian-api-request.git
synced 2026-07-22 07:50:27 +00:00
update(doc): added spanish & chinese
This commit is contained in:
parent
4a9670c80a
commit
bab5f0de7d
21 changed files with 2729 additions and 391 deletions
371
CONTRIBUTING.md
Normal file
371
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
# Contributing to API Request Plugin
|
||||
|
||||
Thank you for your interest in contributing to the API Request plugin! This document provides guidelines and information for contributors.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Making Changes](#making-changes)
|
||||
- [Code Style](#code-style)
|
||||
- [Testing](#testing)
|
||||
- [Submitting Changes](#submitting-changes)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Requesting Features](#requesting-features)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository
|
||||
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/obsidian-api-request.git`
|
||||
3. Create a branch: `git checkout -b feature/your-feature-name`
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js (v16 or higher)
|
||||
- npm (v7 or higher)
|
||||
- Obsidian (for testing)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build the plugin
|
||||
npm run build
|
||||
|
||||
# Development mode (auto-rebuild on changes)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
obsidian-api-request/
|
||||
├── src/
|
||||
│ ├── main.ts # Main plugin file
|
||||
│ ├── functions/
|
||||
│ │ ├── general.ts # General utility functions
|
||||
│ │ ├── regx.ts # Regular expressions
|
||||
│ │ ├── security.ts # Security utilities
|
||||
│ │ └── frontmatterUtils.ts # Frontmatter parsing
|
||||
│ └── settings/
|
||||
│ ├── settingsData.ts # Settings interface
|
||||
│ └── settingsTab.ts # Settings UI
|
||||
├── docs/ # Documentation
|
||||
├── main.js # Compiled output
|
||||
├── manifest.json # Plugin manifest
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
|
||||
### Branch Naming
|
||||
|
||||
- `feature/` - New features
|
||||
- `fix/` - Bug fixes
|
||||
- `docs/` - Documentation updates
|
||||
- `refactor/` - Code refactoring
|
||||
- `security/` - Security improvements
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Follow conventional commits format:
|
||||
|
||||
```
|
||||
type(scope): brief description
|
||||
|
||||
Longer description if needed
|
||||
|
||||
- Additional details
|
||||
- More details
|
||||
```
|
||||
|
||||
Types:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation changes
|
||||
- `refactor`: Code refactoring
|
||||
- `perf`: Performance improvements
|
||||
- `test`: Adding or updating tests
|
||||
- `security`: Security improvements
|
||||
|
||||
Examples:
|
||||
```
|
||||
feat(request): add support for PATCH method
|
||||
|
||||
fix(cache): prevent race condition in localStorage
|
||||
|
||||
docs(readme): add security best practices section
|
||||
|
||||
security(validation): sanitize UUID input to prevent injection
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
### TypeScript
|
||||
|
||||
- Use TypeScript for all new code
|
||||
- Add proper type annotations
|
||||
- Avoid `any` type when possible
|
||||
- Use interfaces for complex types
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add JSDoc comments to all public functions
|
||||
- Include `@param`, `@returns`, `@throws` tags
|
||||
- Add `@example` for complex functions
|
||||
- Document security considerations with `@security` tag
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
/**
|
||||
* Validates a URL to ensure it's safe to request
|
||||
*
|
||||
* @param url - The URL to validate
|
||||
* @returns true if valid, false otherwise
|
||||
* @security Only allows http:// and https:// protocols
|
||||
* @example
|
||||
* isValidUrl("https://api.example.com") // returns true
|
||||
* isValidUrl("file:///etc/passwd") // returns false
|
||||
*/
|
||||
export function isValidUrl(url: string): boolean {
|
||||
// implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Code Organization
|
||||
|
||||
- Keep functions focused and single-purpose
|
||||
- Extract magic numbers to constants
|
||||
- Use descriptive variable names
|
||||
- Limit function length to ~50 lines
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Always wrap risky operations in try-catch
|
||||
- Log errors to console with context
|
||||
- Show user-friendly error messages via Notice
|
||||
- Never expose sensitive data in errors
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
try {
|
||||
const data = localStorage.getItem(key);
|
||||
if (data) {
|
||||
return safeJsonParse(data);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error("Error reading from localStorage:", e);
|
||||
new Notice("Error: Failed to retrieve cached data");
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Testing
|
||||
|
||||
1. Build the plugin: `npm run build`
|
||||
2. Copy `main.js`, `manifest.json`, and `styles.css` to your Obsidian vault's plugins folder
|
||||
3. Reload Obsidian
|
||||
4. Test your changes thoroughly
|
||||
|
||||
### Test Cases to Verify
|
||||
|
||||
- URL validation with various protocols
|
||||
- UUID sanitization with special characters
|
||||
- JSONPath injection attempts
|
||||
- File path traversal attempts
|
||||
- XSS prevention in format strings
|
||||
- Error handling for network failures
|
||||
- localStorage cache operations
|
||||
- Variable substitution (global, frontmatter, localStorage)
|
||||
|
||||
### Security Testing
|
||||
|
||||
Before submitting security-related changes:
|
||||
|
||||
1. Test with malicious inputs
|
||||
2. Verify input sanitization
|
||||
3. Check for XSS vulnerabilities
|
||||
4. Test error handling edge cases
|
||||
5. Review console logs for sensitive data leaks
|
||||
|
||||
## Submitting Changes
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
1. Update documentation if needed
|
||||
2. Add yourself to contributors if first contribution
|
||||
3. Ensure the build passes: `npm run build`
|
||||
4. Update CHANGELOG.md if significant change
|
||||
5. Create a pull request with:
|
||||
- Clear title describing the change
|
||||
- Description of what and why
|
||||
- Related issue numbers
|
||||
- Screenshots for UI changes
|
||||
- Testing steps
|
||||
|
||||
### Pull Request Template
|
||||
|
||||
```markdown
|
||||
## Description
|
||||
Brief description of changes
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [ ] Documentation update
|
||||
- [ ] Security improvement
|
||||
|
||||
## Testing
|
||||
Steps to test the changes
|
||||
|
||||
## Checklist
|
||||
- [ ] Code follows project style guidelines
|
||||
- [ ] Added/updated documentation
|
||||
- [ ] Tested manually
|
||||
- [ ] No console errors
|
||||
- [ ] Updated CHANGELOG.md
|
||||
```
|
||||
|
||||
### Review Process
|
||||
|
||||
- Maintainers will review your PR
|
||||
- Address feedback and requested changes
|
||||
- Once approved, your PR will be merged
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
### Before Reporting
|
||||
|
||||
1. Check existing issues
|
||||
2. Verify you're using the latest version
|
||||
3. Reproduce the bug with minimal configuration
|
||||
|
||||
### Bug Report Template
|
||||
|
||||
```markdown
|
||||
**Description**
|
||||
Clear description of the bug
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce:
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. See error
|
||||
|
||||
**Expected Behavior**
|
||||
What should happen
|
||||
|
||||
**Actual Behavior**
|
||||
What actually happens
|
||||
|
||||
**Screenshots**
|
||||
If applicable
|
||||
|
||||
**Environment**
|
||||
- Plugin version:
|
||||
- Obsidian version:
|
||||
- OS:
|
||||
|
||||
**Additional Context**
|
||||
Any other relevant information
|
||||
```
|
||||
|
||||
## Requesting Features
|
||||
|
||||
### Feature Request Template
|
||||
|
||||
```markdown
|
||||
**Problem Statement**
|
||||
What problem does this solve?
|
||||
|
||||
**Proposed Solution**
|
||||
How should it work?
|
||||
|
||||
**Alternatives Considered**
|
||||
Other approaches considered
|
||||
|
||||
**Additional Context**
|
||||
Examples, mockups, related issues
|
||||
```
|
||||
|
||||
## Security Issues
|
||||
|
||||
**Do not report security vulnerabilities in public issues!**
|
||||
|
||||
See [SECURITY.md](SECURITY.md) for how to report security issues.
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
When reviewing code:
|
||||
|
||||
1. **Functionality**: Does it work as intended?
|
||||
2. **Security**: Are inputs validated? Any XSS risks?
|
||||
3. **Performance**: Any unnecessary operations?
|
||||
4. **Maintainability**: Is the code clear and documented?
|
||||
5. **Consistency**: Does it follow project conventions?
|
||||
|
||||
## Development Tips
|
||||
|
||||
### Hot Reload
|
||||
|
||||
For faster development:
|
||||
1. Run `npm run dev` to watch for changes
|
||||
2. Use Obsidian's developer tools (Ctrl+Shift+I)
|
||||
3. Reload plugin: Ctrl+R in dev tools
|
||||
|
||||
### Debugging
|
||||
|
||||
```typescript
|
||||
// Add debug logging
|
||||
console.log("Debug:", variable);
|
||||
|
||||
// Use Obsidian's Notice for user feedback
|
||||
new Notice("Debug: Operation completed");
|
||||
|
||||
// Use debugger breakpoints
|
||||
debugger;
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Build fails**:
|
||||
- Check TypeScript errors: `npm run build`
|
||||
- Verify all imports are correct
|
||||
- Check for missing type definitions
|
||||
|
||||
**Plugin not loading**:
|
||||
- Verify manifest.json is correct
|
||||
- Check Obsidian console for errors
|
||||
- Ensure minAppVersion matches your Obsidian
|
||||
|
||||
**Changes not appearing**:
|
||||
- Rebuild: `npm run build`
|
||||
- Reload Obsidian: Ctrl+R
|
||||
- Check file is copied to correct location
|
||||
|
||||
## Questions?
|
||||
|
||||
- Open a discussion on GitHub
|
||||
- Ask in issues (for bug-related questions)
|
||||
- Check existing documentation
|
||||
|
||||
## Recognition
|
||||
|
||||
Contributors will be:
|
||||
- Listed in the project README
|
||||
- Mentioned in release notes
|
||||
- Credited in the plugin description
|
||||
|
||||
Thank you for contributing! 🎉
|
||||
63
README.es.md
63
README.es.md
|
|
@ -8,9 +8,18 @@
|
|||
[](README.md)
|
||||
[](README.zh.md)
|
||||
|
||||
Este plugin para [Obsidian](https://obsidian.md/) permite a los usuarios realizar solicitudes HTTP desde sus notas y mostrar la respuesta en un bloque de código, una ventana modal o pegarla directamente en su documento actual.
|
||||
|
||||

|
||||
Este plugin para [Obsidian](https://obsidian.md/) permite a los usuarios realizar solicitudes API directamente desde sus notas y mostrar las respuestas en bloques de código.
|
||||
|
||||
## 🔒 Seguridad
|
||||
|
||||
Este plugin implementa medidas de seguridad completas que incluyen:
|
||||
|
||||
- Validación de entradas para URLs, rutas y datos de usuario
|
||||
- Prevención de XSS mediante sanitización de HTML
|
||||
- Protección contra ataques de directory traversal
|
||||
- Evaluación segura de expresiones JSONPath
|
||||
- Manejo seguro de localStorage
|
||||
|
||||
## 🚀 Instalación
|
||||
|
||||
|
|
@ -30,13 +39,47 @@ Este plugin se puede instalar desde Obsidian.
|
|||
|
||||

|
||||
|
||||
## ✅ To-do
|
||||
## ✅ Por hacer
|
||||
|
||||
- [x] Añadir más tipos de solicitudes (POST, PUT, DELETE)
|
||||
- [x] Añadir soporte para autenticación
|
||||
- [x] Guardar la respuesta en un archivo
|
||||
- [x] Eliminar uno a uno del localStorage
|
||||
- [ ] Traducir la documentación
|
||||
> Consulta todos los cambios en [TODOS-v2.md](TODOS-v2.md)
|
||||
|
||||
- [ ] Traducir (y actualizar) documentación
|
||||
- [x] Reutilización de datos (sintaxis `{{ls.UUID>JSONPath}}`, donde `ls` significa `localStorage`)
|
||||
- [x] Soporte para comentarios usando sintaxis `#` o `//`
|
||||
- [x] Consulta en línea desde la respuesta (usando **Dataview**)
|
||||
- [ ] Añadir pruebas (!!!)
|
||||
- [ ] Re-implementar flag `repeat` (repetir solicitudes X veces o cada X segundos)
|
||||
- [x] Mejoras de seguridad (validación de entradas, prevención de XSS, protección contra directory traversal)
|
||||
- [x] Mejoras de seguridad de tipos TypeScript
|
||||
- [x] Manejo completo de errores
|
||||
|
||||
## 🤝 Contribuir
|
||||
|
||||
¡Las contribuciones son bienvenidas! Por favor, lee nuestras [Guías de Contribución](CONTRIBUTING.md) antes de enviar un pull request.
|
||||
|
||||
### Desarrollo
|
||||
|
||||
```bash
|
||||
# Instalar dependencias
|
||||
npm install
|
||||
|
||||
# Compilar
|
||||
npm run build
|
||||
|
||||
# Modo desarrollo (auto-recompilación)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Para más detalles, consulta [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## 🔒 Mejores Prácticas de Seguridad
|
||||
|
||||
Al usar este plugin:
|
||||
|
||||
1. **Usa HTTPS**: Siempre usa URLs HTTPS para solicitudes API
|
||||
2. **Protege las Claves API**: Almacena las claves API en variables globales, nunca en las notas
|
||||
3. **Revisa Datos en Caché**: Limpia regularmente respuestas antiguas en caché
|
||||
4. **Valida Fuentes**: Solo conéctate a endpoints API de confianza
|
||||
|
||||
## ❤️ Patrocinadores
|
||||
|
||||
|
|
@ -44,6 +87,6 @@ Este plugin se puede instalar desde Obsidian.
|
|||
|
||||
## ✍️ Comentarios y Contribuciones
|
||||
|
||||
Si encuentras algún problema o tienes comentarios sobre el plugin, no dudes en abrir un problema en el [repositorio de GitHub](https://github.com/Rooyca/obsidian-api-request).
|
||||
Si encuentras algún problema o tienes comentarios sobre el plugin, no dudes en abrir un issue en el [repositorio de GitHub](https://github.com/Rooyca/obsidian-api-request).
|
||||
|
||||
¡Las contribuciones también son bienvenidas!
|
||||
¡Las contribuciones también son bienvenidas!
|
||||
|
|
|
|||
41
README.md
41
README.md
|
|
@ -11,6 +11,15 @@
|
|||
|
||||
This [Obsidian](https://obsidian.md/) plugin enables users to make API requests directly within their notes and display the response in a code-block.
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
This plugin implements comprehensive security measures including:
|
||||
- Input validation for URLs, paths, and user data
|
||||
- XSS prevention through HTML sanitization
|
||||
- Protection against directory traversal attacks
|
||||
- Safe JSONPath expression evaluation
|
||||
- Secure localStorage handling
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
The plugin can be installed from within Obsidian.
|
||||
|
|
@ -37,6 +46,38 @@ The plugin can be installed from within Obsidian.
|
|||
- [x] Inline query from response (using **Dataview**)
|
||||
- [ ] Add tests (!!!)
|
||||
- [ ] Re-implement `repeat` flag (repeat requests X times or every X seconds)
|
||||
- [x] Security improvements (input validation, XSS prevention, path traversal protection)
|
||||
- [x] TypeScript type safety improvements
|
||||
- [x] Comprehensive error handling
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please read our [Contributing Guidelines](CONTRIBUTING.md) before submitting a pull request.
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Development mode (auto-rebuild)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
For more details, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
When using this plugin:
|
||||
|
||||
1. **Use HTTPS**: Always use HTTPS URLs for API requests
|
||||
2. **Protect API Keys**: Store API keys in global variables, not in notes
|
||||
3. **Review Cached Data**: Regularly clear old cached responses
|
||||
4. **Validate Sources**: Only connect to trusted API endpoints
|
||||
|
||||
|
||||
## ❤️ Sponsors
|
||||
|
||||
|
|
|
|||
103
README.zh.md
103
README.zh.md
|
|
@ -9,39 +9,82 @@
|
|||
[](README.md)
|
||||
|
||||
|
||||
这个[Obsidian](https://obsidian.md/)插件能让用户直接在笔记中进行 HTTP 请求,并在代码块、模式窗口中显示响应,或直接将其粘贴到活动文档中。
|
||||
这个[Obsidian](https://obsidian.md/)插件能让用户直接在笔记中进行 API 请求,并在代码块中显示响应。
|
||||
|
||||
## 🔒 安全性
|
||||
|
||||
本插件实现了全面的安全措施,包括:
|
||||
- URL、路径和用户数据的输入验证
|
||||
- 通过 HTML 清理防止 XSS 攻击
|
||||
- 防止目录遍历攻击
|
||||
- 安全的 JSONPath 表达式评估
|
||||
- 安全的 localStorage 处理
|
||||
|
||||
## 🚀 安装
|
||||
|
||||
该插件可以从 Obsidian 内部安装。
|
||||
|
||||
### Obsidian 社区插件浏览器
|
||||
|
||||
- 转到 `设置` -> `社区插件`
|
||||
- 确保 `受限模式` 已 **关闭**
|
||||
- 点击 `浏览`
|
||||
- 搜索 `APIRequest`
|
||||
- 点击 `安装`,然后点击 `启用`
|
||||
|
||||
## 🛠️ 使用
|
||||
|
||||
### [阅读文档](https://rooyca.github.io/obsidian-api-request/)
|
||||
|
||||
## ✅ 待办事项
|
||||
|
||||
> 查看 [TODOS-v2.md](TODOS-v2.md) 中的所有更改
|
||||
|
||||
- [ ] 翻译(和更新)文档
|
||||
- [x] 数据重用(`{{ls.UUID>JSONPath}}` 语法,其中 `ls` 代表 `localStorage`)
|
||||
- [x] 支持使用 `#` 或 `//` 语法的注释
|
||||
- [x] 从响应中进行内联查询(使用 **Dataview**)
|
||||
- [ ] 添加测试(!!!)
|
||||
- [ ] 重新实现 `repeat` 标志(重复请求 X 次或每 X 秒)
|
||||
- [x] 安全改进(输入验证、XSS 防护、目录遍历保护)
|
||||
- [x] TypeScript 类型安全改进
|
||||
- [x] 全面的错误处理
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎贡献!在提交 pull request 之前,请阅读我们的[贡献指南](CONTRIBUTING.md)。
|
||||
|
||||
### 开发
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 构建
|
||||
npm run build
|
||||
|
||||
# 开发模式(自动重建)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
有关更多详细信息,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||
|
||||
## 🔒 安全最佳实践
|
||||
|
||||
使用此插件时:
|
||||
|
||||
1. **使用 HTTPS**:进行 API 请求时始终使用 HTTPS URL
|
||||
2. **保护 API 密钥**:将 API 密钥存储在全局变量中,而不是在笔记中
|
||||
3. **审查缓存数据**:定期清除旧的缓存响应
|
||||
4. **验证来源**:仅连接到受信任的 API 端点
|
||||
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
The plugin can be installed from within Obsidian.
|
||||
|
||||
### Obsidian Community Plugin Browser
|
||||
|
||||
- Go to `Settings` -> `Community plugins`
|
||||
- Make sure `Restricted mode` is **off**
|
||||
- Click `Browse`
|
||||
- Search for `APIRequest`
|
||||
- Click `Install` and then `Enable`
|
||||
|
||||
## 🛠️ Usage
|
||||
|
||||
### [Read the docs](https://rooyca.github.io/obsidian-api-request/)
|
||||
|
||||
## ✅ To-do
|
||||
|
||||
- [ ] Translate documentation
|
||||
- [x] Data re-usage (`{{ls.UUID>JSONPath}}` syntax, where `ls` stands for `localStorage`)
|
||||
- [x] Support for comments using `#` or `//` syntax
|
||||
- [ ] Inline query from response
|
||||
- [ ] Add tests (!!!)
|
||||
- [ ] Re-implement `repeat` flag (repeat requests X times or every X seconds)
|
||||
- [ ] Re-implement `properties` flag (specifies the frontmatter properties to update with the response)
|
||||
|
||||
## ❤️ Sponsors
|
||||
## ❤️ 赞助商
|
||||
|
||||
<a href="https://github.com/tlwt"><img src="https://github.com/tlwt.png" width="40px" /></a>
|
||||
|
||||
## ✍️ Feedback and Contributions
|
||||
## ✍️ 反馈和贡献
|
||||
|
||||
If you encounter any issues or have feedback on the plugin, feel free to open an issue on the [GitHub repository](https://github.com/Rooyca/obsidian-api-request). Contributions are also welcome!
|
||||
如果您遇到任何问题或对插件有反馈,请随时在 [GitHub 仓库](https://github.com/Rooyca/obsidian-api-request)上提出问题。
|
||||
|
||||
也欢迎贡献!
|
||||
|
|
|
|||
|
|
@ -1,28 +1,50 @@
|
|||
# 👨🏻💻 Codeblocks
|
||||
|
||||
The `codeblock` is a versatile block that can be used to write code in different languages. In this case, we will use it to make API requests.
|
||||
The `codeblock` is a versatile block that can be used to make API requests directly from your notes. This guide covers all available flags and how to use them effectively and securely.
|
||||
|
||||
## 🏳️ Flags
|
||||
|
||||
Flags are the way to specify the parameters of our request.
|
||||
Flags are the way to specify the parameters of our request. All flags are **case-insensitive**.
|
||||
|
||||
| Flag | Default |
|
||||
| ------------| ---------|
|
||||
| [url](#url) | |
|
||||
| [method](#method) | GET |
|
||||
| [body](#body) | |
|
||||
| [headers](#headers) | |
|
||||
| [show](#show) | ALL |
|
||||
| [req-uuid](#req-uuid) | req-general |
|
||||
| [hidden](#hidden) | FALSE |
|
||||
| [disabled](#disabled) | |
|
||||
| [save-as](#save-as) | |
|
||||
| [auto-update](#auto-update) | FALSE |
|
||||
| [format](#format) | |
|
||||
| [properties](#properties) | |
|
||||
| Flag | Default | Required | Description |
|
||||
| ------------| ---------| -------- | ----------- |
|
||||
| [url](#url) | | ✅ Yes | The API endpoint to request |
|
||||
| [method](#method) | GET | No | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
|
||||
| [body](#body) | | No | Request body (JSON format) |
|
||||
| [headers](#headers) | | No | Request headers (JSON format) |
|
||||
| [show](#show) | ALL | No | JSONPath to extract specific data |
|
||||
| [req-uuid](#req-uuid) | req-general | No | Unique ID for caching |
|
||||
| [hidden](#hidden) | FALSE | No | Hide the output |
|
||||
| [disabled](#disabled) | | No | Disable the request |
|
||||
| [save-as](#save-as) | | No | Save response to file |
|
||||
| [auto-update](#auto-update) | FALSE | No | Always fetch fresh data |
|
||||
| [format](#format) | | No | Custom output format |
|
||||
| [properties](#properties) | | No | Update frontmatter properties |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
!!! danger "Important Security Guidelines"
|
||||
- **Always use HTTPS**: Use `https://` URLs to ensure encrypted communication
|
||||
- **Protect API Keys**: Store sensitive tokens in global variables, never hardcode them in notes
|
||||
- **Validate Inputs**: The plugin validates all inputs, but only connect to trusted APIs
|
||||
- **Review Cached Data**: Regularly clear old cached responses from Settings
|
||||
|
||||
### Input Validation
|
||||
|
||||
All user inputs are automatically validated:
|
||||
|
||||
- **URLs**: Only `http://` and `https://` protocols are allowed
|
||||
- **UUIDs**: Sanitized to alphanumeric, hyphens, and underscores only
|
||||
- **File Paths**: Directory traversal (`..`) and absolute paths are blocked
|
||||
- **JSONPath**: Expressions are validated to prevent script injection
|
||||
- **Format Strings**: HTML is sanitized to prevent XSS attacks
|
||||
|
||||
---
|
||||
|
||||
## 📖 Variables & Data Reuse
|
||||
|
||||
### LocalStorage & Variables
|
||||
|
||||
API responses can be stored in `localStorage` and reused in other codeblocks or notes. To store a response, you must assign it a unique identifier using the `req-uuid` flag.
|
||||
|
|
@ -33,36 +55,99 @@ You can access stored responses using the following syntax:
|
|||
{{ls.UUID>JSONPath}}
|
||||
```
|
||||
|
||||
* `UUID`: The unique identifier defined in the `req-uuid` flag.
|
||||
* `JSONPath`: The path to the specific data you want from the response.
|
||||
* `UUID`: The unique identifier defined in the `req-uuid` flag (without the `req-` prefix)
|
||||
* `JSONPath`: The path to the specific data you want from the response
|
||||
|
||||
**Example:**
|
||||
If you have a request with `req-uuid: user`, you can access the user’s name like this:
|
||||
If you have a request with `req-uuid: user`, you can access the user's name like this:
|
||||
|
||||
```
|
||||
{{ls.user>$.name}}
|
||||
```
|
||||
|
||||
!!! info "Security Note"
|
||||
UUIDs are automatically sanitized. Only alphanumeric characters, hyphens, and underscores are allowed.
|
||||
|
||||
---
|
||||
|
||||
You can also reference variables defined in the note's **frontmatter** using:
|
||||
### Frontmatter Variables
|
||||
|
||||
You can reference variables defined in the note's **frontmatter** using:
|
||||
|
||||
```
|
||||
{{this.variableName}}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
Frontmatter:
|
||||
```yaml
|
||||
---
|
||||
userId: 12345
|
||||
title: "My Note"
|
||||
---
|
||||
```
|
||||
|
||||
Usage in request:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/{{this.userId}}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! warning "Security Warning"
|
||||
Avoid storing sensitive API keys in frontmatter. Use global variables instead.
|
||||
|
||||
---
|
||||
|
||||
For **global variables**, you can define them in the plugin settings. These are saved in `localStorage` and can be accessed with:
|
||||
### Global Variables
|
||||
|
||||
Define reusable variables in plugin settings (Settings → APIRequest → Global variables). These are stored securely and can be accessed with:
|
||||
|
||||
```
|
||||
{{ls.variableName}}
|
||||
{{VARIABLE_NAME}}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
1. In plugin settings, add:
|
||||
- Key: `API_KEY`
|
||||
- Value: `your-secret-key`
|
||||
|
||||
2. In your request:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/data
|
||||
headers: {"Authorization": "Bearer {{API_KEY}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Best Practice"
|
||||
Store all API keys and tokens as global variables to keep your notes secure and portable.
|
||||
|
||||
---
|
||||
|
||||
### Special Variables
|
||||
|
||||
- `{{this.file.name}}` - Current file's basename (without extension)
|
||||
|
||||
---
|
||||
|
||||
## Flag Details
|
||||
|
||||
### url
|
||||
|
||||
Is the only **required** flag. It specifies the endpoint of the request.
|
||||
The only **required** flag. It specifies the endpoint of the API request.
|
||||
|
||||
**Syntax:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/endpoint
|
||||
```
|
||||
~~~
|
||||
|
||||
**With variables:**
|
||||
~~~markdown
|
||||
```req
|
||||
# this is just a comment
|
||||
|
|
@ -72,16 +157,25 @@ url: https://jsonplaceholder.typicode.com/users/{{this.id}}
|
|||
|
||||
!!! note "Where `{{this.id}}` is a variable (`id`) defined in the frontmatter."
|
||||
|
||||
!!! danger "Security"
|
||||
Only HTTPS and HTTP URLs are allowed. Other protocols (file://, javascript:, etc.) are blocked for security.
|
||||
|
||||
---
|
||||
|
||||
### method
|
||||
|
||||
Specifies the request method. The default value is `GET` and the available methods are:
|
||||
Specifies the HTTP request method. The default value is `GET`.
|
||||
|
||||
- GET
|
||||
**Available methods:**
|
||||
- GET (default)
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- HEAD
|
||||
- OPTIONS
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
|
|
@ -89,24 +183,40 @@ method: post
|
|||
```
|
||||
~~~
|
||||
|
||||
|
||||
---
|
||||
|
||||
### body
|
||||
|
||||
Specifies the body of the request. The default value is an empty object. The data should be in JSON format, separating key and value with a colon plus space (`, `).
|
||||
Specifies the body of the request. The data should be in JSON format.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
method: post
|
||||
body: {"title": {{this.filename}}, "body": "bar", "userId": 1}
|
||||
body: {"title": "{{this.filename}}", "body": "bar", "userId": 1}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "Where `{{this.filename}}` is the name of the working file."
|
||||
|
||||
**Advanced usage with variables:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/create
|
||||
method: post
|
||||
body: {"userId": {{this.userId}}, "token": "{{API_TOKEN}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
### headers
|
||||
|
||||
Specifies the headers of the request. The default value is an empty object. The data should be in JSON format, separating key and value with a colon plus space (`, `).
|
||||
Specifies the request headers. The data should be in JSON format.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
|
|
@ -115,23 +225,32 @@ headers: {"Content-type": "application/json; charset=UTF-8"}
|
|||
```
|
||||
~~~
|
||||
|
||||
**Using cached responses as headers:**
|
||||
|
||||
You can use responses from other requests as headers/body/url/show. For example, if you have a request with `req-uuid: token`, you can use it like this:
|
||||
|
||||
~~~makdown
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer {{ls.token>$.access_token}}"}
|
||||
show: $..content
|
||||
format: - [ ] {}
|
||||
req-id: todos
|
||||
req-uuid: todos
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Common Headers"
|
||||
- `Content-Type: application/json` - For JSON data
|
||||
- `Authorization: Bearer TOKEN` - For API authentication
|
||||
- `Accept: application/json` - To request JSON responses
|
||||
|
||||
---
|
||||
|
||||
### show
|
||||
|
||||
Specifies the response data to display. See [JSONPath examples](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples), or try the online tool by [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/).
|
||||
|
||||
Specifies which data from the response to display using JSONPath expressions. See [JSONPath examples](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples), or try the online tool by [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/).
|
||||
|
||||
**Simple example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
|
|
@ -139,8 +258,7 @@ show: $['chess_daily']['last']['rating']
|
|||
```
|
||||
~~~
|
||||
|
||||
Multiple outputs can be displayed by using `[]`.
|
||||
|
||||
**Multiple outputs using brackets:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
|
|
@ -148,8 +266,7 @@ show: $.chess_daily[last,best].rating
|
|||
```
|
||||
~~~
|
||||
|
||||
Or you can also use `+` to get multiple outputs.
|
||||
|
||||
**Multiple outputs using +:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
|
|
@ -157,8 +274,9 @@ show: $.chess_daily[last,best].rating + $.chess960_daily[last,best].rating
|
|||
```
|
||||
~~~
|
||||
|
||||
Looping over an array is also possible. The following example retrieves the city from all users.
|
||||
**Looping over arrays:**
|
||||
|
||||
Get city from all users:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
|
|
@ -166,8 +284,7 @@ show: $..address.city
|
|||
```
|
||||
~~~
|
||||
|
||||
Looping over a specified number of elements of the array is also possible.
|
||||
|
||||
**Looping over first N elements:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
|
|
@ -175,8 +292,7 @@ show: $..[:3].address.city
|
|||
```
|
||||
~~~
|
||||
|
||||
It's also possible to loop over a specified range of indexes of the array.
|
||||
|
||||
**Specific array indexes:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
|
|
@ -184,8 +300,9 @@ show: $..[3,2,6].address.city
|
|||
```
|
||||
~~~
|
||||
|
||||
You can access the last element using `(@.length-1)` or just `[-1:]`.
|
||||
**Last element:**
|
||||
|
||||
You can access the last element using `(@.length-1)` or just `[-1:]`:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.modrinth.com/v2/project/distanthorizons
|
||||
|
|
@ -193,21 +310,24 @@ show: $.game_versions[(@.length-1)]
|
|||
```
|
||||
~~~
|
||||
|
||||
To access multiple elements at the same time.
|
||||
|
||||
**Multiple properties:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
|
||||
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key={{API_KEY}}&format=json&limit=4
|
||||
show: $..recenttracks.track[0:][streamable,name,artist]
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! info "Security Note"
|
||||
JSONPath expressions are validated to prevent script injection. Malicious expressions will be blocked.
|
||||
|
||||
---
|
||||
|
||||
### req-uuid
|
||||
|
||||
Specifies the unique identifier of the request. This is useful when we want to store the response in `localStorage` and use it in other blocks or notes.
|
||||
|
||||
Specifies the unique identifier for caching the request response in `localStorage`.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
|
|
@ -218,7 +338,9 @@ req-uuid: test-{{this.username}}
|
|||
|
||||
!!! note "Where `{{this.username}}` is a variable (`username`) defined in the frontmatter."
|
||||
|
||||
Stored responses can be accessed using the `req-uuid` (which won't trigger a new request).
|
||||
**Accessing cached responses:**
|
||||
|
||||
Stored responses can be accessed using the `req-uuid` (which won't trigger a new request):
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
|
|
@ -227,7 +349,9 @@ req-uuid: name
|
|||
```
|
||||
~~~
|
||||
|
||||
Responses can also be accessed using [dataview](https://blacksmithgu.github.io/dataview/).
|
||||
**Using with Dataview:**
|
||||
|
||||
Responses can also be accessed using [dataview](https://blacksmithgu.github.io/obsidian-dataview/):
|
||||
|
||||
~~~markdown
|
||||
```dataviewjs
|
||||
|
|
@ -235,22 +359,31 @@ dv.paragraph(localStorage.getItem("req-UUID"))
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! info "Is mandatory to use `req-` before whatever you defined in `req-uuid` flag."
|
||||
!!! info "Always use `req-` prefix when accessing via localStorage"
|
||||
When accessing directly through localStorage, use: `localStorage.getItem("req-UUID")`
|
||||
|
||||
To remove responses from localStorage, run:
|
||||
**Removing cached responses:**
|
||||
|
||||
From code:
|
||||
~~~markdown
|
||||
```dataviewjs
|
||||
localStorage.removeItem("req-name")
|
||||
```
|
||||
~~~
|
||||
|
||||
To remove responses, go to settings and click over the response you want to delete.
|
||||
From settings:
|
||||
Go to Settings → APIRequest → Saved API Requests and click on the response you want to delete.
|
||||
|
||||
!!! tip "Cache Management"
|
||||
Regularly review and clear old cached responses to free up localStorage space.
|
||||
|
||||
---
|
||||
|
||||
### hidden
|
||||
|
||||
Executes the code block without displaying its output.
|
||||
Executes the code block without displaying its output. Useful for background requests that cache data.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
|
|
@ -259,10 +392,16 @@ hidden
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Use Case"
|
||||
Great for fetching authentication tokens or other data you want to cache but not display.
|
||||
|
||||
---
|
||||
|
||||
### disabled
|
||||
|
||||
Disables the request. The codeblock won't be executed.
|
||||
Disables the request. The codeblock won't be executed. Useful for temporarily disabling requests without deleting them.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
|
|
@ -272,10 +411,16 @@ disabled
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Development Tip"
|
||||
Use `disabled` during development to prevent hitting API rate limits.
|
||||
|
||||
---
|
||||
|
||||
### save-as
|
||||
|
||||
Specifies the path to save the response. It'll save the entire response. A file extension is required. It won't create directories.
|
||||
Saves the entire response to a file. A file extension is required.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
|
|
@ -283,10 +428,23 @@ save-as: posts/1.json
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! warning "Important"
|
||||
- File paths are validated for security
|
||||
- Directory traversal (`..`) is blocked
|
||||
- Absolute paths are not allowed
|
||||
- The file path is relative to your vault root
|
||||
- Directories are NOT created automatically
|
||||
|
||||
!!! tip "File Management"
|
||||
Create the target directory in your vault before using `save-as`.
|
||||
|
||||
---
|
||||
|
||||
### auto-update
|
||||
|
||||
If present, the codeblock will automatically update the response every time is possible. This is only needed when using the flag `req-uuid`, because the default behavior of the codeblock is to run every time the note is loaded.
|
||||
Forces the codeblock to fetch fresh data every time, ignoring cached responses.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
|
|
@ -296,10 +454,20 @@ save-as: posts/1.json
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! note "When to use"
|
||||
Use `auto-update` when you need real-time data or when the cached response becomes stale.
|
||||
|
||||
**Without `req-uuid`:** Requests always run fresh by default.
|
||||
**With `req-uuid`:** Without `auto-update`, cached responses are used.
|
||||
**With `auto-update`:** Ignores cache and always fetches fresh data.
|
||||
|
||||
---
|
||||
|
||||
### format
|
||||
|
||||
Specifies the format in which the response should be displayed. It can be any string (including `html`). If more than one output is specified, more then one format should be specified, otherwise it'd just render the first output.
|
||||
Specifies a custom format for displaying the response. Supports HTML and uses `{}` as placeholders.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
|
|
@ -308,14 +476,40 @@ format: <h1>{}</h1> <p>{}</p>
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! note "In this example, first `{}` will be replaced with the *title*, and second `{}` will be replaced with the *body*."
|
||||
!!! note "In this example, the first `{}` will be replaced with the *title*, and the second `{}` will be replaced with the *body*."
|
||||
|
||||
**Advanced formatting:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/1
|
||||
show: $.[name,email,age]
|
||||
format: **Name:** {} | **Email:** {} | **Age:** {}
|
||||
```
|
||||
~~~
|
||||
|
||||
**List formatting:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer {{TODOIST_TOKEN}}"}
|
||||
show: $..content
|
||||
format: - [ ] {}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! danger "Security Warning"
|
||||
Format strings are sanitized to prevent XSS attacks. Script tags, event handlers, and javascript: URIs are automatically removed.
|
||||
|
||||
---
|
||||
|
||||
### properties
|
||||
|
||||
!!! warning "To use this flag you need a JSON response and the `show` flag"
|
||||
Updates frontmatter properties with response data. Requires a JSON response and the `show` flag.
|
||||
|
||||
Specifies the frontmatter properties to update with the response. The data should be strings separated by commas. To set internal links use the this `[[..]]` syntax.
|
||||
**Syntax:**
|
||||
The data should be property names separated by commas.
|
||||
|
||||
**Example:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
|
|
@ -323,3 +517,99 @@ show: $.[id,title]
|
|||
properties: id, title
|
||||
```
|
||||
~~~
|
||||
|
||||
**For internal links, use `[[..]]` syntax:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/related
|
||||
show: $.[id,name]
|
||||
properties: relatedId, [[relatedNote]]
|
||||
```
|
||||
~~~
|
||||
|
||||
This will create:
|
||||
```yaml
|
||||
---
|
||||
relatedId: 123
|
||||
relatedNote: "[[Note Name]]"
|
||||
---
|
||||
```
|
||||
|
||||
!!! tip "Use Case"
|
||||
Great for automatically populating metadata from API responses.
|
||||
|
||||
---
|
||||
|
||||
## 💡 Common Patterns
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. Get auth token (hidden):
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/auth/login
|
||||
method: post
|
||||
body: {"username": "{{this.username}}", "password": "{{this.password}}"}
|
||||
req-uuid: auth
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
2. Use token in subsequent requests:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/data
|
||||
headers: {"Authorization": "Bearer {{ls.auth>$.token}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
### Error Handling
|
||||
|
||||
If a request fails, an error message will be displayed:
|
||||
|
||||
```
|
||||
Error: Failed to fetch
|
||||
```
|
||||
|
||||
Common errors:
|
||||
|
||||
- **Network Error**: Check your internet connection
|
||||
- **401 Unauthorized**: Check your API key/token
|
||||
- **404 Not Found**: Verify the URL is correct
|
||||
- **Invalid JSONPath**: Check your `show:` expression
|
||||
|
||||
---
|
||||
|
||||
## 📚 Further Reading
|
||||
|
||||
- [JSONPath Syntax Examples](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples)
|
||||
- [JSONPath Online Tester](https://jsonpath-plus.github.io/JSONPath/demo/)
|
||||
- [Use Cases](usecase/index.md) - Real-world examples
|
||||
|
||||
---
|
||||
|
||||
## ❓ Troubleshooting
|
||||
|
||||
**Request not working?**
|
||||
|
||||
- Check the console (Ctrl+Shift+I) for detailed error messages
|
||||
- Verify your URL is correct and uses https://
|
||||
- Check that all required flags are present
|
||||
- Ensure your JSONPath expression is valid
|
||||
|
||||
**Cached data not updating?**
|
||||
|
||||
- Remove `req-uuid` to always fetch fresh data
|
||||
- Or add `auto-update` flag to force refreshing
|
||||
- Or manually clear cache in Settings
|
||||
|
||||
**Variables not resolving?**
|
||||
|
||||
- Check frontmatter syntax is correct
|
||||
- Verify global variables are defined in settings
|
||||
- Ensure you're using the correct variable syntax
|
||||
|
||||
---
|
||||
|
||||
!!! tip "Need Help?"
|
||||
If you encounter issues or have questions, please open an issue on [GitHub](https://github.com/Rooyca/obsidian-api-request/issues).
|
||||
|
|
|
|||
|
|
@ -1,14 +1,34 @@
|
|||
# 🔎 Overview
|
||||
|
||||
APIRequest (APIR) is a plugin for the note taking app [Obsidian](https://obsidian.md/) that allows you to make requests to apis display the response in your notes.
|
||||
APIRequest (APIR) is a plugin for the note taking app [Obsidian](https://obsidian.md/) that allows you to make requests to APIs and display the response directly in your notes.
|
||||
|
||||
## 🔥 Features
|
||||
|
||||
- Perform requests using various methods such as `GET`, `POST`, `PUT`, and `DELETE`.
|
||||
- Utilize variables from the `front-matter`, global variables or even reuse responses from another codeblocks.
|
||||
- Save responses in the `localStorage` for convenient access and reuse.
|
||||
- Disable code blocks as needed to optimize performance.
|
||||
- Display specific values from responses, providing granular control over the presentation of data.
|
||||
- **Multiple HTTP Methods**: Perform requests using `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, and `OPTIONS`.
|
||||
- **Variable Substitution**: Utilize variables from the `front-matter`, global variables, or even reuse responses from other code blocks.
|
||||
- **Response Caching**: Save responses in `localStorage` for convenient access and reuse across notes.
|
||||
- **Performance Control**: Disable code blocks as needed to optimize performance.
|
||||
- **Precise Data Extraction**: Display specific values from responses using JSONPath, providing granular control over data presentation.
|
||||
- **Security First**: Built-in input validation and sanitization to protect against XSS, injection attacks, and directory traversal.
|
||||
- **Auto-update**: Automatically refresh cached responses when needed.
|
||||
- **Format Output**: Custom HTML/text formatting for response data.
|
||||
|
||||
## 🔒 Security Features
|
||||
|
||||
APIRequest implements comprehensive security measures:
|
||||
|
||||
- ✅ **URL Validation**: Only HTTPS and HTTP protocols are allowed
|
||||
- ✅ **Input Sanitization**: All user inputs are validated and sanitized
|
||||
- ✅ **XSS Prevention**: HTML output is sanitized to prevent script injection
|
||||
- ✅ **Path Traversal Protection**: File paths are validated to prevent unauthorized access
|
||||
- ✅ **Safe JSONPath**: JSONPath expressions are validated before execution
|
||||
- ✅ **UUID Sanitization**: Request identifiers are sanitized to prevent injection attacks
|
||||
|
||||
!!! warning "Security Best Practices"
|
||||
- Always use HTTPS URLs when making API requests
|
||||
- Store API keys in global variables (Settings → APIRequest → Global variables), never in notes
|
||||
- Only connect to trusted API endpoints
|
||||
- Regularly review and clear cached responses
|
||||
|
||||
## ⚡ How to use
|
||||
|
||||
|
|
@ -28,3 +48,41 @@ disabled
|
|||
```
|
||||
~~~
|
||||
|
||||
## 📚 Quick Start
|
||||
|
||||
1. **Simple GET Request**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.github.com/users/octocat
|
||||
show: $.name
|
||||
```
|
||||
~~~
|
||||
|
||||
2. **Using Variables**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/{{this.userId}}
|
||||
headers: {"Authorization": "Bearer {{API_TOKEN}}"}
|
||||
show: $.data.name
|
||||
```
|
||||
~~~
|
||||
|
||||
3. **Caching Responses**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/data
|
||||
req-uuid: mydata
|
||||
show: $.result
|
||||
```
|
||||
~~~
|
||||
|
||||
4. **Reusing Cached Data**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/more-data
|
||||
headers: {"X-Token": "{{ls.mydata>$.token}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
For detailed documentation on all flags and features, see [Codeblocks](codeblocks.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,181 @@
|
|||
# 👨🏻💻 Bloque de código
|
||||
# 👨🏻💻 Bloques de Código
|
||||
|
||||
El `bloque de código` es un bloque versátil que se puede usar para escribir código en diferentes lenguajes. En este caso, lo usaremos para realizar solicitudes.
|
||||
El `bloque de código` es un bloque versátil que se puede utilizar para realizar solicitudes API directamente desde tus notas. Esta guía cubre todas las banderas disponibles y cómo usarlas de manera efectiva y segura.
|
||||
|
||||
## 🏳️ Banderas
|
||||
|
||||
Las banderas son la forma de especificar los parámetros de nuestra solicitud y también el formato en el que queremos nuestra respuesta.
|
||||
Las banderas son la forma de especificar los parámetros de nuestra solicitud. Todas las banderas **no distinguen entre mayúsculas y minúsculas**.
|
||||
|
||||
| Bandera | Valor predeterminado |
|
||||
| ------------| ---------|
|
||||
| [url](#url) | |
|
||||
| [method](#method) | GET |
|
||||
| [body](#body) | |
|
||||
| [headers](#headers) | |
|
||||
| [show](#show) | ALL |
|
||||
| [req-uuid](#req-uuid) | |
|
||||
| [disabled](#disabled) | |
|
||||
| [save-as](#save-as) | |
|
||||
| Bandera | Por defecto | Requerido | Descripción |
|
||||
| ------------| ---------| -------- | ----------- |
|
||||
| [url](#url) | | ✅ Sí | El endpoint de la API a solicitar |
|
||||
| [method](#method) | GET | No | Método HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
|
||||
| [body](#body) | | No | Cuerpo de la solicitud (formato JSON) |
|
||||
| [headers](#headers) | | No | Encabezados de la solicitud (formato JSON) |
|
||||
| [show](#show) | ALL | No | JSONPath para extraer datos específicos |
|
||||
| [req-uuid](#req-uuid) | req-general | No | ID único para almacenamiento en caché |
|
||||
| [hidden](#hidden) | FALSE | No | Ocultar la salida |
|
||||
| [disabled](#disabled) | | No | Deshabilitar la solicitud |
|
||||
| [save-as](#save-as) | | No | Guardar la respuesta en un archivo |
|
||||
| [auto-update](#auto-update) | FALSE | No | Siempre obtener datos frescos |
|
||||
| [format](#format) | | No | Formato de salida personalizado |
|
||||
| [properties](#properties) | | No | Actualizar propiedades del frontmatter |
|
||||
|
||||
### url
|
||||
---
|
||||
|
||||
Es la única bandera **obligatoria**. Especifica la URL de la solicitud. Se pueden utilizar variables definidas en el `frontmatter`.
|
||||
## 🔒 Consideraciones de Seguridad
|
||||
|
||||
!!! danger "Directrices de Seguridad Importantes"
|
||||
- **Siempre usa HTTPS**: Usa URLs `https://` para asegurar comunicación cifrada
|
||||
- **Protege las Claves API**: Almacena tokens sensibles en variables globales, nunca los codifiques directamente en las notas
|
||||
- **Valida las Entradas**: El plugin valida todas las entradas, pero solo conéctate a APIs de confianza
|
||||
- **Revisa los Datos en Caché**: Limpia regularmente las respuestas antiguas en caché desde la Configuración
|
||||
|
||||
### Validación de Entradas
|
||||
|
||||
Todas las entradas del usuario se validan automáticamente:
|
||||
|
||||
- **URLs**: Solo se permiten los protocolos `http://` y `https://`
|
||||
- **UUIDs**: Sanitizados solo a alfanuméricos, guiones y guiones bajos
|
||||
- **Rutas de Archivos**: Se bloquean el recorrido de directorios (`..`) y las rutas absolutas
|
||||
- **JSONPath**: Las expresiones se validan para prevenir inyección de scripts
|
||||
- **Cadenas de Formato**: El HTML se sanitiza para prevenir ataques XSS
|
||||
|
||||
---
|
||||
|
||||
## 📖 Variables y Reutilización de Datos
|
||||
|
||||
### LocalStorage y Variables
|
||||
|
||||
Las respuestas de la API se pueden almacenar en `localStorage` y reutilizar en otros bloques de código o notas. Para almacenar una respuesta, debes asignarle un identificador único usando la bandera `req-uuid`.
|
||||
|
||||
Puedes acceder a las respuestas almacenadas usando la siguiente sintaxis:
|
||||
|
||||
```
|
||||
{{ls.UUID>JSONPath}}
|
||||
```
|
||||
|
||||
* `UUID`: El identificador único definido en la bandera `req-uuid` (sin el prefijo `req-`)
|
||||
* `JSONPath`: La ruta a los datos específicos que deseas de la respuesta
|
||||
|
||||
**Ejemplo:**
|
||||
Si tienes una solicitud con `req-uuid: user`, puedes acceder al nombre del usuario así:
|
||||
|
||||
```
|
||||
{{ls.user>$.name}}
|
||||
```
|
||||
|
||||
!!! info "Nota de Seguridad"
|
||||
Los UUIDs se sanitizan automáticamente. Solo se permiten caracteres alfanuméricos, guiones y guiones bajos.
|
||||
|
||||
---
|
||||
|
||||
### Variables del Frontmatter
|
||||
|
||||
Puedes hacer referencia a variables definidas en el **frontmatter** de la nota usando:
|
||||
|
||||
```
|
||||
{{this.variableName}}
|
||||
```
|
||||
|
||||
**Ejemplo:**
|
||||
|
||||
Frontmatter:
|
||||
```yaml
|
||||
---
|
||||
userId: 12345
|
||||
title: "Mi Nota"
|
||||
---
|
||||
```
|
||||
|
||||
Uso en la solicitud:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/{{this.userId}}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! warning "Advertencia de Seguridad"
|
||||
Evita almacenar claves API sensibles en el frontmatter. Usa variables globales en su lugar.
|
||||
|
||||
---
|
||||
|
||||
### Variables Globales
|
||||
|
||||
Define variables reutilizables en la configuración del plugin (Configuración → APIRequest → Variables globales). Estas se almacenan de forma segura y se pueden acceder con:
|
||||
|
||||
```
|
||||
{{VARIABLE_NAME}}
|
||||
```
|
||||
|
||||
**Ejemplo:**
|
||||
|
||||
1. En la configuración del plugin, agrega:
|
||||
- Clave: `API_KEY`
|
||||
- Valor: `your-secret-key`
|
||||
|
||||
2. En tu solicitud:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
# un comentario
|
||||
url: https://api.example.com/data
|
||||
headers: {"Authorization": "Bearer {{API_KEY}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Mejor Práctica"
|
||||
Almacena todas las claves API y tokens como variables globales para mantener tus notas seguras y portables.
|
||||
|
||||
---
|
||||
|
||||
### Variables Especiales
|
||||
|
||||
- `{{this.file.name}}` - Nombre base del archivo actual (sin extensión)
|
||||
|
||||
---
|
||||
|
||||
## Detalles de las Banderas
|
||||
|
||||
### url
|
||||
|
||||
La única bandera **requerida**. Especifica el endpoint de la solicitud API.
|
||||
|
||||
**Sintaxis:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/endpoint
|
||||
```
|
||||
~~~
|
||||
|
||||
**Con variables:**
|
||||
~~~markdown
|
||||
```req
|
||||
# esto es solo un comentario
|
||||
url: https://jsonplaceholder.typicode.com/users/{{this.id}}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "Donde `{{this.id}}` es una variable (`id`) definida en el frontmatter."
|
||||
|
||||
!!! danger "Seguridad"
|
||||
Solo se permiten URLs HTTPS y HTTP. Otros protocolos (file://, javascript:, etc.) están bloqueados por seguridad.
|
||||
|
||||
---
|
||||
|
||||
### method
|
||||
|
||||
Especifica el método de solicitud. El valor predeterminado es `GET` y los valores disponibles son:
|
||||
Especifica el método de solicitud HTTP. El valor por defecto es `GET`.
|
||||
|
||||
- GET
|
||||
**Métodos disponibles:**
|
||||
- GET (por defecto)
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- HEAD
|
||||
- OPTIONS
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
|
|
@ -47,24 +183,39 @@ method: post
|
|||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
### body
|
||||
|
||||
Especifica el cuerpo de la solicitud. El valor predeterminado es un objeto vacío. Los datos deben estar en formato JSON separando las claves y valores con dos puntos y espacio. Se pueden utilizar variables definidas en el `frontmatter`.
|
||||
Especifica el cuerpo de la solicitud. Los datos deben estar en formato JSON.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
method: post
|
||||
body: {"title": {{this.title}}, "body": "bar", "userId": 1}
|
||||
body: {"title": "{{this.filename}}", "body": "bar", "userId": 1}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "Donde `{{this.title}}` es una variable (`title`) definida en el frontmatter."
|
||||
!!! note "Donde `{{this.filename}}` es el nombre del archivo de trabajo."
|
||||
|
||||
**Uso avanzado con variables:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/create
|
||||
method: post
|
||||
body: {"userId": {{this.userId}}, "token": "{{API_TOKEN}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
### headers
|
||||
|
||||
Especifica los encabezados de la solicitud. El valor predeterminado es un objeto vacío. Los datos deben estar en formato JSON separando las claves y valores con dos puntos y espacio. Se pueden utilizar variables definidas en el `frontmatter`.
|
||||
Especifica los encabezados de la solicitud. Los datos deben estar en formato JSON.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
|
|
@ -73,10 +224,32 @@ headers: {"Content-type": "application/json; charset=UTF-8"}
|
|||
```
|
||||
~~~
|
||||
|
||||
**Usando respuestas en caché como encabezados:**
|
||||
|
||||
Puedes usar respuestas de otras solicitudes como encabezados/cuerpo/url/show. Por ejemplo, si tienes una solicitud con `req-uuid: token`, puedes usarla así:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer {{ls.token>$.access_token}}"}
|
||||
show: $..content
|
||||
format: - [ ] {}
|
||||
req-uuid: todos
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Encabezados Comunes"
|
||||
- `Content-Type: application/json` - Para datos JSON
|
||||
- `Authorization: Bearer TOKEN` - Para autenticación API
|
||||
- `Accept: application/json` - Para solicitar respuestas JSON
|
||||
|
||||
---
|
||||
|
||||
### show
|
||||
|
||||
Especifica los datos de respuesta que se van a mostrar. Ver [ejemplos de JSONPath](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples), o prueba la herramienta online de [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/).
|
||||
Especifica qué datos de la respuesta mostrar usando expresiones JSONPath. Ver [ejemplos de JSONPath](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples), o prueba la herramienta en línea de [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/).
|
||||
|
||||
**Ejemplo simple:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
|
|
@ -84,8 +257,7 @@ show: $['chess_daily']['last']['rating']
|
|||
```
|
||||
~~~
|
||||
|
||||
Se pueden mostrar múltiples resultados usando `[]`.
|
||||
|
||||
**Múltiples salidas usando corchetes:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
|
|
@ -93,8 +265,17 @@ show: $.chess_daily[last,best].rating
|
|||
```
|
||||
~~~
|
||||
|
||||
También es posible iterar sobre un arreglo. El siguiente ejemplo muestra la ciudad de todos los usuarios.
|
||||
**Múltiples salidas usando +:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
show: $.chess_daily[last,best].rating + $.chess960_daily[last,best].rating
|
||||
```
|
||||
~~~
|
||||
|
||||
**Iterando sobre arrays:**
|
||||
|
||||
Obtener ciudad de todos los usuarios:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
|
|
@ -102,8 +283,7 @@ show: $..address.city
|
|||
```
|
||||
~~~
|
||||
|
||||
También es posible iterar sobre un número especificado de elementos del arreglo.
|
||||
|
||||
**Iterando sobre los primeros N elementos:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
|
|
@ -111,8 +291,7 @@ show: $..[:3].address.city
|
|||
```
|
||||
~~~
|
||||
|
||||
También es posible iterar sobre un rango especificado de índices del arreglo.
|
||||
|
||||
**Índices específicos del array:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
|
|
@ -120,8 +299,9 @@ show: $..[3,2,6].address.city
|
|||
```
|
||||
~~~
|
||||
|
||||
Puedes acceder al último elemento usando `(@.length-1)` o simplemente `[-1:]`.
|
||||
**Último elemento:**
|
||||
|
||||
Puedes acceder al último elemento usando `(@.length-1)` o simplemente `[-1:]`:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.modrinth.com/v2/project/distanthorizons
|
||||
|
|
@ -129,62 +309,98 @@ show: $.game_versions[(@.length-1)]
|
|||
```
|
||||
~~~
|
||||
|
||||
Para acceder a multiples resultados podemos usar:
|
||||
|
||||
**Múltiples propiedades:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
|
||||
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key={{API_KEY}}&format=json&limit=4
|
||||
show: $..recenttracks.track[0:][streamable,name,artist]
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! info "Nota de Seguridad"
|
||||
Las expresiones JSONPath se validan para prevenir inyección de scripts. Las expresiones maliciosas serán bloqueadas.
|
||||
|
||||
---
|
||||
|
||||
### req-uuid
|
||||
|
||||
Especifica el ID con la que se almacenará la solicitud. Esto es útil cuando queremos almacenar la respuesta en `localStorage` y usarla en otros bloques o notas.
|
||||
|
||||
Especifica el identificador único para almacenar en caché la respuesta de la solicitud en `localStorage`.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
show: $.name
|
||||
req-uuid: name
|
||||
req-uuid: test-{{this.username}}
|
||||
```
|
||||
~~~
|
||||
|
||||
Las respuestas almacenadas se pueden ver usando el `req-uuid` con la bandera `disabled` (que no activará una nueva solicitud).
|
||||
!!! note "Donde `{{this.username}}` es una variable (`username`) definida en el frontmatter."
|
||||
|
||||
**Accediendo a respuestas en caché:**
|
||||
|
||||
Las respuestas almacenadas se pueden acceder usando el `req-uuid` (lo cual no activará una nueva solicitud):
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
req-uuid: name
|
||||
disabled
|
||||
```
|
||||
~~~
|
||||
|
||||
Las respuestas también se pueden ver usando [dataview](https://blacksmithgu.github.io/dataview/).
|
||||
**Usando con Dataview:**
|
||||
|
||||
Las respuestas también se pueden acceder usando [dataview](https://blacksmithgu.github.io/obsidian-dataview/):
|
||||
|
||||
~~~markdown
|
||||
```dataview
|
||||
dv.paragraph(localStorage.getItem("req-name"))
|
||||
```dataviewjs
|
||||
dv.paragraph(localStorage.getItem("req-UUID"))
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! info "Es obligatorio usar `req-` antes de lo que sea que hayas definido en la bandera `req-uuid`."
|
||||
!!! info "Siempre usa el prefijo `req-` cuando accedas vía localStorage"
|
||||
Al acceder directamente a través de localStorage, usa: `localStorage.getItem("req-UUID")`
|
||||
|
||||
Para eliminar respuestas de localStorage, ejecuta:
|
||||
**Eliminando respuestas en caché:**
|
||||
|
||||
Desde código:
|
||||
~~~markdown
|
||||
```dataview
|
||||
```dataviewjs
|
||||
localStorage.removeItem("req-name")
|
||||
```
|
||||
~~~
|
||||
|
||||
Para eliminar todas las respuestas, ve a configuraciones y haz clic sobre la respuesta que quieras eliminar.
|
||||
Desde configuración:
|
||||
Ve a Configuración → APIRequest → Solicitudes API Guardadas y haz clic en la respuesta que deseas eliminar.
|
||||
|
||||
!!! tip "Gestión de Caché"
|
||||
Revisa y limpia regularmente las respuestas antiguas en caché para liberar espacio en localStorage.
|
||||
|
||||
---
|
||||
|
||||
### hidden
|
||||
|
||||
Ejecuta el bloque de código sin mostrar su salida. Útil para solicitudes en segundo plano que almacenan datos en caché.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
req-uuid: name
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Caso de Uso"
|
||||
Excelente para obtener tokens de autenticación u otros datos que deseas almacenar en caché pero no mostrar.
|
||||
|
||||
---
|
||||
|
||||
### disabled
|
||||
|
||||
Deshabilita la solicitud. El codeblock no se ejecutará.
|
||||
Deshabilita la solicitud. El bloque de código no se ejecutará. Útil para deshabilitar temporalmente solicitudes sin eliminarlas.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
|
|
@ -194,10 +410,16 @@ disabled
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! tip "Consejo de Desarrollo"
|
||||
Usa `disabled` durante el desarrollo para evitar alcanzar los límites de tasa de la API.
|
||||
|
||||
---
|
||||
|
||||
### save-as
|
||||
|
||||
Especifica la ruta para guardar la respuesta. Guardará toda la respuesta. Se requiere una extensión de archivo. No creará directorios.
|
||||
Guarda la respuesta completa en un archivo. Se requiere una extensión de archivo.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
|
|
@ -205,10 +427,23 @@ save-as: posts/1.json
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! warning "Importante"
|
||||
- Las rutas de archivo se validan por seguridad
|
||||
- Se bloquea el recorrido de directorios (`..`)
|
||||
- No se permiten rutas absolutas
|
||||
- La ruta del archivo es relativa a la raíz de tu bóveda
|
||||
- Los directorios NO se crean automáticamente
|
||||
|
||||
!!! tip "Gestión de Archivos"
|
||||
Crea el directorio de destino en tu bóveda antes de usar `save-as`.
|
||||
|
||||
---
|
||||
|
||||
### auto-update
|
||||
|
||||
El codeblock se actualizará de manera automatica cada que sea posible. Esto solo es necesario cuando la bandera `req-uuid` está precente, porque el comportamiento predeterminado del codeblock es ejecutarse cada vez que se carga la nota.
|
||||
Fuerza al bloque de código a obtener datos frescos cada vez, ignorando las respuestas en caché.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
|
|
@ -216,4 +451,171 @@ req-uuid: firstPost
|
|||
auto-update
|
||||
save-as: posts/1.json
|
||||
```
|
||||
~~~
|
||||
~~~
|
||||
|
||||
!!! note "Cuándo usar"
|
||||
Usa `auto-update` cuando necesites datos en tiempo real o cuando la respuesta en caché se vuelva obsoleta.
|
||||
|
||||
**Sin `req-uuid`:** Las solicitudes siempre se ejecutan frescas por defecto.
|
||||
**Con `req-uuid`:** Sin `auto-update`, se usan las respuestas en caché.
|
||||
**Con `auto-update`:** Ignora el caché y siempre obtiene datos frescos.
|
||||
|
||||
---
|
||||
|
||||
### format
|
||||
|
||||
Especifica un formato personalizado para mostrar la respuesta. Admite HTML y usa `{}` como marcadores de posición.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
show: $.[title,body]
|
||||
format: <h1>{}</h1> <p>{}</p>
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "En este ejemplo, el primer `{}` será reemplazado con el *título*, y el segundo `{}` será reemplazado con el *cuerpo*."
|
||||
|
||||
**Formato avanzado:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/1
|
||||
show: $.[name,email,age]
|
||||
format: **Nombre:** {} | **Email:** {} | **Edad:** {}
|
||||
```
|
||||
~~~
|
||||
|
||||
**Formato de lista:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer {{TODOIST_TOKEN}}"}
|
||||
show: $..content
|
||||
format: - [ ] {}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! danger "Advertencia de Seguridad"
|
||||
Las cadenas de formato se sanitizan para prevenir ataques XSS. Las etiquetas de script, manejadores de eventos y URIs javascript: se eliminan automáticamente.
|
||||
|
||||
---
|
||||
|
||||
### properties
|
||||
|
||||
Actualiza las propiedades del frontmatter con datos de la respuesta. Requiere una respuesta JSON y la bandera `show`.
|
||||
|
||||
**Sintaxis:**
|
||||
Los datos deben ser nombres de propiedades separados por comas.
|
||||
|
||||
**Ejemplo:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
show: $.[id,title]
|
||||
properties: id, title
|
||||
```
|
||||
~~~
|
||||
|
||||
**Para enlaces internos, usa la sintaxis `[[..]]`:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/related
|
||||
show: $.[id,name]
|
||||
properties: relatedId, [[relatedNote]]
|
||||
```
|
||||
~~~
|
||||
|
||||
Esto creará:
|
||||
```yaml
|
||||
---
|
||||
relatedId: 123
|
||||
relatedNote: "[[Note Name]]"
|
||||
---
|
||||
```
|
||||
|
||||
!!! tip "Caso de Uso"
|
||||
Excelente para poblar automáticamente metadatos desde respuestas API.
|
||||
|
||||
---
|
||||
|
||||
## 💡 Patrones Comunes
|
||||
|
||||
### Flujo de Autenticación
|
||||
|
||||
1. Obtener token de autenticación (oculto):
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/auth/login
|
||||
method: post
|
||||
body: {"username": "{{this.username}}", "password": "{{this.password}}"}
|
||||
req-uuid: auth
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
2. Usar el token en solicitudes subsecuentes:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/data
|
||||
headers: {"Authorization": "Bearer {{ls.auth>$.token}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
### Manejo de Errores
|
||||
|
||||
Si una solicitud falla, se mostrará un mensaje de error:
|
||||
|
||||
```
|
||||
Error: Failed to fetch
|
||||
```
|
||||
|
||||
Errores comunes:
|
||||
- **Error de Red**: Verifica tu conexión a internet
|
||||
- **401 No Autorizado**: Verifica tu clave/token API
|
||||
- **404 No Encontrado**: Verifica que la URL sea correcta
|
||||
- **JSONPath Inválido**: Verifica tu expresión `show:`
|
||||
|
||||
### Consejos de Rendimiento
|
||||
|
||||
1. **Almacena en caché las respuestas** - Usa `req-uuid` para datos que no cambian a menudo
|
||||
2. **Usa `disabled`** - Deshabilita temporalmente solicitudes costosas durante el desarrollo
|
||||
3. **Usa `hidden`** - Oculta la salida para la obtención de datos en segundo plano
|
||||
|
||||
---
|
||||
|
||||
## 📚 Lecturas Adicionales
|
||||
|
||||
- [Ejemplos de Sintaxis JSONPath](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples)
|
||||
- [Probador en Línea de JSONPath](https://jsonpath-plus.github.io/JSONPath/demo/)
|
||||
- [Casos de Uso](usecase/index.md) - Ejemplos del mundo real
|
||||
|
||||
---
|
||||
|
||||
## ❓ Solución de Problemas
|
||||
|
||||
**¿La solicitud no funciona?**
|
||||
|
||||
- Verifica la consola (Ctrl+Shift+I) para mensajes de error detallados
|
||||
- Verifica que tu URL sea correcta y use https://
|
||||
- Verifica que todas las banderas requeridas estén presentes
|
||||
- Asegúrate de que tu expresión JSONPath sea válida
|
||||
|
||||
**¿Los datos en caché no se actualizan?**
|
||||
|
||||
- Elimina `req-uuid` para siempre obtener datos frescos
|
||||
- O agrega la bandera `auto-update` para forzar la actualización
|
||||
- O limpia manualmente el caché en Configuración
|
||||
|
||||
**¿Las variables no se resuelven?**
|
||||
|
||||
- Verifica que la sintaxis del frontmatter sea correcta
|
||||
- Verifica que las variables globales estén definidas en la configuración
|
||||
- Asegúrate de estar usando la sintaxis correcta de variables
|
||||
|
||||
---
|
||||
|
||||
!!! tip "¿Necesitas Ayuda?"
|
||||
Si encuentras problemas o tienes preguntas, por favor abre un issue en [GitHub](https://github.com/Rooyca/obsidian-api-request/issues).
|
||||
|
|
|
|||
|
|
@ -1,14 +1,34 @@
|
|||
# 🔎 APIRequest
|
||||
# 🔎 Descripción General
|
||||
|
||||
APIRequest (APIR) es un plugin para [Obsidian](https://obsidian.md/) que te permite realizar solicitudes HTTP y mostrar la respuesta en tus notas.
|
||||
APIRequest (APIR) es un plugin para la aplicación de toma de notas [Obsidian](https://obsidian.md/) que te permite realizar solicitudes a APIs y mostrar la respuesta directamente en tus notas.
|
||||
|
||||
## 🔥 Características
|
||||
|
||||
- Realiza solicitudes HTTP utilizando varios métodos como `GET`, `POST`, `PUT` y `DELETE`.
|
||||
- Utiliza variables del front-matter dentro de bloques de código.
|
||||
- Guarda respuestas en `localStorage` para un acceso y reutilización convenientes.
|
||||
- Desactiva bloques de código según sea necesario para optimizar el rendimiento.
|
||||
- Muestra valores específicos de las respuestas, proporcionando un control detallado sobre la presentación de datos.
|
||||
- **Múltiples Métodos HTTP**: Realiza solicitudes usando `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD` y `OPTIONS`.
|
||||
- **Sustitución de Variables**: Utiliza variables del `front-matter`, variables globales o incluso reutiliza respuestas de otros bloques de código.
|
||||
- **Caché de Respuestas**: Guarda respuestas en `localStorage` para acceso y reutilización conveniente entre notas.
|
||||
- **Control de Rendimiento**: Desactiva bloques de código según sea necesario para optimizar el rendimiento.
|
||||
- **Extracción Precisa de Datos**: Muestra valores específicos de las respuestas usando JSONPath, proporcionando control granular sobre la presentación de datos.
|
||||
- **Seguridad Primero**: Validación y sanitización de entradas incorporada para proteger contra XSS, ataques de inyección y directory traversal.
|
||||
- **Auto-actualización**: Actualiza automáticamente respuestas en caché cuando sea necesario.
|
||||
- **Formato de Salida**: Formato HTML/texto personalizado para datos de respuesta.
|
||||
|
||||
## 🔒 Características de Seguridad
|
||||
|
||||
APIRequest implementa medidas de seguridad completas:
|
||||
|
||||
- ✅ **Validación de URL**: Solo se permiten protocolos HTTPS y HTTP
|
||||
- ✅ **Sanitización de Entradas**: Todas las entradas de usuario son validadas y sanitizadas
|
||||
- ✅ **Prevención de XSS**: La salida HTML es sanitizada para prevenir inyección de scripts
|
||||
- ✅ **Protección contra Directory Traversal**: Las rutas de archivos son validadas para prevenir acceso no autorizado
|
||||
- ✅ **JSONPath Seguro**: Las expresiones JSONPath son validadas antes de la ejecución
|
||||
- ✅ **Sanitización de UUID**: Los identificadores de solicitud son sanitizados para prevenir ataques de inyección
|
||||
|
||||
!!! warning "Mejores Prácticas de Seguridad"
|
||||
- Siempre usa URLs HTTPS al realizar solicitudes API
|
||||
- Almacena claves API en variables globales (Configuración → APIRequest → Variables globales), nunca en notas
|
||||
- Revisa y limpia regularmente respuestas en caché (Configuración → APIRequest → Solicitudes API Guardadas)
|
||||
- Solo conéctate a endpoints API de confianza
|
||||
|
||||
## ⚡ Cómo usar
|
||||
|
||||
|
|
@ -23,7 +43,49 @@ method: post
|
|||
body: {"id":1}
|
||||
headers: {"Accept": "application/json"}
|
||||
show: $.id
|
||||
req-uuid: id-persona
|
||||
req-uuid: IDpersona
|
||||
disabled
|
||||
```
|
||||
~~~
|
||||
~~~
|
||||
|
||||
## 📚 Inicio Rápido
|
||||
|
||||
1. **Solicitud GET Simple**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.github.com/users/octocat
|
||||
show: $.name
|
||||
```
|
||||
~~~
|
||||
|
||||
2. **Usando Variables**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/{{this.userId}}
|
||||
headers: {"Authorization": "Bearer {{API_TOKEN}}"}
|
||||
show: $.data.name
|
||||
```
|
||||
~~~
|
||||
|
||||
3. **Almacenando Respuestas en Caché**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/data
|
||||
req-uuid: mydata
|
||||
show: $.result
|
||||
```
|
||||
~~~
|
||||
|
||||
4. **Reutilizando Datos en Caché**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/more-data
|
||||
headers: {"X-Token": "{{ls.mydata>$.token}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
Para documentación detallada sobre todas las banderas y características, consulta [Bloques de código](codeblocks.md).
|
||||
|
|
|
|||
|
|
@ -1,9 +1,34 @@
|
|||
# Ejemplos de uso
|
||||
# Casos de uso
|
||||
|
||||
A collection of use cases for this plugin. **If you have a one, please share it with us.**
|
||||
Una colección ejemplos de uso para este plugin. **Si tienes uno, por favor compártelo con nosotros.**
|
||||
Una colección de casos de uso para este complemento. **Si tienes uno, por favor compártelo con nosotros.**
|
||||
|
||||
## Ver el precio de BITCOIN (o cualquier criptomoneda)
|
||||
## Uso de respuestas en línea con [Dataview](https://blacksmithgu.github.io/obsidian-dataview/)
|
||||
|
||||
Primero, haz una solicitud y almacena la respuesta usando `req-uuid`:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/comments/1
|
||||
req-uuid: test
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
A continuación, con **DataviewJS** y las consultas JavaScript en línea habilitadas, puedes acceder a los datos guardados de esta manera:
|
||||
|
||||
```markdown
|
||||
El correo electrónico es `$=dv.el("span", JSON.parse(localStorage.getItem("req-test")).email)`
|
||||
y el ID es `$=dv.el("span", JSON.parse(localStorage.getItem("req-test")).id, { cls: "mod-warning" })`
|
||||
```
|
||||
|
||||
Esto recupera el correo electrónico y el ID de la respuesta guardada (el prefijo `req-` siempre es requerido).
|
||||
Aquí, también estamos añadiendo una clase personalizada a la segunda consulta en línea.
|
||||
|
||||
La salida renderizada se verá así:
|
||||
|
||||
> El correo electrónico es [Eliseo@gardner.biz](mailto:Eliseo@gardner.biz) y el ID es <span style="color:red;">1</span>
|
||||
|
||||
## Consultar el precio de BITCOIN (o cualquier criptomoneda)
|
||||
|
||||
~~~makdown
|
||||
```req
|
||||
|
|
@ -23,7 +48,7 @@ show: $.main.temp
|
|||
```
|
||||
~~~
|
||||
|
||||
## Buscar peliculas
|
||||
## Buscar películas
|
||||
|
||||
~~~makdown
|
||||
```req
|
||||
|
|
@ -32,25 +57,27 @@ show: $.results[0:].title
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! info "Nota el uso de `{{this.title}}`. Esta es una característica que te permite pasar propiedades del front-matter."
|
||||
!!! info "Observa el uso de `{{this.title}}`. Esta es una característica que te permite pasar propiedades del front-matter."
|
||||
|
||||
## Obtener más de un resultado
|
||||
## Renderizar datos
|
||||
|
||||
~~~makdown
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://mapi.mobilelegends.com/hero/detail?id={{this.file.name}}
|
||||
url: https://mapi.mobilelegends.com/hero/detail?id=1
|
||||
show: $.data[cover_picture,name,type]
|
||||
format:  <br> <strong>Name:</strong> {} <br> <strong>Type:</strong> {}
|
||||
```
|
||||
~~~
|
||||
|
||||
## Obtener Tareas de [todoist](https://todoist.com/)
|
||||
## Obtener TAREAS desde [todoist](https://todoist.com/)
|
||||
|
||||
~~~makdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer YOUR_TOKEN"}
|
||||
show: $..content
|
||||
req-uuid: todos
|
||||
format: - [ ] {}
|
||||
req-id: todos
|
||||
```
|
||||
~~~
|
||||
|
||||
|
|
@ -58,4 +85,4 @@ req-uuid: todos
|
|||
|
||||
## Tu caso de uso
|
||||
|
||||
> **Si deseas compartir tu caso de uso, por favor siéntete libre de abrir una PR o un [Issue](https://github.com/Rooyca/obsidian-api-request/issues/new/choose)**
|
||||
> **Si quieres compartir tu caso de uso, siéntete libre de abrir un PR o un [Issue](https://github.com/Rooyca/obsidian-api-request/issues/new/choose).**
|
||||
|
|
@ -1,43 +1,181 @@
|
|||
# 👨🏻💻 代码块
|
||||
|
||||
`codeblock` 是一个多功能块,可用于用不同语言编写代码。在本例中,我们将使用它来发出请求。
|
||||
`codeblock` 是一个多功能代码块,可用于直接从你的笔记中发送 API 请求。本指南涵盖所有可用的标志以及如何有效且安全地使用它们。
|
||||
|
||||
## 🏳️ Flag
|
||||
## 🏳️ 标志
|
||||
|
||||
Flag是指定请求参数以及我们想要的响应格式的方式。
|
||||
标志是指定请求参数的方式。所有标志都**不区分大小写**。
|
||||
|
||||
| 标志 | 默认 |
|
||||
| ------------| ---------|
|
||||
| [url](#url) | |
|
||||
| [method](#method) | GET |
|
||||
| [body](#body) | |
|
||||
| [headers](#headers) | |
|
||||
| [show](#show) | ALL |
|
||||
| [req-id](#req-id) | req-general |
|
||||
| [disabled](#disabled) | |
|
||||
| [save-as](#save-as) | |
|
||||
| 标志 | 默认值 | 必需 | 描述 |
|
||||
| ------------| ---------| -------- | ----------- |
|
||||
| [url](#url) | | ✅ 是 | 要请求的 API 端点 |
|
||||
| [method](#method) | GET | 否 | HTTP 方法 (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
|
||||
| [body](#body) | | 否 | 请求体 (JSON 格式) |
|
||||
| [headers](#headers) | | 否 | 请求头 (JSON 格式) |
|
||||
| [show](#show) | ALL | 否 | 用于提取特定数据的 JSONPath |
|
||||
| [req-uuid](#req-uuid) | req-general | 否 | 用于缓存的唯一 ID |
|
||||
| [hidden](#hidden) | FALSE | 否 | 隐藏输出 |
|
||||
| [disabled](#disabled) | | 否 | 禁用请求 |
|
||||
| [save-as](#save-as) | | 否 | 将响应保存到文件 |
|
||||
| [auto-update](#auto-update) | FALSE | 否 | 总是获取新数据 |
|
||||
| [format](#format) | | 否 | 自定义输出格式 |
|
||||
| [properties](#properties) | | 否 | 更新前置元数据属性 |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 安全注意事项
|
||||
|
||||
!!! danger "重要安全指南"
|
||||
- **始终使用 HTTPS**: 使用 `https://` URL 以确保加密通信
|
||||
- **保护 API 密钥**: 将敏感令牌存储在全局变量中,永远不要在笔记中硬编码
|
||||
- **验证输入**: 插件会验证所有输入,但只连接到受信任的 API
|
||||
- **检查缓存数据**: 定期从设置中清除旧的缓存响应
|
||||
|
||||
### 输入验证
|
||||
|
||||
所有用户输入都会自动验证:
|
||||
|
||||
- **URL**: 只允许 `http://` 和 `https://` 协议
|
||||
- **UUID**: 仅清理为字母数字、连字符和下划线
|
||||
- **文件路径**: 阻止目录遍历 (`..`) 和绝对路径
|
||||
- **JSONPath**: 验证表达式以防止脚本注入
|
||||
- **格式字符串**: 清理 HTML 以防止 XSS 攻击
|
||||
|
||||
---
|
||||
|
||||
## 📖 变量与数据复用
|
||||
|
||||
### 本地存储与变量
|
||||
|
||||
API 响应可以存储在 `localStorage` 中,并在其他代码块或笔记中重用。要存储响应,必须使用 `req-uuid` 标志为其分配唯一标识符。
|
||||
|
||||
你可以使用以下语法访问存储的响应:
|
||||
|
||||
```
|
||||
{{ls.UUID>JSONPath}}
|
||||
```
|
||||
|
||||
* `UUID`: 在 `req-uuid` 标志中定义的唯一标识符(不含 `req-` 前缀)
|
||||
* `JSONPath`: 你想要从响应中获取的特定数据的路径
|
||||
|
||||
**示例:**
|
||||
如果你有一个带有 `req-uuid: user` 的请求,你可以这样访问用户的名字:
|
||||
|
||||
```
|
||||
{{ls.user>$.name}}
|
||||
```
|
||||
|
||||
!!! info "安全说明"
|
||||
UUID 会自动清理。只允许字母数字字符、连字符和下划线。
|
||||
|
||||
---
|
||||
|
||||
### 前置元数据变量
|
||||
|
||||
你可以使用以下语法引用笔记**前置元数据**中定义的变量:
|
||||
|
||||
```
|
||||
{{this.variableName}}
|
||||
```
|
||||
|
||||
**示例:**
|
||||
|
||||
前置元数据:
|
||||
```yaml
|
||||
---
|
||||
userId: 12345
|
||||
title: "我的笔记"
|
||||
---
|
||||
```
|
||||
|
||||
在请求中使用:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/{{this.userId}}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! warning "安全警告"
|
||||
避免在前置元数据中存储敏感的 API 密钥。请改用全局变量。
|
||||
|
||||
---
|
||||
|
||||
### 全局变量
|
||||
|
||||
在插件设置中定义可重用的变量(设置 → APIRequest → 全局变量)。这些变量会被安全存储,可以通过以下方式访问:
|
||||
|
||||
```
|
||||
{{VARIABLE_NAME}}
|
||||
```
|
||||
|
||||
**示例:**
|
||||
|
||||
1. 在插件设置中添加:
|
||||
- 键: `API_KEY`
|
||||
- 值: `your-secret-key`
|
||||
|
||||
2. 在你的请求中:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/data
|
||||
headers: {"Authorization": "Bearer {{API_KEY}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "最佳实践"
|
||||
将所有 API 密钥和令牌存储为全局变量,以保持笔记的安全性和可移植性。
|
||||
|
||||
---
|
||||
|
||||
### 特殊变量
|
||||
|
||||
- `{{this.file.name}}` - 当前文件的基本名称(不含扩展名)
|
||||
|
||||
---
|
||||
|
||||
## 标志详情
|
||||
|
||||
### url
|
||||
|
||||
是唯一的**必需**标志。它指定请求的端点。可以使用 `frontmatter` 中定义的变量。
|
||||
唯一**必需**的标志。它指定 API 请求的端点。
|
||||
|
||||
**语法:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/endpoint
|
||||
```
|
||||
~~~
|
||||
|
||||
**使用变量:**
|
||||
~~~markdown
|
||||
```req
|
||||
# 这只是一个注释
|
||||
url: https://jsonplaceholder.typicode.com/users/{{this.id}}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "其中`{{this.id}}`是frontmatter中定义的变量(`id`)。"
|
||||
!!! note "其中 `{{this.id}}` 是在前置元数据中定义的变量 (`id`)。"
|
||||
|
||||
!!! danger "安全性"
|
||||
只允许 HTTPS 和 HTTP URL。出于安全考虑,其他协议(file://、javascript: 等)会被阻止。
|
||||
|
||||
---
|
||||
|
||||
### method
|
||||
|
||||
指定请求方法。默认值为 `GET`,可用值为:
|
||||
指定 HTTP 请求方法。默认值为 `GET`。
|
||||
|
||||
- GET
|
||||
**可用方法:**
|
||||
- GET (默认)
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- HEAD
|
||||
- OPTIONS
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
|
|
@ -45,24 +183,39 @@ method: post
|
|||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
### body
|
||||
|
||||
指定请求的正文。默认值为空对象。数据应为 JSON 格式,双引号用冒号和空格分隔键和值。可以使用 `frontmatter` 中定义的变量。
|
||||
指定请求体。数据应为 JSON 格式。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
method: post
|
||||
body: {"title": {{this.title}}, "body": "bar", "userId": 1}
|
||||
body: {"title": "{{this.filename}}", "body": "bar", "userId": 1}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "其中 `{{this.title}}` 是 frontmatter 中定义的变量(`title`)。"
|
||||
!!! note "其中 `{{this.filename}}` 是工作文件的名称。"
|
||||
|
||||
**使用变量的高级用法:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/create
|
||||
method: post
|
||||
body: {"userId": {{this.userId}}, "token": "{{API_TOKEN}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
### headers
|
||||
|
||||
指定请求的标头。默认值为空对象。数据应为 JSON 格式,双引号将键和值用冒号和空格分隔。可以使用 `frontmatter` 中定义的变量。
|
||||
指定请求头。数据应为 JSON 格式。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts
|
||||
|
|
@ -71,145 +224,398 @@ headers: {"Content-type": "application/json; charset=UTF-8"}
|
|||
```
|
||||
~~~
|
||||
|
||||
**使用缓存响应作为请求头:**
|
||||
|
||||
你可以使用其他请求的响应作为 headers/body/url/show。例如,如果你有一个带有 `req-uuid: token` 的请求,你可以这样使用:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer {{ls.token>$.access_token}}"}
|
||||
show: $..content
|
||||
format: - [ ] {}
|
||||
req-uuid: todos
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "常用请求头"
|
||||
- `Content-Type: application/json` - 用于 JSON 数据
|
||||
- `Authorization: Bearer TOKEN` - 用于 API 认证
|
||||
- `Accept: application/json` - 请求 JSON 响应
|
||||
|
||||
---
|
||||
|
||||
### show
|
||||
|
||||
指定要显示的响应数据。使用右箭头 `->` 访问嵌套对象。默认值为 `ALL`。
|
||||
使用 JSONPath 表达式指定要显示响应中的哪些数据。查看 [JSONPath 示例](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples),或尝试 [jsonpath-plus](https://jsonpath-plus.github.io/JSONPath/demo/) 在线工具。
|
||||
|
||||
**简单示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
show: chess_daily -> last -> rating
|
||||
show: $['chess_daily']['last']['rating']
|
||||
```
|
||||
~~~
|
||||
|
||||
可以通过用逗号分隔来显示多个输出。
|
||||
|
||||
**使用方括号的多个输出:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
show: chess_daily -> last -> rating, chess_daily -> best -> rating
|
||||
format: <p>Last game: {}</p> <strong>Best game: {}</strong>
|
||||
render
|
||||
show: $.chess_daily[last,best].rating
|
||||
```
|
||||
~~~
|
||||
|
||||
也可以使用 `{..}` 循环遍历数组。以下示例从所有用户中检索城市 (city)。
|
||||
|
||||
**使用 + 的多个输出:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
show: {..} -> address -> city
|
||||
url: https://api.chess.com/pub/player/hikaru/stats
|
||||
show: $.chess_daily[last,best].rating + $.chess960_daily[last,best].rating
|
||||
```
|
||||
~~~
|
||||
|
||||
也可以使用 `{n..n}` 循环遍历数组中指定数量的元素。
|
||||
**循环遍历数组:**
|
||||
|
||||
获取所有用户的城市:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
show: {0..2} -> address -> city
|
||||
show: $..address.city
|
||||
```
|
||||
~~~
|
||||
|
||||
也可以使用 `{n-n-n}` 循环遍历数组的指定范围的索引。
|
||||
|
||||
**循环遍历前 N 个元素:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
show: {0-2-1} -> address -> city
|
||||
show: $..[:3].address.city
|
||||
```
|
||||
~~~
|
||||
|
||||
您可以使用 `{-1}` 访问最后一个元素...
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url:https://api.modrinth.com/v2/project/distanthorizons
|
||||
show: game_versions -> {-1}
|
||||
```
|
||||
~~~
|
||||
|
||||
...或者使用 `{len}` 获取数组的长度。
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url:https://api.modrinth.com/v2/project/distanthorizons
|
||||
show: game_versions -> {len}
|
||||
```
|
||||
~~~
|
||||
|
||||
使用 `{..}` 时,若要同时访问多个元素,请使用 `&` 分隔键,并使用 `.` 访问值。
|
||||
|
||||
**特定数组索引:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key=API_KEY&format=json&limit=4
|
||||
show: recenttracks -> track -> {..} -> name & artist.#text & streamable
|
||||
maketable: name, artist, stream
|
||||
url: https://jsonplaceholder.typicode.com/users
|
||||
show: $..[3,2,6].address.city
|
||||
```
|
||||
~~~
|
||||
|
||||
### req-id
|
||||
**最后一个元素:**
|
||||
|
||||
指定请求的 ID。默认值为 `req-general`。当我们想要将响应存储在 `localStorage` 中并在其他块或注释中使用它时,这很有用。
|
||||
你可以使用 `(@.length-1)` 或只使用 `[-1:]` 访问最后一个元素:
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.modrinth.com/v2/project/distanthorizons
|
||||
show: $.game_versions[(@.length-1)]
|
||||
```
|
||||
~~~
|
||||
|
||||
**多个属性:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=rooyca&api_key={{API_KEY}}&format=json&limit=4
|
||||
show: $..recenttracks.track[0:][streamable,name,artist]
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! info "安全说明"
|
||||
JSONPath 表达式会被验证以防止脚本注入。恶意表达式将被阻止。
|
||||
|
||||
---
|
||||
|
||||
### req-uuid
|
||||
|
||||
指定用于在 `localStorage` 中缓存请求响应的唯一标识符。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
show: $.name
|
||||
req-uuid: test-{{this.username}}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "其中 `{{this.username}}` 是在前置元数据中定义的变量 (`username`)。"
|
||||
|
||||
**访问缓存响应:**
|
||||
|
||||
可以使用 `req-uuid` 访问存储的响应(这不会触发新请求):
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
show: name
|
||||
req-id: name
|
||||
req-uuid: name
|
||||
```
|
||||
~~~
|
||||
|
||||
可以使用带有 `disabled` 标志的 `req-id` 访问存储的响应(不会触发新请求)。
|
||||
**与 Dataview 一起使用:**
|
||||
|
||||
也可以使用 [dataview](https://blacksmithgu.github.io/obsidian-dataview/) 访问响应:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
req-id: name
|
||||
disabled
|
||||
```dataviewjs
|
||||
dv.paragraph(localStorage.getItem("req-UUID"))
|
||||
```
|
||||
~~~
|
||||
|
||||
也可以使用 [dataview](https://blacksmithgu.github.io/dataview/) 访问响应。
|
||||
!!! info "通过 localStorage 访问时始终使用 `req-` 前缀"
|
||||
通过 localStorage 直接访问时,使用: `localStorage.getItem("req-UUID")`
|
||||
|
||||
**删除缓存响应:**
|
||||
|
||||
从代码中:
|
||||
~~~markdown
|
||||
```dataview
|
||||
dv.paragraph(localStorage.getItem("req-name"))
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! info "在`req-id`标志中定义的任何内容之前,必须使用`req-`"
|
||||
|
||||
要从 localStorage 中删除响应,请运行:
|
||||
|
||||
~~~markdown
|
||||
```dataview
|
||||
```dataviewjs
|
||||
localStorage.removeItem("req-name")
|
||||
```
|
||||
~~~
|
||||
|
||||
要删除所有响应,请转到设置并单击 `Clear ID's` (清除ID) 按钮。
|
||||
从设置中:
|
||||
转到 设置 → APIRequest → 已保存的 API 请求,然后点击你想删除的响应。
|
||||
|
||||
### disabled
|
||||
!!! tip "缓存管理"
|
||||
定期检查并清除旧的缓存响应以释放 localStorage 空间。
|
||||
|
||||
禁用请求。如果指定了 `req-id`,APIR 将在`localStorage`中检查响应。如果未找到,它将发出新请求并存储它。之后,APIR 将使用存储的响应。
|
||||
---
|
||||
|
||||
### hidden
|
||||
|
||||
执行代码块但不显示其输出。适用于缓存数据的后台请求。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
show: name
|
||||
req-id: name
|
||||
req-uuid: name
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "用例"
|
||||
非常适合获取身份验证令牌或其他你想缓存但不显示的数据。
|
||||
|
||||
---
|
||||
|
||||
### disabled
|
||||
|
||||
禁用请求。代码块不会被执行。适用于临时禁用请求而不删除它们。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/users/1
|
||||
show: $.name
|
||||
req-uuid: name
|
||||
disabled
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! tip "开发提示"
|
||||
在开发期间使用 `disabled` 以防止达到 API 速率限制。
|
||||
|
||||
---
|
||||
|
||||
### save-as
|
||||
|
||||
指定保存响应的路径。它将保存整个响应。需要文件扩展名。它不会创建目录。
|
||||
将整个响应保存到文件。需要文件扩展名。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
save-as: posts/1.json
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! warning "重要"
|
||||
- 文件路径会进行安全验证
|
||||
- 阻止目录遍历 (`..`)
|
||||
- 不允许绝对路径
|
||||
- 文件路径相对于你的仓库根目录
|
||||
- 不会自动创建目录
|
||||
|
||||
!!! tip "文件管理"
|
||||
在使用 `save-as` 之前,在你的仓库中创建目标目录。
|
||||
|
||||
---
|
||||
|
||||
### auto-update
|
||||
|
||||
强制代码块每次都获取新数据,忽略缓存响应。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
req-uuid: firstPost
|
||||
auto-update
|
||||
save-as: posts/1.json
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "何时使用"
|
||||
当你需要实时数据或缓存响应过时时使用 `auto-update`。
|
||||
|
||||
**不使用 `req-uuid`:** 请求默认总是获取新数据。
|
||||
**使用 `req-uuid`:** 不使用 `auto-update` 时,会使用缓存响应。
|
||||
**使用 `auto-update`:** 忽略缓存并总是获取新数据。
|
||||
|
||||
---
|
||||
|
||||
### format
|
||||
|
||||
指定显示响应的自定义格式。支持 HTML 并使用 `{}` 作为占位符。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
show: $.[title,body]
|
||||
format: <h1>{}</h1> <p>{}</p>
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! note "在此示例中,第一个 `{}` 将被替换为 *title*,第二个 `{}` 将被替换为 *body*。"
|
||||
|
||||
**高级格式化:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/1
|
||||
show: $.[name,email,age]
|
||||
format: **Name:** {} | **Email:** {} | **Age:** {}
|
||||
```
|
||||
~~~
|
||||
|
||||
**列表格式化:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.todoist.com/rest/v2/tasks
|
||||
headers: {"Authorization": "Bearer {{TODOIST_TOKEN}}"}
|
||||
show: $..content
|
||||
format: - [ ] {}
|
||||
```
|
||||
~~~
|
||||
|
||||
!!! danger "安全警告"
|
||||
格式字符串会被清理以防止 XSS 攻击。脚本标签、事件处理程序和 javascript: URI 会被自动删除。
|
||||
|
||||
---
|
||||
|
||||
### properties
|
||||
|
||||
使用响应数据更新前置元数据属性。需要 JSON 响应和 `show` 标志。
|
||||
|
||||
**语法:**
|
||||
数据应为用逗号分隔的属性名称。
|
||||
|
||||
**示例:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/posts/1
|
||||
show: $.[id,title]
|
||||
properties: id, title
|
||||
```
|
||||
~~~
|
||||
|
||||
**对于内部链接,使用 `[[..]]` 语法:**
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/related
|
||||
show: $.[id,name]
|
||||
properties: relatedId, [[relatedNote]]
|
||||
```
|
||||
~~~
|
||||
|
||||
这将创建:
|
||||
```yaml
|
||||
---
|
||||
relatedId: 123
|
||||
relatedNote: "[[Note Name]]"
|
||||
---
|
||||
```
|
||||
|
||||
!!! tip "用例"
|
||||
非常适合从 API 响应中自动填充元数据。
|
||||
|
||||
---
|
||||
|
||||
## 💡 常见模式
|
||||
|
||||
### 身份验证流程
|
||||
|
||||
1. 获取身份验证令牌(隐藏):
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/auth/login
|
||||
method: post
|
||||
body: {"username": "{{this.username}}", "password": "{{this.password}}"}
|
||||
req-uuid: auth
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
2. 在后续请求中使用令牌:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/data
|
||||
headers: {"Authorization": "Bearer {{ls.auth>$.token}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
### 错误处理
|
||||
|
||||
如果请求失败,将显示错误消息:
|
||||
|
||||
```
|
||||
Error: Failed to fetch
|
||||
```
|
||||
|
||||
常见错误:
|
||||
- **网络错误**: 检查你的互联网连接
|
||||
- **401 未授权**: 检查你的 API 密钥/令牌
|
||||
- **404 未找到**: 验证 URL 是否正确
|
||||
- **无效 JSONPath**: 检查你的 `show:` 表达式
|
||||
|
||||
### 性能提示
|
||||
|
||||
1. **缓存响应** - 对于不经常更改的数据使用 `req-uuid`
|
||||
2. **使用 `disabled`** - 在开发期间临时禁用昂贵的请求
|
||||
3. **使用 `hidden`** - 隐藏后台数据获取的输出
|
||||
|
||||
---
|
||||
|
||||
## 📚 扩展阅读
|
||||
|
||||
- [JSONPath 语法示例](https://github.com/JSONPath-Plus/JSONPath?tab=readme-ov-file#syntax-through-examples)
|
||||
- [JSONPath 在线测试工具](https://jsonpath-plus.github.io/JSONPath/demo/)
|
||||
- [用例](usecase/index.md) - 真实世界示例
|
||||
|
||||
---
|
||||
|
||||
## ❓ 故障排除
|
||||
|
||||
**请求不工作?**
|
||||
|
||||
- 检查控制台 (Ctrl+Shift+I) 查看详细错误消息
|
||||
- 验证你的 URL 是否正确并使用 https://
|
||||
- 检查是否存在所有必需的标志
|
||||
- 确保你的 JSONPath 表达式有效
|
||||
|
||||
**缓存数据未更新?**
|
||||
|
||||
- 删除 `req-uuid` 以始终获取新数据
|
||||
- 或添加 `auto-update` 标志以强制刷新
|
||||
- 或在设置中手动清除缓存
|
||||
|
||||
**变量未解析?**
|
||||
|
||||
- 检查前置元数据语法是否正确
|
||||
- 验证全局变量是否在设置中定义
|
||||
- 确保你使用正确的变量语法
|
||||
|
||||
---
|
||||
|
||||
!!! tip "需要帮助?"
|
||||
如果你遇到问题或有疑问,请在 [GitHub](https://github.com/Rooyca/obsidian-api-request/issues) 上提出问题。
|
||||
|
|
|
|||
|
|
@ -1,20 +1,40 @@
|
|||
# 🔎 概述
|
||||
|
||||
APIRequest (APIR) 是笔记应用 [Obsidian](https://obsidian.md/) 的一个插件,它允许您向 api 或其他来源发出请求并在笔记中显示响应。
|
||||
APIRequest (APIR) 是一个用于笔记应用 [Obsidian](https://obsidian.md/) 的插件,允许您向 API 发出请求并直接在笔记中显示响应。
|
||||
|
||||
## 🔥 功能
|
||||
## 🔥 功能特性
|
||||
|
||||
- 使用各种方法执行 HTTP 请求,例如 `GET`、`POST`、`PUT` 和 `DELETE`。
|
||||
- 在代码块内利用前言中的变量。
|
||||
- 将响应保存在 `localStorage` 中,以方便访问和重用。
|
||||
- 根据需要禁用代码块以优化性能。
|
||||
- 以指定的间隔多次重复请求,促进自动化任务或连续数据检索而无需人工干预。
|
||||
- **多种 HTTP 方法**:使用 `GET`、`POST`、`PUT`、`DELETE`、`PATCH`、`HEAD` 和 `OPTIONS` 执行请求。
|
||||
- **变量替换**:利用 `front-matter` 中的变量、全局变量,甚至重用其他代码块的响应。
|
||||
- **响应缓存**:将响应保存在 `localStorage` 中,方便跨笔记访问和重用。
|
||||
- **性能控制**:根据需要禁用代码块以优化性能。
|
||||
- **精确数据提取**:使用 JSONPath 显示响应中的特定值,提供对数据呈现的细粒度控制。
|
||||
- **安全优先**:内置输入验证和清理,防止 XSS、注入攻击和目录遍历。
|
||||
- **自动更新**:在需要时自动刷新缓存的响应。
|
||||
- **格式化输出**:为响应数据自定义 HTML/文本格式。
|
||||
|
||||
## 🔒 安全功能
|
||||
|
||||
APIRequest 实现了全面的安全措施:
|
||||
|
||||
- ✅ **URL 验证**:仅允许 HTTPS 和 HTTP 协议
|
||||
- ✅ **输入清理**:所有用户输入都经过验证和清理
|
||||
- ✅ **XSS 防护**:清理 HTML 输出以防止脚本注入
|
||||
- ✅ **目录遍历保护**:验证文件路径以防止未授权访问
|
||||
- ✅ **安全 JSONPath**:在执行前验证 JSONPath 表达式
|
||||
- ✅ **UUID 清理**:清理请求标识符以防止注入攻击
|
||||
|
||||
!!! warning "安全最佳实践"
|
||||
- 进行 API 请求时始终使用 HTTPS URL
|
||||
- 将 API 密钥存储在全局变量中(设置 → APIRequest → 全局变量),而不是笔记中
|
||||
- 定期审查和清除缓存的响应(设置 → APIRequest → 已保存的 API 请求)
|
||||
- 仅连接到受信任的 API 端点
|
||||
|
||||
## ⚡ 如何使用
|
||||
|
||||
### 👨🏻💻 代码块
|
||||
|
||||
要使用它,请创建一个代码块,并将语言设置为 `req`。在代码块内,您可以指定`url`、`method`、`body`、`headers` 等。有关更多信息,请参阅[可用标志](codeblocks.md#flags)。
|
||||
要使用它,创建一个语言设置为 `req` 的代码块。在代码块内,您可以指定 `url`、`method`、`body`、`headers` 等。有关更多信息,请参阅[可用标志](codeblocks.md#flags)。
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
|
|
@ -23,7 +43,49 @@ method: post
|
|||
body: {"id":1}
|
||||
headers: {"Accept": "application/json"}
|
||||
show: $.id
|
||||
req-uuid: id-persona
|
||||
req-uuid: IDpersona
|
||||
disabled
|
||||
```
|
||||
~~~
|
||||
|
||||
## 📚 快速开始
|
||||
|
||||
1. **简单的 GET 请求**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.github.com/users/octocat
|
||||
show: $.name
|
||||
```
|
||||
~~~
|
||||
|
||||
2. **使用变量**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/user/{{this.userId}}
|
||||
headers: {"Authorization": "Bearer {{API_TOKEN}}"}
|
||||
show: $.data.name
|
||||
```
|
||||
~~~
|
||||
|
||||
3. **缓存响应**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/data
|
||||
req-uuid: mydata
|
||||
show: $.result
|
||||
```
|
||||
~~~
|
||||
|
||||
4. **重用缓存数据**
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://api.example.com/more-data
|
||||
headers: {"X-Token": "{{ls.mydata>$.token}}"}
|
||||
```
|
||||
~~~
|
||||
|
||||
有关所有标志和功能的详细文档,请参阅[代码块](codeblocks.md)。
|
||||
|
|
|
|||
|
|
@ -1,9 +1,34 @@
|
|||
# 用例
|
||||
# 使用案例
|
||||
|
||||
此插件的用例集合。 **如果您有,请与我们分享。**
|
||||
这是该插件的使用案例集合。**如果您有其他使用案例,请与我们分享。**
|
||||
|
||||
## 使用内联响应与 [Dataview](https://blacksmithgu.github.io/obsidian-dataview/) 结合
|
||||
|
||||
## 检查比特币(或任何加密货币)价格
|
||||
首先,使用 `req-uuid` 发起请求并存储响应:
|
||||
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://jsonplaceholder.typicode.com/comments/1
|
||||
req-uuid: test
|
||||
hidden
|
||||
```
|
||||
~~~
|
||||
|
||||
然后,启用 **DataviewJS** 和内联 JavaScript 查询后,您可以像这样访问保存的数据:
|
||||
|
||||
```markdown
|
||||
邮箱是 `$=dv.el("span", JSON.parse(localStorage.getItem("req-test")).email)`
|
||||
ID 是 `$=dv.el("span", JSON.parse(localStorage.getItem("req-test")).id, { cls: "mod-warning" })`
|
||||
```
|
||||
|
||||
这将从保存的响应中检索邮箱和 ID(始终需要 `req-` 前缀)。
|
||||
在这里,我们还为第二个内联查询添加了自定义类。
|
||||
|
||||
渲染输出将如下所示:
|
||||
|
||||
> 邮箱是 [Eliseo@gardner.biz](mailto:Eliseo@gardner.biz),ID 是 <span style="color:red;">1</span>
|
||||
|
||||
## 查询比特币(或任何加密货币)价格
|
||||
|
||||
~~~makdown
|
||||
```req
|
||||
|
|
@ -14,7 +39,7 @@ show: $.data.rateUsd
|
|||
|
||||
> 64992.8972508856324769
|
||||
|
||||
## 获取天气
|
||||
## 获取天气信息
|
||||
|
||||
~~~makdown
|
||||
```req
|
||||
|
|
@ -32,18 +57,19 @@ show: $.results[0:].title
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! info "请注意使用 `{{this.title}}`。此功能允许您传递前置属性。"
|
||||
!!! info "注意 `{{this.title}}` 的使用。这是一个允许您传递 front-matter 属性的功能。"
|
||||
|
||||
## 获得多个结果
|
||||
## 渲染数据
|
||||
|
||||
~~~makdown
|
||||
~~~markdown
|
||||
```req
|
||||
url: https://mapi.mobilelegends.com/hero/detail?id={{this.file.name}}
|
||||
url: https://mapi.mobilelegends.com/hero/detail?id=1
|
||||
show: $.data[cover_picture,name,type]
|
||||
format:  <br> <strong>Name:</strong> {} <br> <strong>Type:</strong> {}
|
||||
```
|
||||
~~~
|
||||
|
||||
## 从 [todoist](https://todoist.com/) 获取 TODOS
|
||||
## 从 [todoist](https://todoist.com/) 获取待办事项
|
||||
|
||||
~~~makdown
|
||||
```req
|
||||
|
|
@ -55,8 +81,8 @@ req-id: todos
|
|||
```
|
||||
~~~
|
||||
|
||||
!!! warning "这将把响应保存在 localStorage 中的键 `req-todos` 下"
|
||||
!!! warning "这将把响应保存在 localStorage 中,键名为 `req-todos`"
|
||||
|
||||
## 您的用例
|
||||
## 您的使用案例
|
||||
|
||||
> **如果您想分享您的用例,请随时打开 PR 或 [Issue](https://github.com/Rooyca/obsidian-api-request/issues/new/choose)。**
|
||||
> **如果您想分享您的使用案例,请随时提交 PR 或创建 [Issue](https://github.com/Rooyca/obsidian-api-request/issues/new/choose)。**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "api-request",
|
||||
"name": "APIRequest",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Integrate API data into your notes with request caching, variable support, and precise JSON extraction.",
|
||||
"author": "rooyca",
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "api-request",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "api-request",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"description": "Integrate API data into your notes with request caching, variable support, and precise JSON extraction.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,34 @@
|
|||
// frontmatter read and parse
|
||||
/**
|
||||
* Utilities for reading and parsing frontmatter from markdown files
|
||||
*/
|
||||
|
||||
import { parseYaml } from "obsidian";
|
||||
|
||||
/** Regular expression to extract frontmatter content from markdown */
|
||||
export const FRONTMATTER_REGEX = /^\n*---[^\n]*\n+(?<fm>.+?)\n+---.*/s;
|
||||
|
||||
/** Type definition for frontmatter content */
|
||||
export type Frontmatter = string | null | undefined;
|
||||
|
||||
/**
|
||||
* Extracts frontmatter content from markdown text
|
||||
*
|
||||
* @param md - The markdown content to extract frontmatter from
|
||||
* @returns The frontmatter content string, or undefined if not found
|
||||
*/
|
||||
export function readFrontmatter(md: string) {
|
||||
const result = md.match(FRONTMATTER_REGEX);
|
||||
|
||||
return result?.groups?.fm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses frontmatter YAML content into an object
|
||||
*
|
||||
* @param input - The frontmatter content to parse
|
||||
* @returns Parsed frontmatter object
|
||||
* @throws Error if frontmatter is not defined or cannot be parsed
|
||||
*/
|
||||
export function parseFrontmatter(input: Frontmatter) {
|
||||
if (input === undefined || input === null) {
|
||||
throw new Error("Frontmatter not defined.");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
// Adds the copy button to the code block
|
||||
// Take from: https://github.com/jdbrice/obsidian-code-block-copy/
|
||||
/**
|
||||
* Adds a copy button to the code block element
|
||||
* Button copies the specified text to clipboard when clicked
|
||||
*
|
||||
* @param el - The HTML element to add the button to
|
||||
* @param copyThis - The text to copy to clipboard
|
||||
* @source https://github.com/jdbrice/obsidian-code-block-copy/
|
||||
*/
|
||||
export function addBtnCopy(el: HTMLElement, copyThis: string) {
|
||||
const btnCopy = el.createEl("button", { cls: "copy-req", text: "copy" });
|
||||
btnCopy.addEventListener('click', function () {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
// Matches a specific pattern inside curly braces: {number1..number2}
|
||||
/**
|
||||
* Regular expression utilities for parsing request blocks
|
||||
*/
|
||||
|
||||
/** Matches a numeric range pattern inside curly braces: {number1..number2} */
|
||||
export const num_braces_regx = /{(\d+)\.\.(\d+)}/;
|
||||
|
||||
// Matches a pattern of digits separated by hyphens, e.g., 1-2-3-4
|
||||
/** Matches a pattern of digits separated by hyphens, e.g., 1-2-3-4 */
|
||||
export const num_hyphen_regx = /(\d+-)+\d+/;
|
||||
|
||||
// Matches any sequence of digits, globally.
|
||||
/** Matches any sequence of digits, globally */
|
||||
export const nums_rex = /\d+/g;
|
||||
|
||||
// Matches anything inside curly braces, non-greedy, globally.
|
||||
/** Matches anything inside curly braces, non-greedy, globally */
|
||||
export const in_braces_regx = /{.*?}/g;
|
||||
|
||||
// Matches any text inside double curly braces, preceded by "{{this." and followed by "}}"
|
||||
/** Matches text inside double curly braces, preceded by "{{this." and followed by "}}" */
|
||||
export const varname_regx = /{{this\.([^{}]*)}}/g;
|
||||
|
||||
// Matches "{{this." or "}}" globally
|
||||
/** Matches "{{this." or "}}" globally - used for removing variable syntax */
|
||||
export const no_varname_regx = /{{this\.|}}/g;
|
||||
|
||||
// Matches "{{KEY}}" globally
|
||||
/** Matches "{{KEY}}" globally - for global variable replacement */
|
||||
export const key_regx = /{{(?!this\.)[^{}]*}}/g;
|
||||
196
src/functions/security.ts
Normal file
196
src/functions/security.ts
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Security utilities for input validation and sanitization
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates a URL to ensure it's safe to request
|
||||
* @param url - The URL to validate
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function isValidUrl(url: string): boolean {
|
||||
if (!url || typeof url !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
// Only allow http and https protocols
|
||||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent local network access (optional - can be configured)
|
||||
// Uncomment to prevent requests to localhost/private IPs
|
||||
// const hostname = parsed.hostname.toLowerCase();
|
||||
// if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates HTTP method
|
||||
* @param method - The HTTP method to validate
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function isValidMethod(method: string): boolean {
|
||||
const allowedMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
||||
return allowedMethods.includes(method.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a UUID to prevent injection attacks
|
||||
* @param uuid - The UUID to sanitize
|
||||
* @returns Sanitized UUID or null if invalid
|
||||
*/
|
||||
export function sanitizeUuid(uuid: string): string | null {
|
||||
if (!uuid || typeof uuid !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only allow alphanumeric characters, hyphens, and underscores
|
||||
const sanitized = uuid.replace(/[^a-zA-Z0-9\-_]/g, '');
|
||||
|
||||
// Limit length to prevent storage issues
|
||||
if (sanitized.length > 100) {
|
||||
return sanitized.substring(0, 100);
|
||||
}
|
||||
|
||||
return sanitized || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates JSONPath expression to prevent malicious code execution
|
||||
* @param jsonPath - The JSONPath expression to validate
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function isValidJsonPath(jsonPath: string): boolean {
|
||||
if (!jsonPath || typeof jsonPath !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for potential script injection
|
||||
const dangerousPatterns = [
|
||||
/<script/i,
|
||||
/javascript:/i,
|
||||
/on\w+=/i, // event handlers like onclick=
|
||||
/eval\(/i,
|
||||
/Function\(/i,
|
||||
/setTimeout\(/i,
|
||||
/setInterval\(/i,
|
||||
];
|
||||
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(jsonPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Basic JSONPath should start with $ or be a simple path
|
||||
if (!jsonPath.startsWith('$') && !jsonPath.startsWith('.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes HTML to prevent XSS attacks
|
||||
* @param html - The HTML string to sanitize
|
||||
* @returns Sanitized HTML string
|
||||
*/
|
||||
export function sanitizeHtml(html: string): string {
|
||||
if (!html || typeof html !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove script tags and event handlers
|
||||
return html
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
|
||||
.replace(/on\w+\s*=\s*[^\s>]*/gi, '')
|
||||
.replace(/javascript:/gi, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates format string to prevent XSS
|
||||
* @param format - The format string to validate
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function isValidFormat(format: string): boolean {
|
||||
if (!format || typeof format !== 'string') {
|
||||
return true; // Empty format is valid
|
||||
}
|
||||
|
||||
// Check for script tags and event handlers
|
||||
const dangerousPatterns = [
|
||||
/<script/i,
|
||||
/javascript:/i,
|
||||
/on\w+=/i,
|
||||
/<iframe/i,
|
||||
/<embed/i,
|
||||
/<object/i,
|
||||
];
|
||||
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(format)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates file path to prevent directory traversal attacks
|
||||
* @param filePath - The file path to validate
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function isValidFilePath(filePath: string): boolean {
|
||||
if (!filePath || typeof filePath !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent directory traversal
|
||||
if (filePath.includes('..') || filePath.includes('//')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent absolute paths (should be relative to vault)
|
||||
if (filePath.startsWith('/') || filePath.match(/^[a-zA-Z]:\\/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely parse JSON with error handling
|
||||
* @param jsonString - The JSON string to parse
|
||||
* @returns Parsed object or null if invalid
|
||||
*/
|
||||
export function safeJsonParse(jsonString: string): any {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates localStorage key to prevent attacks
|
||||
* @param key - The localStorage key to validate
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function isValidStorageKey(key: string): boolean {
|
||||
if (!key || typeof key !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow safe characters
|
||||
return /^[a-zA-Z0-9\-_]+$/.test(key);
|
||||
}
|
||||
444
src/main.ts
444
src/main.ts
|
|
@ -5,6 +5,8 @@ import {
|
|||
requestUrl,
|
||||
debounce,
|
||||
Menu,
|
||||
TFile,
|
||||
TAbstractFile,
|
||||
} from "obsidian";
|
||||
import {
|
||||
readFrontmatter,
|
||||
|
|
@ -12,12 +14,61 @@ import {
|
|||
} from "src/functions/frontmatterUtils";
|
||||
import { addBtnCopy } from "src/functions/general";
|
||||
import { varname_regx, no_varname_regx, key_regx } from "src/functions/regx";
|
||||
import {
|
||||
isValidUrl,
|
||||
isValidMethod,
|
||||
sanitizeUuid,
|
||||
isValidJsonPath,
|
||||
sanitizeHtml,
|
||||
isValidFormat,
|
||||
isValidFilePath,
|
||||
safeJsonParse,
|
||||
isValidStorageKey
|
||||
} from "src/functions/security";
|
||||
import APRSettings from "src/settings/settingsTab";
|
||||
import { JSONPath } from "jsonpath-plus";
|
||||
import { LoadAPIRSettings, DEFAULT_SETTINGS } from "src/settings/settingsData";
|
||||
|
||||
// Get global variables (defined in settings)
|
||||
export function checkGlobalValue(value: string, settings: LoadAPIRSettings) {
|
||||
/**
|
||||
* Interface for tracking request code blocks in the editor
|
||||
*/
|
||||
interface ReqCodeBlock {
|
||||
/** Unique identifier for the request (optional) */
|
||||
uuid: string | null;
|
||||
/** Index of the block in the document */
|
||||
index: number;
|
||||
/** Line number where the code block starts */
|
||||
lineStart: number;
|
||||
/** Whether the request is disabled */
|
||||
disabled: boolean;
|
||||
/** Whether the request should auto-update */
|
||||
autoUpdate: boolean;
|
||||
/** Whether the request is active (for UI styling) */
|
||||
isActive: boolean;
|
||||
/** Display name for the request in the UI */
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for key-value pairs in plugin settings
|
||||
*/
|
||||
interface KeyValuePair {
|
||||
/** The key identifier */
|
||||
key: string;
|
||||
/** The value associated with the key */
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves global variables defined in plugin settings and replaces them in the input string
|
||||
* @param value - The string containing variable placeholders in the format {{KEY}}
|
||||
* @param settings - The plugin settings containing key-value pairs
|
||||
* @returns The string with variables replaced by their values
|
||||
* @example
|
||||
* // If settings has {key: "API_KEY", value: "12345"}
|
||||
* checkGlobalValue("url={{API_KEY}}", settings) // returns "url=12345"
|
||||
*/
|
||||
export function checkGlobalValue(value: string, settings: LoadAPIRSettings): string {
|
||||
const match = value.match(key_regx);
|
||||
|
||||
if (match) {
|
||||
|
|
@ -25,7 +76,7 @@ export function checkGlobalValue(value: string, settings: LoadAPIRSettings) {
|
|||
const key = match[i].replace(/{{|}}/g, "");
|
||||
value = value.replace(
|
||||
match[i],
|
||||
settings.KeyValueCodeblocks.find((obj) => obj.key === key)
|
||||
(settings.KeyValueCodeblocks as KeyValuePair[]).find((obj) => obj.key === key)
|
||||
?.value || "",
|
||||
);
|
||||
}
|
||||
|
|
@ -33,9 +84,18 @@ export function checkGlobalValue(value: string, settings: LoadAPIRSettings) {
|
|||
return value;
|
||||
}
|
||||
|
||||
// Get data from localStorage using this syntax: {{ls.UUID>JSONPath}}
|
||||
// where `ls` stands for `localStorage`
|
||||
export function checkLocalStorage(value: string) {
|
||||
/**
|
||||
* Retrieves data from localStorage using the syntax {{ls.UUID>JSONPath}}
|
||||
* where 'ls' stands for 'localStorage'
|
||||
*
|
||||
* @param value - The string containing localStorage references
|
||||
* @returns The string with localStorage references replaced by their values
|
||||
* @security Validates UUID format and JSONPath expressions to prevent injection attacks
|
||||
* @example
|
||||
* // If localStorage has "req-mydata" with {user: {name: "John"}}
|
||||
* checkLocalStorage("{{ls.mydata>$.user.name}}") // returns "John"
|
||||
*/
|
||||
export function checkLocalStorage(value: string): string {
|
||||
const match = value.match(key_regx);
|
||||
|
||||
if (match) {
|
||||
|
|
@ -43,28 +103,57 @@ export function checkLocalStorage(value: string) {
|
|||
const key = match[i].replace(/{{|}}/g, "");
|
||||
let uuid = key.split(">")[0];
|
||||
const jsonPath = key.split(">")[1];
|
||||
uuid = uuid.split(".")[1]
|
||||
uuid = uuid.split(".")[1];
|
||||
|
||||
// Validate uuid and jsonPath for security
|
||||
const sanitizedUuid = sanitizeUuid(uuid);
|
||||
if (!sanitizedUuid) {
|
||||
console.warn("Invalid UUID format:", uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (jsonPath && !isValidJsonPath(jsonPath)) {
|
||||
console.warn("Invalid JSONPath expression:", jsonPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try with req- prefix first (for req-uuid data)
|
||||
let data = localStorage.getItem(`req-${uuid}`);
|
||||
let data = localStorage.getItem(`req-${sanitizedUuid}`);
|
||||
|
||||
// If not found, try without prefix (for manual variables)
|
||||
if (!data) {
|
||||
data = localStorage.getItem(uuid);
|
||||
data = localStorage.getItem(sanitizedUuid);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
const parsedData = JSON.parse(data);
|
||||
const output = JSONPath({ path: jsonPath, json: parsedData });
|
||||
value = value.replace(match[i], output);
|
||||
const parsedData = safeJsonParse(data);
|
||||
if (parsedData && jsonPath) {
|
||||
try {
|
||||
const output = JSONPath({ path: jsonPath, json: parsedData });
|
||||
value = value.replace(match[i], output);
|
||||
} catch (e) {
|
||||
console.error("JSONPath evaluation error:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// parse headers and body to valid JSON
|
||||
export function parseToValidJson(input, type) {
|
||||
/**
|
||||
* Parses and validates input string to JSON format
|
||||
* Attempts to convert various quote styles to valid JSON
|
||||
*
|
||||
* @param input - The input string to parse
|
||||
* @param type - The type of data being parsed (for error messages)
|
||||
* @returns Parsed JSON object or null if input is empty
|
||||
* @throws Error if the input cannot be parsed to valid JSON
|
||||
* @example
|
||||
* parseToValidJson("{key: value}", "headers") // returns {"key": "value"}
|
||||
* parseToValidJson("'key': 'value'", "body") // returns {"key": "value"}
|
||||
*/
|
||||
export function parseToValidJson(input: string, type: string): Record<string, any> | null {
|
||||
const trimmedInput = input.trim();
|
||||
|
||||
if (!trimmedInput) {
|
||||
|
|
@ -89,7 +178,16 @@ export function parseToValidJson(input, type) {
|
|||
}
|
||||
}
|
||||
|
||||
// format the output to be displayed
|
||||
/**
|
||||
* Formats output for display in the code block
|
||||
* Handles arrays, objects, and primitive values
|
||||
*
|
||||
* @param output - The output to format (can be any type)
|
||||
* @returns Formatted string representation of the output
|
||||
* @example
|
||||
* formatOutput([1, 2, 3]) // returns "1, 2, 3"
|
||||
* formatOutput({key: "value"}) // returns '{\n "key": "value"\n}'
|
||||
*/
|
||||
export function formatOutput(output: any): string {
|
||||
// If output is Array
|
||||
if (Array.isArray(output)) {
|
||||
|
|
@ -113,54 +211,98 @@ export function formatOutput(output: any): string {
|
|||
return String(output ?? "");
|
||||
}
|
||||
|
||||
// Check if the value has variables and replace them
|
||||
export function checkVariables(req_prop: string, settings: LoadAPIRSettings) {
|
||||
// search value in localStorage
|
||||
req_prop = checkLocalStorage(req_prop);
|
||||
// search value globally
|
||||
req_prop = checkGlobalValue(req_prop, settings);
|
||||
const match = req_prop.match(varname_regx);
|
||||
/**
|
||||
* Checks if the value contains variables and replaces them with actual values
|
||||
* Supports:
|
||||
* - localStorage references: {{ls.UUID>JSONPath}}
|
||||
* - Global variables: {{KEY}}
|
||||
* - Frontmatter variables: {{this.propertyName}}
|
||||
* - File name: {{this.file.name}}
|
||||
*
|
||||
* @param req_prop - The string containing variable placeholders
|
||||
* @param settings - The plugin settings
|
||||
* @returns The string with variables replaced, or undefined if an error occurs
|
||||
* @security All variable values are validated before substitution
|
||||
* @example
|
||||
* checkVariables("{{this.file.name}}", settings) // returns current file name
|
||||
* checkVariables("{{API_KEY}}", settings) // returns value from global settings
|
||||
*/
|
||||
export function checkVariables(req_prop: string, settings: LoadAPIRSettings): string | undefined {
|
||||
try {
|
||||
// search value in localStorage
|
||||
req_prop = checkLocalStorage(req_prop);
|
||||
// search value globally
|
||||
req_prop = checkGlobalValue(req_prop, settings);
|
||||
const match = req_prop.match(varname_regx);
|
||||
|
||||
if (match) {
|
||||
for (let i = 0; i < match.length; i++) {
|
||||
const var_name = match[i].replace(no_varname_regx, "");
|
||||
if (match) {
|
||||
for (let i = 0; i < match.length; i++) {
|
||||
const var_name = match[i].replace(no_varname_regx, "");
|
||||
|
||||
// if {{this.file.name}} return filename
|
||||
if (var_name == "file.name") {
|
||||
req_prop = req_prop.replace(
|
||||
match[i],
|
||||
this.app.workspace.getActiveFile().basename,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// if {{this.file.name}} return filename
|
||||
if (var_name == "file.name") {
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFile) {
|
||||
console.warn("No active file found");
|
||||
continue;
|
||||
}
|
||||
req_prop = req_prop.replace(
|
||||
match[i],
|
||||
activeFile.basename,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const activeView =
|
||||
this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
const markdownContent = activeView.editor.getValue();
|
||||
const activeView =
|
||||
this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
|
||||
if (!activeView) {
|
||||
console.warn("No active markdown view found");
|
||||
continue;
|
||||
}
|
||||
|
||||
const markdownContent = activeView.editor.getValue();
|
||||
|
||||
try {
|
||||
const frontmatterData = parseFrontmatter(
|
||||
readFrontmatter(markdownContent),
|
||||
);
|
||||
req_prop = req_prop.replace(
|
||||
match[i],
|
||||
frontmatterData[var_name] || "",
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
new Notice("Error: " + e.message);
|
||||
return;
|
||||
try {
|
||||
const frontmatterData = parseFrontmatter(
|
||||
readFrontmatter(markdownContent),
|
||||
);
|
||||
req_prop = req_prop.replace(
|
||||
match[i],
|
||||
frontmatterData[var_name] || "",
|
||||
);
|
||||
} catch (e: any) {
|
||||
console.error("Frontmatter parsing error:", e.message);
|
||||
new Notice("Error reading frontmatter: " + e.message);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
return req_prop;
|
||||
} catch (e: any) {
|
||||
console.error("Variable substitution error:", e);
|
||||
new Notice("Error processing variables: " + e.message);
|
||||
return undefined;
|
||||
}
|
||||
return req_prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main plugin class for API Request functionality
|
||||
* Allows users to make HTTP requests directly from Obsidian code blocks
|
||||
* and display responses with caching, variable substitution, and data extraction
|
||||
*/
|
||||
export default class MainAPIR extends Plugin {
|
||||
/** Plugin settings */
|
||||
settings: LoadAPIRSettings;
|
||||
/** Status bar element for displaying request count */
|
||||
private statusBarItem: HTMLElement;
|
||||
/** List of request code blocks in the current file */
|
||||
private reqBlocks: ReqCodeBlock[] = [];
|
||||
|
||||
/**
|
||||
* Called when the plugin is loaded
|
||||
* Registers code block processor, event handlers, and settings tab
|
||||
*/
|
||||
async onload() {
|
||||
console.log("Loading: api-request");
|
||||
await this.loadSettings();
|
||||
|
|
@ -228,7 +370,7 @@ export default class MainAPIR extends Plugin {
|
|||
// get the method and check if is a valid method
|
||||
} else if (lowercaseLine.startsWith("method:")) {
|
||||
method = line.replace(/method:/i, "").toUpperCase().trim();
|
||||
if (!allowedMethods.includes(method)) {
|
||||
if (!isValidMethod(method)) {
|
||||
el.createEl("strong", {
|
||||
text: `Error: Method ${method} not supported`,
|
||||
});
|
||||
|
|
@ -248,6 +390,14 @@ export default class MainAPIR extends Plugin {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL for security
|
||||
if (!isValidUrl(URL)) {
|
||||
el.createEl("strong", {
|
||||
text: "Error: Invalid or unsafe URL",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// extract data using jsonpath-plus (https://www.npmjs.com/package/jsonpath-plus)
|
||||
} else if (lowercaseLine.startsWith("show:")) {
|
||||
|
|
@ -262,6 +412,17 @@ export default class MainAPIR extends Plugin {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate JSONPath for security
|
||||
const paths = show.split(" + ");
|
||||
for (const path of paths) {
|
||||
if (!isValidJsonPath(path.trim())) {
|
||||
el.createEl("strong", {
|
||||
text: "Error: Invalid JSONPath expression",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// get headers. They can use double, single quotes or none
|
||||
} else if (lowercaseLine.startsWith("headers:")) {
|
||||
|
|
@ -276,8 +437,8 @@ export default class MainAPIR extends Plugin {
|
|||
tempHeaders,
|
||||
"headers",
|
||||
);
|
||||
} catch (e) {
|
||||
el.createEl("strong", { text: e.message });
|
||||
} catch (e: any) {
|
||||
el.createEl("strong", { text: e.message || "Error parsing headers" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -291,8 +452,9 @@ export default class MainAPIR extends Plugin {
|
|||
|
||||
try {
|
||||
body = parseToValidJson(tempBody, "body");
|
||||
} catch {
|
||||
} catch (e: any) {
|
||||
// use raw body if it's not a valid JSON
|
||||
console.log("Using raw body (not valid JSON):", e.message);
|
||||
body = tempBody;
|
||||
}
|
||||
|
||||
|
|
@ -305,6 +467,14 @@ export default class MainAPIR extends Plugin {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file path for security
|
||||
if (!isValidFilePath(saveTo)) {
|
||||
el.createEl("strong", {
|
||||
text: "Error: Invalid file path. Path traversal and absolute paths are not allowed.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (lowercaseLine.startsWith("req-uuid:")) {
|
||||
uuid = line.replace(/req-uuid:/i, "").trim();
|
||||
if (!uuid) {
|
||||
|
|
@ -313,18 +483,35 @@ export default class MainAPIR extends Plugin {
|
|||
});
|
||||
return;
|
||||
}
|
||||
uuid =
|
||||
uuid =
|
||||
checkVariables(
|
||||
uuid,
|
||||
this.settings,
|
||||
) ?? "";
|
||||
uuid = `req-${uuid}`
|
||||
|
||||
// Sanitize UUID for security
|
||||
const sanitized = sanitizeUuid(uuid);
|
||||
if (!sanitized) {
|
||||
el.createEl("strong", {
|
||||
text: "Error: Invalid UUID format",
|
||||
});
|
||||
return;
|
||||
}
|
||||
uuid = `req-${sanitized}`;
|
||||
} else if (lowercaseLine.startsWith("auto-update")) {
|
||||
autoUpdate = true;
|
||||
} else if (lowercaseLine.startsWith("hidden")) {
|
||||
hidden = true;
|
||||
} else if (lowercaseLine.startsWith("hidden")) {
|
||||
hidden = true;
|
||||
} else if (lowercaseLine.startsWith("format:")) {
|
||||
format = line.replace(/format:/i, "").trim();
|
||||
|
||||
// Validate format for XSS prevention
|
||||
if (!isValidFormat(format)) {
|
||||
el.createEl("strong", {
|
||||
text: "Error: Invalid format string. Script tags and event handlers are not allowed.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (lowercaseLine.startsWith("properties:")) {
|
||||
properties = line
|
||||
.replace(/properties:/i, "")
|
||||
|
|
@ -333,23 +520,31 @@ export default class MainAPIR extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
let responseData;
|
||||
let responseDataText;
|
||||
let responseData: any;
|
||||
let responseDataText: string | undefined;
|
||||
|
||||
// Check if the response is cached in localStorage
|
||||
if (uuid && !autoUpdate) {
|
||||
const cachedResponse = localStorage.getItem(uuid);
|
||||
if (cachedResponse) {
|
||||
responseData = JSON.parse(cachedResponse);
|
||||
const temp_uuid = uuid.split("req-")[1]
|
||||
new Notice(`Using cached data with UUID: ${temp_uuid}`);
|
||||
try {
|
||||
const cachedResponse = localStorage.getItem(uuid);
|
||||
if (cachedResponse) {
|
||||
responseData = safeJsonParse(cachedResponse);
|
||||
if (responseData) {
|
||||
const temp_uuid = uuid.split("req-")[1];
|
||||
new Notice(`Using cached data with UUID: ${temp_uuid}`);
|
||||
} else {
|
||||
console.warn("Failed to parse cached data");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error reading from localStorage:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// If no cached data or auto-update is requested, make a new request
|
||||
if (!responseData || autoUpdate) {
|
||||
try {
|
||||
body = method == "GET" ? undefined : JSON.stringify(body);
|
||||
body = method == "GET" ? undefined : JSON.stringify(body);
|
||||
const response = await requestUrl({
|
||||
url: URL,
|
||||
method,
|
||||
|
|
@ -361,12 +556,17 @@ export default class MainAPIR extends Plugin {
|
|||
|
||||
// Cache the response in localStorage if req-uuid is provided
|
||||
if (uuid) {
|
||||
localStorage.setItem(
|
||||
uuid,
|
||||
JSON.stringify(responseData),
|
||||
);
|
||||
try {
|
||||
localStorage.setItem(
|
||||
uuid,
|
||||
JSON.stringify(responseData),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error saving to localStorage:", e);
|
||||
new Notice("Warning: Failed to cache response");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
console.error(e.message);
|
||||
new Notice("Error: " + e.message);
|
||||
responseData = `Error: ${e.message}`;
|
||||
|
|
@ -382,8 +582,22 @@ export default class MainAPIR extends Plugin {
|
|||
// check if the user defined more than one path
|
||||
// if so, iterate over each path and get the data
|
||||
output = show.length > 1
|
||||
? show.map((path) => JSONPath({ path: path.trim(), json: responseData }))
|
||||
: JSONPath({ path: show[0], json: responseData });
|
||||
? show.map((path) => {
|
||||
try {
|
||||
return JSONPath({ path: path.trim(), json: responseData });
|
||||
} catch (e: any) {
|
||||
console.error("JSONPath error for path:", path, e);
|
||||
return `Error: ${e.message}`;
|
||||
}
|
||||
})
|
||||
: (() => {
|
||||
try {
|
||||
return JSONPath({ path: show[0], json: responseData });
|
||||
} catch (e: any) {
|
||||
console.error("JSONPath error:", e);
|
||||
return `Error: ${e.message}`;
|
||||
}
|
||||
})();
|
||||
|
||||
if (properties.length > 0 && properties[0] !== '') {
|
||||
// Format the output and split it into an array
|
||||
|
|
@ -446,26 +660,41 @@ export default class MainAPIR extends Plugin {
|
|||
// try to create the file. It'll fail if already exists
|
||||
await this.app.vault.create(
|
||||
saveTo,
|
||||
responseDataText,
|
||||
responseDataText || "",
|
||||
);
|
||||
new Notice("Saved to: " + saveTo);
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
// try to modify the file
|
||||
const file =
|
||||
this.app.vault.getAbstractFileByPath(saveTo);
|
||||
await this.app.vault.modify(file, responseDataText);
|
||||
new Notice("File modified");
|
||||
try {
|
||||
const file = this.app.vault.getAbstractFileByPath(saveTo);
|
||||
if (file instanceof TFile) {
|
||||
await this.app.vault.modify(file, responseDataText || "");
|
||||
new Notice("File modified");
|
||||
} else {
|
||||
new Notice("Error: Could not save to file");
|
||||
console.error("File save error:", e);
|
||||
}
|
||||
} catch (modifyError: any) {
|
||||
new Notice("Error: Failed to save file");
|
||||
console.error("File modification error:", modifyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a *format* is defined in the codeblock
|
||||
// render the response, else just *return* the response as String
|
||||
if (hidden) {
|
||||
return;
|
||||
return;
|
||||
} else if (format) {
|
||||
const parts = formattedOutput.split(",");
|
||||
el.innerHTML = format.replace(/{}/g, () => parts.shift() || "");
|
||||
} else {
|
||||
// Sanitize the format output to prevent XSS
|
||||
const sanitizedFormat = sanitizeHtml(format);
|
||||
el.innerHTML = sanitizedFormat.replace(/{}/g, () => {
|
||||
const part = parts.shift() || "";
|
||||
// Escape the part to prevent XSS
|
||||
return sanitizeHtml(part);
|
||||
});
|
||||
} else {
|
||||
el.createEl("pre", { text: formattedOutput });
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +710,12 @@ export default class MainAPIR extends Plugin {
|
|||
this.addSettingTab(new APRSettings(this.app, this));
|
||||
}
|
||||
|
||||
// Parse all req code blocks from the active file
|
||||
/**
|
||||
* Parses all req code blocks from the active file
|
||||
* Extracts metadata like UUID, disabled status, and auto-update flag
|
||||
*
|
||||
* @returns Array of parsed request code blocks
|
||||
*/
|
||||
private parseReqBlocks(): ReqCodeBlock[] {
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!view) return [];
|
||||
|
|
@ -549,8 +783,11 @@ export default class MainAPIR extends Plugin {
|
|||
return blocks;
|
||||
}
|
||||
|
||||
// Update the status bar display
|
||||
private updateStatusBar() {
|
||||
/**
|
||||
* Updates the status bar display with request count
|
||||
* Shows/hides based on settings and number of requests
|
||||
*/
|
||||
updateStatusBar() {
|
||||
// Check if status bar is enabled
|
||||
if (!this.settings.enableStatusBar) {
|
||||
this.statusBarItem.style.display = 'none';
|
||||
|
|
@ -585,7 +822,12 @@ export default class MainAPIR extends Plugin {
|
|||
`;
|
||||
}
|
||||
|
||||
// Show menu with all requests
|
||||
/**
|
||||
* Shows a menu with all available request blocks
|
||||
* Allows navigation to specific blocks
|
||||
*
|
||||
* @param event - The mouse event that triggered the menu
|
||||
*/
|
||||
private showRequestMenu(event: MouseEvent) {
|
||||
const menu = new Menu();
|
||||
|
||||
|
|
@ -600,15 +842,19 @@ export default class MainAPIR extends Plugin {
|
|||
item.setTitle(block.displayName);
|
||||
item.setIcon('rocket');
|
||||
|
||||
// Add color styling using settings colors
|
||||
const color = block.isActive
|
||||
? this.settings.statusBarActiveColor
|
||||
: this.settings.statusBarInactiveColor;
|
||||
item.dom.style.color = color;
|
||||
|
||||
// Store color in a CSS variable that can be used by styles
|
||||
const isActive = block.isActive;
|
||||
item.onClick(() => {
|
||||
this.navigateToBlock(block);
|
||||
});
|
||||
|
||||
// Add custom styling via callback
|
||||
const color = isActive
|
||||
? this.settings.statusBarActiveColor
|
||||
: this.settings.statusBarInactiveColor;
|
||||
|
||||
// Use setIcon with color hint through title
|
||||
item.setTitle(`${block.displayName}${isActive ? ' 🟢' : ' ⚪'}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -616,7 +862,12 @@ export default class MainAPIR extends Plugin {
|
|||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
// Navigate to the code block in the editor
|
||||
/**
|
||||
* Navigates to a specific code block in the editor
|
||||
* Scrolls to the block and selects it for visual feedback
|
||||
*
|
||||
* @param block - The request block to navigate to
|
||||
*/
|
||||
private navigateToBlock(block: ReqCodeBlock) {
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!view) {
|
||||
|
|
@ -656,10 +907,16 @@ export default class MainAPIR extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the plugin is unloaded
|
||||
*/
|
||||
onunload() {
|
||||
console.log("Unloading: api-request");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads plugin settings from storage
|
||||
*/
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
|
|
@ -668,6 +925,9 @@ export default class MainAPIR extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves plugin settings to storage
|
||||
*/
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,37 @@
|
|||
/**
|
||||
* Interface for key-value pairs in plugin settings
|
||||
*/
|
||||
interface KeyValuePair {
|
||||
/** The key identifier */
|
||||
key: string;
|
||||
/** The value associated with the key */
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin settings interface
|
||||
* Contains all user-configurable options for the API Request plugin
|
||||
*/
|
||||
export interface LoadAPIRSettings {
|
||||
/** Text to display when a request is disabled */
|
||||
DisabledReq: string;
|
||||
/** Temporary key input field value */
|
||||
Key: string;
|
||||
/** Temporary value input field value */
|
||||
Value: string;
|
||||
KeyValueCodeblocks: object[];
|
||||
/** Array of global key-value pairs for variable substitution */
|
||||
KeyValueCodeblocks: KeyValuePair[];
|
||||
/** Whether to show the interactive status bar */
|
||||
enableStatusBar: boolean;
|
||||
/** Color for active requests in status bar (hex color) */
|
||||
statusBarActiveColor: string;
|
||||
/** Color for inactive/disabled requests in status bar (hex color) */
|
||||
statusBarInactiveColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default plugin settings
|
||||
*/
|
||||
export const DEFAULT_SETTINGS: LoadAPIRSettings = {
|
||||
DisabledReq: '>> Disabled <<',
|
||||
Key: '',
|
||||
|
|
|
|||
Loading…
Reference in a new issue