<p align="center">
  <img src="icons/icon128.png" width="76" alt="PromptMill">
</p>

<h1 align="center">PromptMill</h1>

<p align="center">
  <b>Sinh ảnh hàng loạt trên Google Flow.</b><br>
  Dán danh sách prompt, bấm Bắt đầu, đi làm việc khác. Ảnh tự về thư mục Downloads.
</p>

<p align="center">
  <img alt="Chrome MV3" src="https://img.shields.io/badge/Chrome-Manifest%20V3-4285F4?logo=googlechrome&logoColor=white">
  <img alt="License MIT" src="https://img.shields.io/badge/license-MIT-22c55e">
  <img alt="Không cần tài khoản" src="https://img.shields.io/badge/t%C3%A0i%20kho%E1%BA%A3n-kh%C3%B4ng%20c%E1%BA%A7n-22c55e">
  <a href="PRIVACY.vi.md"><img alt="Không thu dữ liệu" src="https://img.shields.io/badge/thu%20d%E1%BB%AF%20li%E1%BB%87u-kh%C3%B4ng-22c55e"></a>
  <img alt="Không cần build" src="https://img.shields.io/badge/build-kh%C3%B4ng%20c%E1%BA%A7n-8b5cf6">
  <a href="https://www.paypal.com/qrcodes/p2pqrc/7KVTHR9K7H2AU"><img alt="Mời cà phê" src="https://img.shields.io/badge/m%E1%BB%9Di%20c%C3%A0%20ph%C3%AA-PayPal-00457C?logo=paypal&logoColor=white"></a>
</p>

<p align="center">
  <a href="README.md">English</a> · <b>Tiếng Việt</b>
</p>

---

<p align="center">
  <img src="docs/screenshot-running.jpg" alt="PromptMill đang chạy hàng đợi 105 prompt trên Google Flow" width="100%">
  <br><em>Hàng đợi 105 prompt đang chạy dở. Prompt đã xong sẽ được bỏ qua ở lần chạy sau.</em>
</p>

---

## Nó làm gì

Bạn có 200 prompt và một tab Google Flow. Làm tay nghĩa là 200 lần dán → Enter → chờ → chuột phải →
Lưu ảnh. PromptMill làm hộ bạn cái vòng lặp đó.

1. **Dán danh sách prompt** — mỗi dòng một cái — hoặc kéo thả file `.txt` vào panel.
2. **Bấm Bắt đầu.** Tool gõ từng prompt vào Flow, gửi, chờ ảnh xong, rồi tải hết về
   `Downloads/<thư mục của bạn>/`.
3. **Gập máy đi ngủ.** Giữa các prompt nó nghỉ theo nhịp giống người thật, để cả phiên chạy không
   giống một con script đang nện vào trang.

Prompt nào hỏng thì có nút **Tạo lại**. Prompt nào đã xong thì lần chạy sau tự bỏ qua — không bao
giờ trả tiền hai lần cho cùng một tấm ảnh.

---

## Vì sao chọn cái này

Extension tự động hoá Google Flow thì nhiều. Phần lớn đua nhau đếm tính năng — chạy song song,
video, 4K. PromptMill cố tình đi ngược lại, tối ưu đúng cái mà đánh giá của những tool kia than
phiền nhiều nhất: **chạy là phải chạy, và bạn phải biết nó đang làm gì.**

| | |
|---|---|
| **Không có gì rời khỏi máy bạn** | Không tài khoản, không server, không analytics, không cấu hình từ xa. Extension chỉ nói chuyện với đúng hai host: `labs.google` và `flow.google.com`. Đọc [`background.js`](background.js) và [`sidepanel.js`](sidepanel.js) là thấy toàn bộ bề mặt mạng, và [chính sách quyền riêng tư](PRIVACY.vi.md) chỉ luôn câu lệnh một dòng để bạn tự kiểm. |
| **Nó nói khi nó không chắc** | Trước khi nhấn Enter, nó đọc ngược ô prompt ra so với chữ nó định gõ. Lệch thì dừng ngay và cho bạn xem ô đang chứa gì thật, thay vì ngồi chờ bốn phút đợi ảnh không bao giờ đến. |
| **Nó đọc được thông báo lỗi của Flow** | Hết quota, vi phạm chính sách, dịch vụ quá tải — tool bắt được thông báo trên trang, dừng hàng đợi, và hiện nguyên văn cho bạn. |
| **Nó sống sót qua chính cái chết của mình** | Chrome giết service worker MV3 sau ~30 giây rỗi, mà một tấm ảnh có thể mất bốn phút. PromptMill nhận ra việc đó và nhận lại phiên debugger cũ thay vì chết theo. |
| **Không cần build** | JS, HTML, CSS thuần. Clone, nạp, sửa. Không npm, không bundler, không transpiler. |

### Và những gì nó cố tình KHÔNG làm

Nói thẳng ngay từ đầu, vì bên khác thường không nói:

- **Chỉ ảnh.** Không sinh video Veo.
- **Mỗi lần một prompt.** Không chạy song song.
- **Không chọn độ phân giải.** Flow trả gì thì tải nấy.
- **Một tab Flow.** Mở nhiều tab thì nó lấy tab đầu tiên tìm được.

Cần video hoặc chạy song song thì dùng extension khác — thật lòng, mảng đó họ làm tốt hơn. Quay lại
đây nếu hoá ra thứ bạn cần là độ tin cậy.

---

## Cài đặt (khoảng 30 giây)

Không cần tài khoản nhà phát triển, không mất $5.

1. Tải repo này về và giải nén.
2. Mở `chrome://extensions`.
3. Bật **Chế độ nhà phát triển** (góc trên bên phải).
4. Bấm **Tải tiện ích đã giải nén** → chọn thư mục `PromptMill`.
5. Icon PromptMill hiện trên thanh công cụ. Xong.

> Mỗi lần sửa code, bấm nút tải lại (↻) trên thẻ extension **và F5 lại tab Flow**. Thiếu bước F5 thì
> bản `content.js` cũ vẫn đang sống trong tab.

---

## Cách dùng

1. Mở một project trên **Google Flow** (`labs.google/fx/tools/flow` hoặc `flow.google.com/project/…`).
2. Bấm icon PromptMill để mở side panel.
3. Đợi dòng trạng thái chuyển xanh.
4. Nạp prompt — **mỗi dòng một prompt** — bằng cách dán, **kéo thả file văn bản** vào panel, hoặc
   bấm *Tải file .txt*.
5. Chọn thư mục lưu, bật/tắt đánh số, đặt thời gian chờ tối đa mỗi ảnh.
6. Bấm **Bắt đầu**.
7. **Tạm dừng** giữ hàng đợi lại sau khi ảnh đang chạy xong — bấm lần nữa để chạy tiếp.
   **Dừng** thì kết thúc hẳn.
8. **Kéo các dòng trong hàng đợi** để đổi thứ tự (khi đang dừng).

Hai cách bắt đầu lại, mỗi nút đặt cạnh đúng thứ nó tác động:

- **Xoá tất cả** — cạnh nút tải file, dọn sạch danh sách prompt.
- **Đặt lại** — trên đầu hàng đợi, giữ nguyên prompt và đưa tất cả về *Chờ* để chạy lại cả loạt.
  Không có nút này thì không còn đường nào chạy lại một danh sách đã xong, vì sửa ô prompt bây giờ
  đã giữ nguyên trạng thái từng dòng.

Cả hai đều cần **bấm hai lần**: lần đầu nút đổi thành *Chắc chưa?*, lần sau mới chạy. Tự huỷ sau 4
giây. Không dùng hộp thoại xác nhận — một nút làm mất danh sách, nút kia đốt quota, mà hộp thoại
chặn trong side panel còn khó chịu hơn là một cái nút hỏi lại.

**Kéo thả khác nút bấm:** kéo thả thì **THÊM VÀO** danh sách, nút *Tải file* thì **THAY** cả danh
sách. Thả nhầm không mất gì; bấm nút thì mất.

<p align="center">
  <img src="docs/screenshot-drop.jpg" alt="Thả file văn bản vào side panel" width="100%">
</p>

Nhận `.txt`, `.md`, `.csv`, `.log`, `.json`, hoặc file có MIME `text/*`, tối đa 2 MB mỗi file.
File Windows xuống dòng `CRLF` và file có BOM đều đọc được, không cần sửa gì.

JSON đọc được cả ba dạng hay gặp ngoài đời: `["a","b"]`, `[{"prompt":"a"}]`, và `{"prompts":[…]}`
(`{"items":[…]}` cũng được). Prompt có xuống dòng sẽ được ép về một dòng, vì mô hình ở đây là mỗi
dòng một prompt.

Ảnh lưu vào `Downloads / <thư mục của bạn> / 001_prompt-cua-ban.jpg`. Chrome tự tạo thư mục con,
bạn không phải chuẩn bị gì.

Bề rộng số thứ tự chạy theo độ dài danh sách nên tên file **luôn sắp đúng thứ tự**: 210 prompt cho
`001` … `210`, còn 1.500 prompt cho `0001` … `1500`. Tắt đánh số thì chỉ còn chữ trong prompt. Một
prompt ra nhiều ảnh thì tải hết, đánh hậu tố `_1`, `_2`.

Đuôi file đặt theo **định dạng thật** của ảnh chứ không tin vào URL — ảnh JPEG vẫn ra `.jpg` dù link
CDN của Google không có đuôi nào.

---

## Nó chạy bằng cách nào

Nên hiểu chỗ này, vì nó giải thích cái thứ trông đáng sợ nhất.

Ô prompt của Flow là editor [Slate.js](https://docs.slatejs.org/), mà Slate bỏ qua sự kiện bàn phím
giả — mấy trò `dispatchEvent` quen thuộc im lặng không ăn gì cả. Nên PromptMill gõ qua **Chrome
DevTools Protocol** bằng API `chrome.debugger`. Phím thật ở tầng trình duyệt, không phân biệt được
với ngón tay bạn.

Hai hệ quả bạn cần biết:

1. **Phải ĐÓNG DevTools (F12) trên tab Flow.** Mỗi lúc chỉ một client debugger được gắn, và DevTools
   thắng.
2. **Chrome hiện thanh vàng "PromptMill đang gỡ lỗi trình duyệt này" khi chạy.** Thanh đó là Chrome
   đang nói thật với bạn. Để yên — bấm Hủy là cắt kết nối giữa chừng.

Đây cũng là lý do khâu gửi prompt lại là phần **bền nhất** chứ không phải mong manh nhất: nó không
đi tìm nút Gửi trong DOM, nên Google có đổi nút đó cũng không ảnh hưởng gì.

### Khoảng nghỉ giữa các prompt

Không phải con số cố định, và cố tình không phải random đều. Khoảng cách giữa hai thao tác của người
thật lệch phải — phần lớn ngắn, thỉnh thoảng rất dài — còn random đều rải phẳng đều giữa hai cận,
mà *chính cái phẳng đó mới là dấu vân tay của máy*. PromptMill bốc theo phân phối log-normal, và
bốc lại khi rơi ngoài dải chứ không kẹp về biên (kẹp thì ~4% số lần dồn vào đúng một con số — đúng
thứ nó sinh ra để tránh).

Mặc định: trung vị 6 giây, một nửa số lần rơi trong 4,9–7,6 giây, 5% số lần nghỉ dài 45–100 giây.
Trung bình khoảng 10 giây mỗi prompt — chạy 100 prompt thì riêng phần nghỉ ngốn chừng 17 phút.

Chỉnh ở khối `DELAY` đầu [`sidepanel.js`](sidepanel.js).

<p align="center">
  <img src="docs/screenshot-progress.jpg" alt="Hàng đợi đang đếm ngược giữa hai prompt" width="100%">
  <br><em>Đồng hồ đếm ngược giữa hai prompt, và trạng thái từng prompt trong hàng đợi.</em>
</p>

---

## Lỗi đã biết

Lỗi thật, liệt kê ở đây thay vì để bạn tự phát hiện lúc 2 giờ sáng.

| Lỗi | Bạn sẽ thấy | Cách sống chung |
|---|---|---|
| **Danh sách rất lớn thì chậm** | Trên ~2.000 prompt, mỗi ký tự gõ vào ô prompt tốn hơn 100 ms, vì hàng đợi bị vẽ lại ở mỗi sự kiện input | Chia thành từng mẻ vài trăm |
| **"Xong" nghĩa là "đã bắt đầu tải"** | Tool chưa chờ file ghi xong, nên link CDN hết hạn vẫn có thể hiện là thành công | Đối chiếu số file trong Downloads với hàng đợi |

---

## Sửa khi Google đổi giao diện Flow

PromptMill điều khiển giao diện Flow chứ không dùng API, nên Google cập nhật là có thể gãy. Nó chạm
vào Flow ở **3 điểm** — mọi thứ còn lại nằm ở tầng trình duyệt nên miễn nhiễm.

| # | Việc | Code | Gãy thì thấy gì |
|---|---|---|---|
| 1 | Nhận ra tab Flow | `getFlowTab()` trong `sidepanel.js` | Ô trạng thái đỏ mãi: *"Hãy mở một project Google Flow"* |
| 2 | Tìm ô nhập prompt | `findPromptInput()` / `wakePromptBox()` trong `content.js` | *"Không tìm thấy ô prompt"* |
| 3 | Biết khi nào ảnh xong | `getCompletedImages()` / `waitForNewImages()` trong `content.js` | Chờ rất lâu rồi báo **Quá giờ** |

### 1. Không nhận ra tab Flow

Google đổi đường dẫn. Sửa **3 chỗ, thiếu chỗ nào cũng không chạy**:

```js
// sidepanel.js → getFlowTab()
if (url.hostname === "flow.google.com") {
  return /^\/project(?:\/|$)/.test(url.pathname);   // ← sửa ở đây
}
```

…cộng với `host_permissions` và `content_scripts[0].matches` trong `manifest.json`. Mẹo: copy nguyên
URL trên thanh địa chỉ ra rồi so với 3 chỗ đó.

### 2. "Không tìm thấy ô prompt"

Mặc định tool tự dò theo một danh sách selector (`[data-slate-editor="true"]`, `[role="textbox"]`,
`textarea`…). Tự dò trượt thì chỉ định tay trong khối `CONFIG` đầu `content.js`:

```js
promptSelector: '[data-slate-editor="true"]',
```

Mở Flow, bấm F12, dùng mũi tên chọn phần tử click vào ô prompt, rồi tìm một thuộc tính **ổn định** —
kiểu `data-slate-editor`, `role`, `aria-label`.

> ⚠️ Đừng dùng **Copy → Copy selector** của DevTools. Nó cho chuỗi kiểu
> `#root > div:nth-child(3) > div > div:nth-child(2)`, gãy ngay lần Google đổi layout kế tiếp.

Nếu ô prompt chỉ mọc ra sau khi click thì `wakePromptBox()` lo việc đó: nó tìm nút có icon
`arrow_forward` rồi click vào vùng bên trái nút. Google đổi tên icon này thì sửa chữ `arrow_forward`
trong `content.js`.

### 3. "Chữ không vào được ô prompt"

Tool lấy toạ độ tâm ô prompt rồi nhờ debugger click đúng điểm đó. Điểm đó bị che thì click trượt
sang chỗ khác và chữ rơi vào hư không.

Trước khi nhấn Enter, tool đọc lại ô prompt để đối chiếu. Lệch là dừng ngay và báo kèm nội dung nó
thật sự thấy — chứ không nhấn Enter rồi ngồi chờ hết 4 phút. Gặp lỗi này thì:

- Đóng hết dialog/popup đang mở trên trang Flow.
- Cuộn trang sao cho ô prompt hiện rõ, đừng để bị khuất.
- Đừng thu nhỏ cửa sổ Chrome hay kéo side panel rộng đến mức che mất ô prompt.
- Đừng đụng chuột vào tab Flow trong lúc chạy.

Hàng đợi dừng hẳn khi gặp lỗi này, vì click trượt thì prompt nào cũng trượt như nhau — chạy tiếp chỉ
tổ đốt thời gian.

### 4. Chờ mãi rồi báo "Quá giờ"

Chỉnh khối `CONFIG` ở đầu `content.js`:

| Tham số | Mặc định | Khi nào chỉnh |
|---|---|---|
| `maxWaitMs` | `240000` (4 phút) | Model chậm hoặc Flow đang tắc → tăng lên |
| `minImageSize` | `256` | Ảnh kết quả nhỏ hơn ngưỡng này bị bỏ qua như icon → giảm xuống |
| `settleMs` | `1800` | Ảnh tải về bị vỡ/thiếu → tăng, cho ảnh load đủ trước khi tải |
| `pollMs` | `2000` | Nhịp kiểm tra ảnh mới. Hiếm khi cần đụng |
| `maxSettleRounds` | `6` | Flow trả ảnh lệch nhau lâu → tăng lên |

Máy chưa từng chạy được lần nào thì kiểm tra trước hai thứ này, chúng gây lỗi nhiều hơn cả selector:
**DevTools (F12) phải đóng** trên tab Flow, và **thanh vàng "đang gỡ lỗi" phải để yên**.

### 5. "Google Flow báo lỗi: …"

Tool đọc được thông báo lỗi mà Flow hiện lên (hết quota, vi phạm chính sách nội dung, dịch vụ quá
tải…) và **dừng hàng đợi ngay**, kèm nguyên văn thông báo. Không có tính năng này thì mỗi prompt
hỏng vẫn ngốn đủ 4 phút chờ.

Dừng cả hàng đợi chứ không chạy tiếp, vì nguyên nhân hay gặp nhất là hết quota — mà hết quota thì
prompt nào cũng hỏng như nhau.

Bị báo nhầm là hiếm (chỉ xét phần tử `role="alert"` **mới hiện ra** và có chữ khớp danh sách từ
khoá), nhưng nếu gặp thì tắt trong `content.js`:

```js
detectPageError: false,
```

…hoặc giữ bật và sửa danh sách từ khoá ở `errorHint` ngay dưới đó.

### 6. Muốn chạy nhanh hơn hoặc chậm hơn

Khoảng nghỉ do tool tự sinh, không có ô nhập. Chỉnh ở khối `DELAY` đầu `sidepanel.js`:

| Tham số | Mặc định | Ý nghĩa |
|---|---|---|
| `medianMs` | `5000` | Một nửa số lần nghỉ ngắn hơn con số này. Nút chỉnh chính |
| `spread` | `0.45` | Độ tản. Tăng → các lần chênh nhau nhiều hơn |
| `minMs` / `maxMs` | `4000` / `40000` | Chặn hai đầu |
| `longBreakChance` | `0.05` | Tỉ lệ nghỉ dài |
| `longBreakMs` | `45000`–`100000` | Dải của một lần nghỉ dài |

> ⚠️ **`medianMs` và `minMs` giẫm chân nhau.** Hai con số càng gần nhau thì đuôi dưới của phân phối
> bị cắt càng nhiều, và `medianMs` thôi không còn đúng nghĩa "trung vị". Ở mặc định hiện tại
> (`5000` với sàn `4000`), **31% số lần bốc bị từ chối** nên trung vị thực tế đội lên **6,0 giây**
> chứ không phải 5,0. Muốn `medianMs` đúng như tên gọi thì để `minMs` thấp hơn nó ít nhất khoảng
> 2,5 lần — ví dụ `minMs: 2000` cho trung vị 5,1 giây thật.

### 7. Hai tham số không còn tác dụng

`generateSelector` và `submitWithEnter` vẫn nằm trong `CONFIG` nhưng là **di sản của bản cũ, sửa vào
không có tác dụng gì**. Từ khi chuyển sang `chrome.debugger`, đường code đọc chúng không còn được
gọi tới nữa. (Kèm theo chúng, khoảng 230 dòng trong `content.js` là code chết — xem
[Đóng góp](#đóng-góp) nếu bạn muốn dọn.)

> **Mẹo gỡ lỗi chung:** mở Console trên tab Flow *trước khi* chạy, lọc theo `[PromptMill]`. Log cho
> biết nó tìm thấy ô prompt nào, chờ bao lâu, thấy ảnh mới lúc nào. Nhớ **đóng DevTools trước khi
> bấm Bắt đầu** — mở DevTools là chiếm mất kênh debugger.

---

## Giới hạn đã biết

- **Ảnh vẫn có thể bị gán nhầm prompt, dù hiếm.** Tool phân biệt ảnh mới với ảnh cũ bằng cách chụp
  trước một danh sách "ảnh nền". Flow lại trả nhiều ảnh cho một lần tạo, đến lệch nhau vài giây —
  ảnh đến muộn mà lọt qua thời điểm chụp thì bị tính là ảnh của prompt kế, tải về nhưng đặt tên theo
  prompt sai. Đã bịt phần lớn (chỉ chụp sau khi danh sách ảnh ngừng thay đổi, và tải hết ảnh của mỗi
  prompt thay vì chỉ lấy ảnh đầu), nhưng vẫn còn khe hẹp ~1 giây. Gặp thì tăng `settleMs` lên
  `3000`–`4000` và chạy trên project ít ảnh cũ.
- **Ảnh `blob:`.** Phần lớn ảnh Flow là link `https` tải thẳng được. Nếu ảnh ở dạng `blob:` mà bị
  chặn CORS thì tải có thể lỗi.

---

## Đổi thương hiệu

- Tên và mô tả → `manifest.json` (`name`, `description`).
- Tiêu đề → `sidepanel.html`.
- Icon → ba file trong `icons/`.
- **Màu → chỉ sửa khối `:root` đầu `sidepanel.css`.** Không còn màu cứng nào nằm rải rác bên dưới.

Giao diện là **neumorphism nền tối**. Hình khối sinh ra hoàn toàn từ hai lớp bóng đối nhau — sáng ở
trên-trái (`--hi`), tối ở dưới-phải (`--lo`) — không có đường viền nào. Nổi = bấm được, lõm = nhập
được, phẳng = bị khoá.

Ba luật bạn sẽ phá nếu không ai nói trước:

> ⚠️ **`--bg` bắt buộc là màu trung gian, không được đen kịt cũng không được trắng.** Nó cần chỗ cho
> bóng ở **cả hai** phía. Với nền tối hiện tại, `--hi` **sáng hơn** `--bg` — ngược hẳn với nền sáng.

> ⚠️ **Đừng cho `body` một cái gradient.** Cả style này dựa trên ảo giác mọi vật cùng nằm trên một
> mặt phẳng đều màu.

> ⚠️ **Cặp màu đảo theo nền.** Nền tối thì mỗi trạng thái là nền **tối** cùng tông + chữ `-ink`
> **sáng**. Bê nguyên cặp của bản nền sáng sang là chữ chìm nghỉm.

Toàn bộ 54 chỗ có chữ ở bảng mặc định đạt WCAG AA (≥ 4,5:1); đổi bảng màu xong nhớ kiểm lại độ
tương phản.

---

## Đóng góp

Hoan nghênh issue và PR. Vài chỗ dễ bắt đầu:

- **Đa ngôn ngữ (i18n)** — rút chuỗi tiếng Việt ra `_locales/`. Thay đổi có tác động lớn nhất repo
  này.
- **Dọn code chết** — khoảng 230 dòng trong `content.js` không bao giờ được gọi (xem mục 7).
- **Chốt chặn số prompt** — xem mục lỗi "danh sách rất lớn".
- **Sinh video Veo**, **chạy song song**, hoặc **chọn độ phân giải** — ba thứ tool khác có mà cái
  này chưa. Cả ba đều cần người có tài khoản Flow thật để dò DOM tương ứng.

Chưa có build step, chưa có test, chưa có CI. Thêm `eslint` là một cải thiện thật sự.

---

## Câu hỏi hay gặp

**Có trái Điều khoản của Google không?** Tự động hoá giao diện web có thể trái điều khoản của
Google. Dùng vừa phải, để delay hợp lý. Đó là quyết định của bạn, không phải của README này.

**Đưa lên Chrome Web Store được không?** Được, có lưu ý. Quyền `debugger` nằm trong nhóm nhạy cảm
của Chrome, nên **bắt buộc bị review thủ công** và bị yêu cầu giải trình — chứ không phải từ chối
tự động. Nhiều extension cùng loại đang sống trên store. Hãy tính trước là review lâu hơn, và chuẩn
bị sẵn lời giải thích vì sao Slate.js buộc phải dùng CDP.

**Dữ liệu đi đâu?** Không đi đâu cả. Prompt nằm trong `chrome.storage.local` trên máy bạn, ảnh đi
thẳng vào thư mục Downloads. [Chính sách quyền riêng tư](PRIVACY.vi.md) liệt kê đầy đủ từng mục được
lưu, từng lệnh DevTools Protocol extension phát ra, và cách bạn tự kiểm chứng tất cả.

**Chọn ổ đĩa khác để lưu ảnh được không?** Không — extension Chrome chỉ ghi được vào thư mục tải
xuống của trình duyệt. Đặt thư mục đó ở `chrome://settings/downloads`.

---

## Ủng hộ dự án

PromptMill miễn phí, không quảng cáo, không tài khoản, không thu thập gì — và sẽ giữ nguyên như vậy.
Nếu nó tiết kiệm cho bạn cả buổi chiều ngồi bấm Lưu ảnh, mời tôi ly cà phê:

<p align="center">
  <a href="https://www.paypal.com/qrcodes/p2pqrc/7KVTHR9K7H2AU">
    <img alt="Mời cà phê qua PayPal" src="https://img.shields.io/badge/%E2%98%95%20M%E1%BB%9Di%20t%C3%B4i%20ly%20c%C3%A0%20ph%C3%AA-PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white">
  </a>
</p>

Hoàn toàn tuỳ tâm. Báo lỗi hay gửi pull request cũng giúp được y như vậy.

---

## Giấy phép

[MIT](LICENSE) © Bui Van Thuong. Muốn làm gì thì làm — fork, đổi thương hiệu, phát cho cộng đồng của
bạn.
