├── README.md └── appscrip_Google_sheet /README.md: -------------------------------------------------------------------------------- 1 | # Hướng dẫn tạo bot Telegram thông báo biển số xe hàng ngày 2 | 3 | Hướng dẫn chi tiết từng bước để tạo một bot Telegram đơn giản, giúp bạn nhận thông báo về các biển số xe được theo dõi hàng ngày. 4 | 5 | ## Bước 1: Tạo bot Telegram thông qua BotFather (@BotFather) 6 | 7 | 1. Mở ứng dụng Telegram và tìm kiếm bot có tên **@BotFather**. 8 | 2. Nhấn **Start** để bắt đầu trò chuyện với BotFather. 9 | 3. Gửi lệnh `/newbot` để tạo một bot mới. 10 | 4. BotFather sẽ yêu cầu bạn đặt tên cho bot của mình. Hãy nhập tên bạn muốn (ví dụ: "Thông báo Biển Số Xe"). 11 | 5. Tiếp theo, bạn cần chọn một username cho bot. Username phải kết thúc bằng từ "bot" hoặc "Bot" (ví dụ: "ThongBaoBienSoXeBot"). 12 | 6. Sau khi tạo thành công, BotFather sẽ cung cấp cho bạn **API token** của bot. Hãy lưu lại token này cẩn thận, bạn sẽ cần nó ở các bước sau. 13 | 7. Mở Bot vừa tạo, nhấn vào nút Start hoặc Bắt đầu 14 | 15 | ![Tạo bot Telegram bằng BotFather](https://github.com/user-attachments/assets/00aa47d0-92ed-4622-a782-eb6353d2baf1) 16 | 17 | ## Bước 2: Lấy ID người dùng của bạn thông qua bot Get My ID (@getmyid_bot) 18 | 19 | 1. Tìm kiếm bot có tên **@getmyid_bot** trên Telegram. 20 | 2. Nhấn **Start** để bắt đầu trò chuyện. 21 | 3. Bot sẽ ngay lập tức gửi cho bạn **User ID** của bạn. Hãy ghi nhớ ID này. 22 | 23 | ![Lấy User ID từ Get My ID bot](https://github.com/user-attachments/assets/151d0bec-393c-4217-aaff-afcb61ddc295) 24 | 25 | ## Bước 3: Tạo Google Sheet và App Script 26 | 27 | 1. Truy cập [sheet.new](https://sheet.new) để tạo một Google Sheet mới. 28 | 2. Trong trang tính mới, vào menu **Tiện ích mở rộng** (Extensions) > **Apps Script**. 29 | 30 | ![Tạo Google Sheet và mở App Script](https://github.com/user-attachments/assets/4a089a26-4a64-44fc-a86c-84a69ca99b30) 31 | 32 | ## Bước 4: Sao chép và dán code App Script 33 | 34 | 1. Mở file App Script vừa tạo. 35 | 2. Sao chép toàn bộ nội dung code từ [LINK](https://github.com/dkhaithanh/bottraphatnguoi/blob/main/appscrip_Google_sheet). 36 | 3. Dán (ghi đè) nội dung đã sao chép vào trình soạn thảo App Script. 37 | 38 | ![Dán code App Script](https://github.com/user-attachments/assets/b03f84bc-5d55-4fb8-a88f-862f095ddf07) 39 | 40 | ## Bước 5: Thêm Telegram Bot Token và Telegram User ID vào App Script 41 | 42 | 1. Trong code App Script, tìm đến các dòng sau: 43 | 44 | ```javascript 45 | var telegram_bot_id = 'YOUR_TELEGRAM_BOT_TOKEN'; 46 | var telegram_user_id = 'YOUR_TELEGRAM_USER_ID'; 47 | ``` 48 | 49 | 2. Thay thế `'YOUR_TELEGRAM_BOT_TOKEN'` bằng **API token** bạn đã nhận được ở Bước 1. 50 | 3. Thay thế `'YOUR_TELEGRAM_USER_ID'` bằng **User ID** bạn đã lấy được ở Bước 2. 51 | 4. Lưu ý: Không bỏ dấu '' ở đầu và cuối. 52 | 53 | ![Thêm Token và User ID vào App Script](https://github.com/user-attachments/assets/bf082a89-b005-4572-8869-1bff3513c224) 54 | 55 | ## Bước 6: Lưu App Script, thêm biển số và chạy thử 56 | 57 | ### 1. Thêm biển số vào Google Sheet 58 | 59 | * Trong Google Sheet của bạn, hãy nhập các biển số xe bạn muốn theo dõi vào **cột A**, bắt đầu từ hàng đầu tiên. Mỗi biển số xe trên một dòng. 60 | 61 | ![Thêm biển số vào Google Sheet](https://github.com/user-attachments/assets/82b7e6fd-bfa5-4fd9-b632-e54bb290a0af) 62 | 63 | ### 2. Chạy thử lần đầu 64 | 65 | 1. Trong trình soạn thảo App Script, chọn hàm `sendDailyPhatNguoiReport` từ dropdown menu (thường ở trên thanh công cụ). 66 | 2. Nhấn nút **Run** (biểu tượng hình tam giác). 67 | 3. Bạn có thể sẽ được yêu cầu cấp quyền cho script truy cập vào Google Sheet và gửi thông báo Telegram. Hãy chấp nhận các yêu cầu này. 68 | 69 | ![Chạy thử App Script](https://github.com/user-attachments/assets/424a2849-9772-43ca-9f78-a6b82a28ceb7) 70 | 71 | ### 3. Kiểm tra Log 72 | 73 | 1. Để kiểm tra xem script đã chạy thành công hay chưa, hãy vào menu **Xem** (View) > **Nhật ký thực thi** (Executions). 74 | 2. Xem lại nhật ký để đảm bảo không có lỗi xảy ra và bot đã gửi thông báo thành công đến Telegram của bạn. 75 | 76 | ![Kiểm tra Log](https://github.com/user-attachments/assets/7fb151d7-b167-412c-afa2-a9d608ff0d09) 77 | 78 | ## Bước 7: Tạo Trigger để tự động chạy theo thời gian 79 | 80 | 1. Trong trình soạn thảo App Script, chọn biểu tượng **Trình kích hoạt** (biểu tượng đồng hồ báo thức) ở menu bên trái. 81 | 2. Nhấn vào nút **Thêm trình kích hoạt** (Add Trigger) ở góc dưới bên phải. 82 | 3. Trong cửa sổ cấu hình trình kích hoạt: 83 | * **Chọn hàm để chạy:** `sendDailyPhatNguoiReport` 84 | * **Chọn sự kiện:** `Dựa trên thời gian` (Time-driven) 85 | * **Chọn loại trình kích hoạt dựa trên thời gian:** `Hẹn giờ theo phút` (Minutes timer) hoặc `Hẹn giờ theo giờ` (Hour timer) tùy theo tần suất bạn muốn nhận thông báo. 86 | * **Ví dụ:** Để nhận thông báo vào khoảng 7-8 giờ sáng hàng ngày, bạn có thể chọn `Hẹn giờ theo giờ` và đặt thời gian chạy từ 7 đến 8. 87 | 88 | ![Tạo Trigger](https://github.com/user-attachments/assets/b2fa49b6-8bb2-468b-be7f-e7881c5b8847) 89 | 90 | ![Cài đặt thời gian cho Trigger](https://github.com/user-attachments/assets/3179414e-5b03-4120-ba35-b4032bc3347b) 91 | 92 | **Chúc mừng!** Bạn đã tạo thành công bot Telegram để nhận thông báo biển số xe hàng ngày. 93 | 94 | ![Hoàn thành](https://github.com/user-attachments/assets/0b8b7134-1584-43d2-b002-576140fa74ca) 95 | -------------------------------------------------------------------------------- /appscrip_Google_sheet: -------------------------------------------------------------------------------- 1 | // --- Cấu hình Telegram --- 2 | // THAY THẾ HAI GIÁ TRỊ NÀY BẰNG TOKEN CỦA BOT VÀ CHAT ID CỦA BẠN 3 | var TELEGRAM_BOT_TOKEN = 'TELEGRAM BOT Token'; 4 | var TELEGRAM_CHAT_ID = 'TELEGRAM CHAT ID'; 5 | 6 | function sendTelegramMessage(chatId, text) { 7 |   var telegramUrl = 'https://api.telegram.org/bot' + TELEGRAM_BOT_TOKEN + '/sendMessage'; 8 | 9 |   if (text.length > 4096) { 10 |     text = text.substring(0, 4000) + "\n... (tin nhắn quá dài, đã cắt bớt)"; 11 |   } 12 | 13 |   var options = { 14 |     method: 'post', 15 |     payload: { 16 |       chat_id: chatId, 17 |       text: text, 18 |     }, 19 |     muteHttpExceptions: true 20 |   }; 21 | 22 |   try { 23 |     var response = UrlFetchApp.fetch(telegramUrl, options); 24 |     var responseCode = response.getResponseCode(); 25 |     var responseText = response.getContentText(); 26 | 27 |     if (responseCode >= 200 && responseCode < 300) { 28 |       Logger.log('Telegram message sent successfully.'); 29 |       return true; 30 |     } else { 31 |       Logger.log('Failed to send Telegram message. Code: ' + responseCode + ', Response: ' + responseText); 32 |       return false; 33 |     } 34 |   } catch (e) { 35 |     Logger.log('Error sending Telegram message: ' + e.toString()); 36 |     return false; 37 |   } 38 | } 39 | 40 | function getPhatNguoiDataForPlate(bienso) { 41 |   var cleanedBienso = bienso.replace(/\s+/g, "").replace(/[^a-zA-Z0-9]/g, ""); 42 | 43 |   if (!cleanedBienso) { 44 |     return '⚠️ Biển số trống/không hợp lệ được bỏ qua.'; 45 |   } 46 | 47 |   var apiUrl = 'https://api.checkphatnguoi.vn/phatnguoi'; 48 |   var payload = { 49 |     bienso: cleanedBienso 50 |   }; 51 | 52 |   var options = { 53 |     method: 'post', 54 |     contentType: 'application/json', 55 |     payload: JSON.stringify(payload), 56 |     muteHttpExceptions: true 57 |   }; 58 | 59 |   try { 60 |     var response = UrlFetchApp.fetch(apiUrl, options); 61 |     var responseCode = response.getResponseCode(); 62 |     var responseText = response.getContentText(); 63 | 64 |     if (responseCode >= 200 && responseCode < 300) { 65 |       var data = JSON.parse(responseText); 66 | 67 |       if (data.error) { 68 |         return '❌ Biển số: ' + bienso + ' - Lỗi từ API: ' + data.error; 69 |       } else if (data.data && Array.isArray(data.data) && data.data.length > 0) { 70 |         var allViolations = data.data; 71 |         var pendingViolations = allViolations.filter(item => item["Trạng thái"] === "Chưa xử phạt"); 72 |         var resolvedViolations = allViolations.filter(item => item["Trạng thái"] === "Đã xử phạt"); 73 | 74 |         var resultMessage = ''; 75 | 76 |         if (pendingViolations.length > 0) { 77 |           resultMessage += '🚨🚨 Biển số: ' + bienso + ' - *CÓ LỖI PHẠT NGUỘI CHƯA XỬ PHẠT!* \n\n'; 78 | 79 |           var formattedPendingViolations = pendingViolations.map(function(item) { 80 |              var noiGiaiQuyet = Array.isArray(item["Nơi giải quyết vụ việc"]) ? item["Nơi giải quyết vụ việc"].join(', ') : (item["Nơi giải quyết vụ việc"] || 'Không rõ'); 81 |              return ( 82 |                '  - Thời gian: ' + item["Thời gian vi phạm"] + '\n' + 83 |                '  - Địa điểm: ' + item["Địa điểm vi phạm"] + '\n' + 84 |                '  - Hành vi: ' + item["Hành vi vi phạm"] + '\n' + 85 |                '  - Trạng thái: ' + item["Trạng thái"] + '\n' + 86 |                '  - Nơi giải quyết: ' + noiGiaiQuyet 87 |              ); 88 |           }).join('\n---\n'); 89 | 90 |           resultMessage += formattedPendingViolations; 91 | 92 |           if (resolvedViolations.length > 0) { 93 |             resultMessage += '\n\n_(' + resolvedViolations.length + ' lỗi đã xử phạt trước đó)_'; 94 |           } 95 | 96 |         } else if (resolvedViolations.length > 0) { 97 |           resultMessage = '✅ Biển số: ' + bienso + ' - Có lỗi phạt nguội nhưng *TẤT CẢ ' + resolvedViolations.length + ' LỖI ĐỀU TRẢ GIÁ BẰNG TIỀN VÀ NƯỚC MẮT*.'; 98 | 99 |         } else { 100 |            resultMessage = '✅ Biển số: ' + bienso + ' - Có dữ liệu trả về nhưng không phân loại được trạng thái lỗi.'; 101 |            Logger.log('Warning: API returned data but no violations with known statuses for plate ' + bienso); 102 |         } 103 | 104 |         return resultMessage; 105 | 106 |       } else { 107 |         return '✅ Biển số: ' + bienso + ' - Không tìm thấy lỗi phạt nguội.'; 108 |       } 109 | 110 |     } else { 111 |        try { 112 |            var errorData = JSON.parse(responseText); 113 |            if (errorData && errorData.error) { 114 |                return '❌ Biển số: ' + bienso + ' - Lỗi server (' + responseCode + '): ' + errorData.error; 115 |            } else { 116 |                return '❌ Biển số: ' + bienso + ' - Lỗi kết nối/Server (' + responseCode + ')'; 117 |            } 118 |        } catch(e){ 119 |             return '❌ Biển số: ' + bienso + ' - Lỗi kết nối/Server (' + responseCode + ')'; 120 |        } 121 |     } 122 | 123 |   } catch (e) { 124 |     return '❌ Biển số: ' + bienso + ' - Lỗi khi gửi yêu cầu: ' + e.toString(); 125 |   } 126 | } 127 | 128 | function sendDailyPhatNguoiReport() { 129 |   var sheet = SpreadsheetApp.getActiveSheet(); 130 |   var ss = SpreadsheetApp.getActiveSpreadsheet(); 131 |   var timeZone = ss.getSpreadsheetTimeZone(); 132 | 133 |   var range = sheet.getRange('A:A'); 134 |   var values = range.getValues(); 135 | 136 |   var licensePlates = []; 137 |   for (var i = 0; i < values.length; i++) { 138 |     var plate = values[i][0]; 139 |     if (plate) { 140 |       var cleanedPlate = String(plate).trim().replace(/\s+/g, "").replace(/[^a-zA-Z0-9]/g, ""); 141 |        if (cleanedPlate) { 142 |          licensePlates.push(cleanedPlate); 143 |        } 144 |     } 145 |   } 146 | 147 |   if (licensePlates.length === 0) { 148 |     Logger.log('Không có biển số nào trong cột A để kiểm tra.'); 149 |     sendTelegramMessage(TELEGRAM_CHAT_ID, 'ℹ️ Báo cáo Phạt Nguội hàng ngày: Không tìm thấy biển số nào trong cột A để tra cứu.'); 150 |     return; 151 |   } 152 | 153 |   var today = new Date(); 154 |   var formattedDate = Utilities.formatDate(today, timeZone, 'dd/MM/yyyy'); 155 | 156 |   var startMessage = '📰 *Bắt đầu tra cứu Phạt Nguội hàng ngày cho ' + licensePlates.length + ' biển số ngày ' + formattedDate + '...*'; 157 |   sendTelegramMessage(TELEGRAM_CHAT_ID, startMessage); 158 | 159 |   Utilities.sleep(2000); 160 | 161 |   for (var i = 0; i < licensePlates.length; i++) { 162 |     var plate = licensePlates[i]; 163 |     Logger.log('Checking plate: ' + plate); 164 | 165 |     var result = getPhatNguoiDataForPlate(plate); 166 | 167 |     sendTelegramMessage(TELEGRAM_CHAT_ID, result); 168 | 169 |     Utilities.sleep(1500); 170 |   } 171 | 172 |   Utilities.sleep(2000); 173 |   sendTelegramMessage(TELEGRAM_CHAT_ID, ' *Hoàn thành tra cứu Phạt Nguội hàng ngày.*'); 174 | 175 |   Logger.log('Finished daily Phat Nguoi report, sent as separate messages.'); 176 | } 177 | 178 | function createDailyTrigger() { 179 |   deleteDailyTrigger(); 180 | 181 |   ScriptApp.newTrigger('sendDailyPhatNguoiReport') 182 |       .timeBased() 183 |       .everyDays(1) 184 |       .atHour(8) 185 |       .create(); 186 | 187 |   SpreadsheetApp.getUi().alert('Thông báo', 'Đã thiết lập Trigger gửi báo cáo Phạt Nguội hàng ngày vào khoảng 8-9 giờ sáng.', SpreadsheetApp.getUi().ButtonSet.OK); 188 | } 189 | 190 | function deleteDailyTrigger() { 191 |   var triggers = ScriptApp.getProjectTriggers(); 192 | 193 |   for (var i = 0; i < triggers.length; i++) { 194 |     if (triggers[i].getHandlerFunction() === 'sendDailyPhatNguoiReport') { 195 |       ScriptApp.deleteTrigger(triggers[i]); 196 |       Logger.log('Đã xóa Trigger báo cáo Telegram cũ.'); 197 |     } 198 |   } 199 |   SpreadsheetApp.getUi().alert('Thông báo', 'Đã xóa các Trigger báo cáo Phạt Nguội hàng ngày (nếu có).', SpreadsheetApp.getUi().ButtonSet.OK); 200 | } 201 | 202 | function onOpen() { 203 |   var ui = SpreadsheetApp.getUi(); 204 |   ui.createMenu('Tra Cứu PN') 205 |       .addItem('Tra cứu biển số đã chọn', 'checkPhatNguoiSelectedCell') 206 |       .addSeparator() 207 |       .addItem('Thiết lập báo cáo Telegram hàng ngày', 'createDailyTrigger') 208 |       .addItem('Xóa báo cáo Telegram hàng ngày', 'deleteDailyTrigger') 209 |       .addToUi(); 210 | } 211 | --------------------------------------------------------------------------------