Add excel-to-markdown skill and converter script
- Add excel_to_markdown.py script for converting Excel files to Markdown tables - Create excel-to-markdown skill with SKILL.md documentation - Support automatic data range detection and flexible output options - Include features for row/column numbering and partial conversions - Add skill-creator reference documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# UV
|
||||
.venv/
|
||||
uv.lock
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Project specific
|
||||
*.xlsx
|
||||
*.xls
|
||||
|
||||
# .claude
|
||||
.claude/
|
||||
220
scripts/excel_to_markdown.py
Normal file
220
scripts/excel_to_markdown.py
Normal file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Excel to Markdown Converter
|
||||
将Excel文档转换为Markdown表格格式
|
||||
"""
|
||||
|
||||
import openpyxl
|
||||
from openpyxl.utils import get_column_letter
|
||||
from typing import Optional, Tuple
|
||||
import argparse
|
||||
|
||||
|
||||
class ExcelToMarkdown:
|
||||
"""Excel转Markdown转换器"""
|
||||
|
||||
def __init__(self, excel_file: str):
|
||||
"""
|
||||
初始化转换器
|
||||
|
||||
Args:
|
||||
excel_file: Excel文件路径
|
||||
"""
|
||||
self.workbook = openpyxl.load_workbook(excel_file, data_only=True)
|
||||
self.sheet = self.workbook.active
|
||||
|
||||
def detect_data_range(self, max_scan_rows: int = 100, max_scan_cols: int = 100) -> Tuple[int, int]:
|
||||
"""
|
||||
自动检测数据范围
|
||||
|
||||
Args:
|
||||
max_scan_rows: 最大扫描行数
|
||||
max_scan_cols: 最大扫描列数
|
||||
|
||||
Returns:
|
||||
(最大行号, 最大列号)
|
||||
"""
|
||||
max_row = 0
|
||||
max_col = 0
|
||||
|
||||
# 扫描前100行和前100列
|
||||
scan_rows = min(max_scan_rows, self.sheet.max_row)
|
||||
scan_cols = min(max_scan_cols, self.sheet.max_column)
|
||||
|
||||
for row in range(1, scan_rows + 1):
|
||||
for col in range(1, scan_cols + 1):
|
||||
cell_value = self.sheet.cell(row=row, column=col).value
|
||||
if cell_value is not None and str(cell_value).strip() != "":
|
||||
max_row = max(max_row, row)
|
||||
max_col = max(max_col, col)
|
||||
|
||||
return max_row, max_col
|
||||
|
||||
def convert(self,
|
||||
max_rows: Optional[int] = None,
|
||||
max_cols: Optional[int] = None,
|
||||
show_row_numbers: bool = False,
|
||||
show_col_numbers: bool = False,
|
||||
start_row: int = 1,
|
||||
start_col: int = 1) -> str:
|
||||
"""
|
||||
转换Excel为Markdown格式
|
||||
|
||||
Args:
|
||||
max_rows: 最大行数(None表示自动检测)
|
||||
max_cols: 最大列数(None表示自动检测)
|
||||
show_row_numbers: 是否显示行号
|
||||
show_col_numbers: 是否显示列号(字母形式)
|
||||
start_row: 起始行号(默认1)
|
||||
start_col: 起始列号(默认1)
|
||||
|
||||
Returns:
|
||||
Markdown格式的表格字符串
|
||||
"""
|
||||
# 自动检测数据范围
|
||||
if max_rows is None or max_cols is None:
|
||||
detected_rows, detected_cols = self.detect_data_range()
|
||||
if max_rows is None:
|
||||
max_rows = detected_rows
|
||||
if max_cols is None:
|
||||
max_cols = detected_cols
|
||||
|
||||
# 确保至少有1行1列
|
||||
if max_rows < 1 or max_cols < 1:
|
||||
return "| 无数据 |\n|--------|\n"
|
||||
|
||||
# 计算实际的结束位置
|
||||
end_row = min(start_row + max_rows - 1, self.sheet.max_row)
|
||||
end_col = min(start_col + max_cols - 1, self.sheet.max_column)
|
||||
|
||||
markdown_lines = []
|
||||
|
||||
# 构建列号行(如果需要)
|
||||
if show_col_numbers:
|
||||
header_parts = []
|
||||
if show_row_numbers:
|
||||
header_parts.append("") # 行号列的占位
|
||||
|
||||
for col in range(start_col, end_col + 1):
|
||||
col_letter = get_column_letter(col)
|
||||
header_parts.append(col_letter)
|
||||
|
||||
markdown_lines.append("| " + " | ".join(header_parts) + " |")
|
||||
|
||||
# 分隔线
|
||||
separator_parts = ["---"] * len(header_parts)
|
||||
markdown_lines.append("| " + " | ".join(separator_parts) + " |")
|
||||
|
||||
# 构建数据行
|
||||
for row in range(start_row, end_row + 1):
|
||||
row_parts = []
|
||||
|
||||
# 添加行号(如果需要)
|
||||
if show_row_numbers:
|
||||
row_parts.append(str(row))
|
||||
|
||||
# 添加单元格数据
|
||||
for col in range(start_col, end_col + 1):
|
||||
cell_value = self.sheet.cell(row=row, column=col).value
|
||||
|
||||
# 处理空值
|
||||
if cell_value is None:
|
||||
cell_str = ""
|
||||
else:
|
||||
cell_str = str(cell_value).strip()
|
||||
# 转义Markdown特殊字符
|
||||
cell_str = cell_str.replace("|", "\\|")
|
||||
cell_str = cell_str.replace("\n", "<br>")
|
||||
|
||||
row_parts.append(cell_str)
|
||||
|
||||
markdown_lines.append("| " + " | ".join(row_parts) + " |")
|
||||
|
||||
# 如果是第一行且没有显示列号,添加分隔线
|
||||
if row == start_row and not show_col_numbers:
|
||||
separator_parts = ["---"] * len(row_parts)
|
||||
markdown_lines.append("| " + " | ".join(separator_parts) + " |")
|
||||
|
||||
return "\n".join(markdown_lines)
|
||||
|
||||
def save_to_file(self, markdown_text: str, output_file: str):
|
||||
"""
|
||||
保存Markdown到文件
|
||||
|
||||
Args:
|
||||
markdown_text: Markdown文本
|
||||
output_file: 输出文件路径
|
||||
"""
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(markdown_text)
|
||||
|
||||
def close(self):
|
||||
"""关闭工作簿"""
|
||||
self.workbook.close()
|
||||
|
||||
|
||||
def main():
|
||||
"""命令行入口"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='将Excel文档转换为Markdown表格格式',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
# 自动检测范围并转换
|
||||
python excel_to_markdown.py input.xlsx -o output.md
|
||||
|
||||
# 指定转换前10行、前5列
|
||||
python excel_to_markdown.py input.xlsx -o output.md -r 10 -c 5
|
||||
|
||||
# 显示行号和列号
|
||||
python excel_to_markdown.py input.xlsx -o output.md --show-rows --show-cols
|
||||
|
||||
# 从第2行第2列开始,转换5行3列
|
||||
python excel_to_markdown.py input.xlsx -o output.md -r 5 -c 3 --start-row 2 --start-col 2
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('input_file', help='输入的Excel文件路径')
|
||||
parser.add_argument('-o', '--output', help='输出的Markdown文件路径(不指定则输出到控制台)')
|
||||
parser.add_argument('-r', '--max-rows', type=int, help='最大转换行数(不指定则自动检测)')
|
||||
parser.add_argument('-c', '--max-cols', type=int, help='最大转换列数(不指定则自动检测)')
|
||||
parser.add_argument('--show-rows', action='store_true', help='显示行号')
|
||||
parser.add_argument('--show-cols', action='store_true', help='显示列号(字母形式)')
|
||||
parser.add_argument('--start-row', type=int, default=1, help='起始行号(默认1)')
|
||||
parser.add_argument('--start-col', type=int, default=1, help='起始列号(默认1)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# 创建转换器
|
||||
converter = ExcelToMarkdown(args.input_file)
|
||||
|
||||
# 执行转换
|
||||
markdown_text = converter.convert(
|
||||
max_rows=args.max_rows,
|
||||
max_cols=args.max_cols,
|
||||
show_row_numbers=args.show_rows,
|
||||
show_col_numbers=args.show_cols,
|
||||
start_row=args.start_row,
|
||||
start_col=args.start_col
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
if args.output:
|
||||
converter.save_to_file(markdown_text, args.output)
|
||||
print(f"✓ 转换完成!已保存到: {args.output}")
|
||||
else:
|
||||
print(markdown_text)
|
||||
|
||||
# 关闭工作簿
|
||||
converter.close()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"✗ 错误: 找不到文件 '{args.input_file}'")
|
||||
except Exception as e:
|
||||
print(f"✗ 错误: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
121
skills/excel-to-markdown/SKILL.md
Normal file
121
skills/excel-to-markdown/SKILL.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: excel-to-markdown
|
||||
description: Convert Excel (.xlsx, .xls) files to Markdown table format. Use for converting Excel documents to Markdown for display or documentation, extracting and analyzing Excel data in a structured format, processing Excel files with automatic data range detection, generating Markdown tables with optional row/column numbering, or handling partial conversions with specific row/column ranges. Triggered by requests like "convert this Excel to Markdown", "export Excel as MD", "analyze this spreadsheet", or when working with .xlsx/.xls files that need to be viewed or processed as text.
|
||||
---
|
||||
|
||||
# Excel to Markdown Converter
|
||||
|
||||
## Overview
|
||||
|
||||
Convert Excel spreadsheets to Markdown table format with automatic data range detection and flexible output options. The converter handles complex multi-sheet workbooks, preserves cell formatting, and can display row/column numbers for easy reference.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Conversion
|
||||
|
||||
Convert an entire Excel file to Markdown:
|
||||
|
||||
```bash
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md
|
||||
```
|
||||
|
||||
### Convert with Row/Column Numbers
|
||||
|
||||
Display Excel row and column identifiers for reference:
|
||||
|
||||
```bash
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md --show-rows --show-cols
|
||||
```
|
||||
|
||||
### Specify Data Range
|
||||
|
||||
Convert only a specific portion of the spreadsheet:
|
||||
|
||||
```bash
|
||||
# Convert first 10 rows and 5 columns
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md -r 10 -c 5
|
||||
|
||||
# Start from row 2, column 3, convert 20 rows
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md --start-row 2 --start-col 3 -r 20
|
||||
```
|
||||
|
||||
## Command Line Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `input_file` | Path to the Excel file (required) |
|
||||
| `-o, --output` | Output Markdown file path (optional, prints to console if not specified) |
|
||||
| `-r, --max-rows` | Maximum number of rows to convert (auto-detect if not specified) |
|
||||
| `-c, --max-cols` | Maximum number of columns to convert (auto-detect if not specified) |
|
||||
| `--show-rows` | Display row numbers in the first column |
|
||||
| `--show-cols` | Display column letters (A, B, C...) in the first row |
|
||||
| `--start-row` | Starting row number (default: 1) |
|
||||
| `--start-col` | Starting column number (default: 1) |
|
||||
|
||||
## Features
|
||||
|
||||
### Automatic Data Range Detection
|
||||
|
||||
The converter automatically scans the spreadsheet to identify the actual data range (up to 100 rows and 100 columns by default), ignoring empty trailing rows and columns.
|
||||
|
||||
### Cell Content Handling
|
||||
|
||||
- Empty cells are rendered as blank
|
||||
- Pipe characters (`|`) in cell content are escaped as `\|`
|
||||
- Newlines within cells are converted to `<br>` tags
|
||||
- All cell values are trimmed of leading/trailing whitespace
|
||||
|
||||
### Multi-Sheet Support
|
||||
|
||||
By default, the converter processes the active (last selected) sheet in the workbook.
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### 1. Data Analysis
|
||||
|
||||
Extract Excel data for analysis or processing:
|
||||
|
||||
```bash
|
||||
python3 scripts/excel_to_markdown.py sales_data.xlsx -o sales_analysis.md
|
||||
```
|
||||
|
||||
Then analyze the Markdown output or provide insights based on the structured data.
|
||||
|
||||
### 2. Documentation
|
||||
|
||||
Convert spreadsheets to Markdown for inclusion in documentation:
|
||||
|
||||
```bash
|
||||
python3 scripts/excel_to_markdown.py specifications.xlsx -o specs.md --show-rows --show-cols
|
||||
```
|
||||
|
||||
The row/column numbers make it easy to reference specific cells in documentation.
|
||||
|
||||
### 3. Quick Preview
|
||||
|
||||
Quickly view Excel contents without opening a spreadsheet application:
|
||||
|
||||
```bash
|
||||
python3 scripts/excel_to_markdown.py report.xlsx
|
||||
```
|
||||
|
||||
### 4. Partial Data Extraction
|
||||
|
||||
Extract specific sections from large spreadsheets:
|
||||
|
||||
```bash
|
||||
# Get header row and first 10 data rows
|
||||
python3 scripts/excel_to_markdown.py large_file.xlsx -o sample.md -r 11
|
||||
```
|
||||
|
||||
## Script Location
|
||||
|
||||
The converter script is located at: `scripts/excel_to_markdown.py`
|
||||
|
||||
## Dependencies
|
||||
|
||||
The script requires `openpyxl` for Excel file processing:
|
||||
|
||||
```bash
|
||||
pip install openpyxl
|
||||
```
|
||||
220
skills/excel-to-markdown/scripts/excel_to_markdown.py
Normal file
220
skills/excel-to-markdown/scripts/excel_to_markdown.py
Normal file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Excel to Markdown Converter
|
||||
将Excel文档转换为Markdown表格格式
|
||||
"""
|
||||
|
||||
import openpyxl
|
||||
from openpyxl.utils import get_column_letter
|
||||
from typing import Optional, Tuple
|
||||
import argparse
|
||||
|
||||
|
||||
class ExcelToMarkdown:
|
||||
"""Excel转Markdown转换器"""
|
||||
|
||||
def __init__(self, excel_file: str):
|
||||
"""
|
||||
初始化转换器
|
||||
|
||||
Args:
|
||||
excel_file: Excel文件路径
|
||||
"""
|
||||
self.workbook = openpyxl.load_workbook(excel_file, data_only=True)
|
||||
self.sheet = self.workbook.active
|
||||
|
||||
def detect_data_range(self, max_scan_rows: int = 100, max_scan_cols: int = 100) -> Tuple[int, int]:
|
||||
"""
|
||||
自动检测数据范围
|
||||
|
||||
Args:
|
||||
max_scan_rows: 最大扫描行数
|
||||
max_scan_cols: 最大扫描列数
|
||||
|
||||
Returns:
|
||||
(最大行号, 最大列号)
|
||||
"""
|
||||
max_row = 0
|
||||
max_col = 0
|
||||
|
||||
# 扫描前100行和前100列
|
||||
scan_rows = min(max_scan_rows, self.sheet.max_row)
|
||||
scan_cols = min(max_scan_cols, self.sheet.max_column)
|
||||
|
||||
for row in range(1, scan_rows + 1):
|
||||
for col in range(1, scan_cols + 1):
|
||||
cell_value = self.sheet.cell(row=row, column=col).value
|
||||
if cell_value is not None and str(cell_value).strip() != "":
|
||||
max_row = max(max_row, row)
|
||||
max_col = max(max_col, col)
|
||||
|
||||
return max_row, max_col
|
||||
|
||||
def convert(self,
|
||||
max_rows: Optional[int] = None,
|
||||
max_cols: Optional[int] = None,
|
||||
show_row_numbers: bool = False,
|
||||
show_col_numbers: bool = False,
|
||||
start_row: int = 1,
|
||||
start_col: int = 1) -> str:
|
||||
"""
|
||||
转换Excel为Markdown格式
|
||||
|
||||
Args:
|
||||
max_rows: 最大行数(None表示自动检测)
|
||||
max_cols: 最大列数(None表示自动检测)
|
||||
show_row_numbers: 是否显示行号
|
||||
show_col_numbers: 是否显示列号(字母形式)
|
||||
start_row: 起始行号(默认1)
|
||||
start_col: 起始列号(默认1)
|
||||
|
||||
Returns:
|
||||
Markdown格式的表格字符串
|
||||
"""
|
||||
# 自动检测数据范围
|
||||
if max_rows is None or max_cols is None:
|
||||
detected_rows, detected_cols = self.detect_data_range()
|
||||
if max_rows is None:
|
||||
max_rows = detected_rows
|
||||
if max_cols is None:
|
||||
max_cols = detected_cols
|
||||
|
||||
# 确保至少有1行1列
|
||||
if max_rows < 1 or max_cols < 1:
|
||||
return "| 无数据 |\n|--------|\n"
|
||||
|
||||
# 计算实际的结束位置
|
||||
end_row = min(start_row + max_rows - 1, self.sheet.max_row)
|
||||
end_col = min(start_col + max_cols - 1, self.sheet.max_column)
|
||||
|
||||
markdown_lines = []
|
||||
|
||||
# 构建列号行(如果需要)
|
||||
if show_col_numbers:
|
||||
header_parts = []
|
||||
if show_row_numbers:
|
||||
header_parts.append("") # 行号列的占位
|
||||
|
||||
for col in range(start_col, end_col + 1):
|
||||
col_letter = get_column_letter(col)
|
||||
header_parts.append(col_letter)
|
||||
|
||||
markdown_lines.append("| " + " | ".join(header_parts) + " |")
|
||||
|
||||
# 分隔线
|
||||
separator_parts = ["---"] * len(header_parts)
|
||||
markdown_lines.append("| " + " | ".join(separator_parts) + " |")
|
||||
|
||||
# 构建数据行
|
||||
for row in range(start_row, end_row + 1):
|
||||
row_parts = []
|
||||
|
||||
# 添加行号(如果需要)
|
||||
if show_row_numbers:
|
||||
row_parts.append(str(row))
|
||||
|
||||
# 添加单元格数据
|
||||
for col in range(start_col, end_col + 1):
|
||||
cell_value = self.sheet.cell(row=row, column=col).value
|
||||
|
||||
# 处理空值
|
||||
if cell_value is None:
|
||||
cell_str = ""
|
||||
else:
|
||||
cell_str = str(cell_value).strip()
|
||||
# 转义Markdown特殊字符
|
||||
cell_str = cell_str.replace("|", "\\|")
|
||||
cell_str = cell_str.replace("\n", "<br>")
|
||||
|
||||
row_parts.append(cell_str)
|
||||
|
||||
markdown_lines.append("| " + " | ".join(row_parts) + " |")
|
||||
|
||||
# 如果是第一行且没有显示列号,添加分隔线
|
||||
if row == start_row and not show_col_numbers:
|
||||
separator_parts = ["---"] * len(row_parts)
|
||||
markdown_lines.append("| " + " | ".join(separator_parts) + " |")
|
||||
|
||||
return "\n".join(markdown_lines)
|
||||
|
||||
def save_to_file(self, markdown_text: str, output_file: str):
|
||||
"""
|
||||
保存Markdown到文件
|
||||
|
||||
Args:
|
||||
markdown_text: Markdown文本
|
||||
output_file: 输出文件路径
|
||||
"""
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(markdown_text)
|
||||
|
||||
def close(self):
|
||||
"""关闭工作簿"""
|
||||
self.workbook.close()
|
||||
|
||||
|
||||
def main():
|
||||
"""命令行入口"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='将Excel文档转换为Markdown表格格式',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
# 自动检测范围并转换
|
||||
python excel_to_markdown.py input.xlsx -o output.md
|
||||
|
||||
# 指定转换前10行、前5列
|
||||
python excel_to_markdown.py input.xlsx -o output.md -r 10 -c 5
|
||||
|
||||
# 显示行号和列号
|
||||
python excel_to_markdown.py input.xlsx -o output.md --show-rows --show-cols
|
||||
|
||||
# 从第2行第2列开始,转换5行3列
|
||||
python excel_to_markdown.py input.xlsx -o output.md -r 5 -c 3 --start-row 2 --start-col 2
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('input_file', help='输入的Excel文件路径')
|
||||
parser.add_argument('-o', '--output', help='输出的Markdown文件路径(不指定则输出到控制台)')
|
||||
parser.add_argument('-r', '--max-rows', type=int, help='最大转换行数(不指定则自动检测)')
|
||||
parser.add_argument('-c', '--max-cols', type=int, help='最大转换列数(不指定则自动检测)')
|
||||
parser.add_argument('--show-rows', action='store_true', help='显示行号')
|
||||
parser.add_argument('--show-cols', action='store_true', help='显示列号(字母形式)')
|
||||
parser.add_argument('--start-row', type=int, default=1, help='起始行号(默认1)')
|
||||
parser.add_argument('--start-col', type=int, default=1, help='起始列号(默认1)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# 创建转换器
|
||||
converter = ExcelToMarkdown(args.input_file)
|
||||
|
||||
# 执行转换
|
||||
markdown_text = converter.convert(
|
||||
max_rows=args.max_rows,
|
||||
max_cols=args.max_cols,
|
||||
show_row_numbers=args.show_rows,
|
||||
show_col_numbers=args.show_cols,
|
||||
start_row=args.start_row,
|
||||
start_col=args.start_col
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
if args.output:
|
||||
converter.save_to_file(markdown_text, args.output)
|
||||
print(f"✓ 转换完成!已保存到: {args.output}")
|
||||
else:
|
||||
print(markdown_text)
|
||||
|
||||
# 关闭工作簿
|
||||
converter.close()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"✗ 错误: 找不到文件 '{args.input_file}'")
|
||||
except Exception as e:
|
||||
print(f"✗ 错误: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user