Skip to content

Instantly share code, notes, and snippets.

@khacnghiem67
Forked from ntkhang03/Lichcupdien.js
Created December 26, 2024 02:12
Show Gist options
  • Save khacnghiem67/1b5a77c6229e408ed68ccb994490760b to your computer and use it in GitHub Desktop.
Save khacnghiem67/1b5a77c6229e408ed68ccb994490760b to your computer and use it in GitHub Desktop.
Gửi thông báo lịch cúp điện qua Email (Sử dụng Google App Scritp)

🚀 Hướng dẫn crawl lịch cúp điện từ trang web lichcupdien.org và gửi thông báo về email mỗi ngày (sử dụng Google App Script)

⚠️ Lưu ý: Script chỉ có tác dụng với trang web lichcupdien.org,.

  1. Truy cập trang chủ Google App Script, ấn New project để tạo một project mới. image.png

  2. Đổi tên cho project (nếu muốn), ví dụ: Thông báo lịch cúp điện. image.png

  3. Copy đoạn code bên trên và dán vào ô code, sau đó ấn Save để lưu lại. image.png

  4. Tiếp theo vào trang https://lichcupdien.org/, chọn trang lịch cúp điện cần theo dõi, ví dụ mình muốn theo dỡi lịch cúp điện Kiên Giang có url là: https://lichcupdien.org/lich-cup-dien-kien-giang/. Và chỉ muốn theo dõi khu vực Cao Lãnh image.png

  5. Copy url của trang lịch cúp điện đó, ví dụ: https://lichcupdien.org/lich-cup-dien-kien-giang/ và dán vào ô urlString của đoạn code bên dưới, và điền khu vực phía sau dấu :, sau đó chỉnh sửa địa chỉ email để nhận thông báo. image.png

  6. Tiếp theo chọn main từ menu thả xuống, sau đó ấn Run để chạy thử code. image.png

  7. Nếu không có lỗi nào xuất hiện, một hộp thoại sẽ hiển thị ra để yêu cầu cấp quyền gửi mail (vì Google App Script sẽ dùng acc gmail là author của project này để gửi mail). Tiếp theo hãy tiến hành cấp quyền cho project này. image.png image.png image.png image.png image.png

⚙️ Cấu hình Triggers để gửi thông báo mỗi ngày

  1. Chọn Triggers từ cột bên trái, sau đó chọn Add Trigger để tạo một trigger mới. image.png

  2. Tại mục Select type of time based trigger chọn Day timer, mục Select time of day chọn khung giờ muốn gửi thông báo, ví dụ: 9pm đến 10pm, sau đó ấn Save để lưu lại. image.png

  3. Sau khi tạo trigger thành công, tại giao diện danh sách trigger, bạn sẽ thấy trigger vừa tạo, và nó sẽ chạy theo khung giờ bạn đã chọn. Nếu muốn gửi thông báo ở nhiều khung giờ khác nhau, bạn có thể tạo nhiều trigger khác nhau. image.png

📸 Kết quả

image.png

// 📌 Description: Crawl lịch cúp điện từ trang web lichcupdien và gửi email thông báo
// ⚠️ Lưu ý: Script này chỉ có thể lấy dữ liệu từ trang web lichcupdien.org, không thể hoạt động với các trang web khác
// Các URL cần lấy dữ liệu
// Mỗi url (tương ứng với tỉnh/thành phố) có thể lọc ra các khu vực cần thiết để nhận thông báo, cách nhau bởi dấu phẩy
// Nếu muốn lấy thông báo cúp điện từ tất cả các khu vực của một tỉnh/thành phố, điền <ALL>
// Điền theo mẫu như ví dụ bên dưới, mỗi url (tỉnh/thành phố) cách nhau bởi dấu xuống dòng
// Ví dụ: Muốn lấy thông báo cúp điện từ Phú Quới (Vĩnh Long), Tắc Vân và Định Bình (Cà Mau), tất cả khu vực ở An Giang
const urlString = `
https://lichcupdien.org/lich-cup-dien-vinh-long: Phú Quới
https://lichcupdien.org/lich-cup-dien-ca-mau: Tắc Vân, Định Bình
https://lichcupdien.org/lich-cup-dien-an-giang: <ALL>
`;
// Hãy thay đổi các giá trị ở trên để lấy thông báo cúp điện từ các tỉnh/thành phố mà bạn quan tâm
// Có thể gửi thông báo đến nhiều email, mỗi email cách nhau bởi dấu phẩy
const recipients = "[email protected]";
function main() {
crawlAndSendMail();
}
function crawlAndSendMail() {
let htmlBody = "";
const urls = {};
urlString.split("\n").forEach((line) => {
if (!line.trim()) {
return;
}
const parts = line.split(":");
const url = parts.slice(0, -1).join(":").trim();
const areas = parts[parts.length - 1].trim();
urls[url] = areas;
});
for (const [url, areas] of Object.entries(urls)) {
const items = crawlData(url, areas);
htmlBody += `<h2 style="color: #4CAF50;">Lịch Cúp Điện Tại ${toUpperCaseEachWord((url.split("lich-cup-dien-")[1] || "").replace(/-/g, " "))} (${areas})</h2>`;
htmlBody += `<div style="font-family: Arial, sans-serif; color: #333;">`;
if (!items.length) {
htmlBody += `<p>Không có thông báo cúp điện nào.</p>`;
} else {
items.forEach((item) => {
const highlightedArea = item.khuVuc.replace(
new RegExp(item.areaDetected, "gi"),
'<strong style="color: red;">' +
toUpperCaseEachWord(item.areaDetected) +
"</strong>"
);
htmlBody += `
<div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; border-radius: 5px;">
<p><strong>Điện lực:</strong> ${item.dienLuc}</p>
<p><strong>Ngày:</strong> ${item.ngay}</p>
<p><strong>Thời Gian Cúp:</strong> Từ <strong>${item.thoiGian.from}</strong> đến <strong>${item.thoiGian.to}</strong></p>
<p><strong>Khu Vực:</strong> ${highlightedArea}</p>
<p><strong>Lý Do:</strong> ${item.lyDo}</p>
<p><strong>Trạng Thái:</strong> ${item.trangThai}</p>
</div>
`;
});
}
htmlBody += `</div>`;
}
htmlBody += `<p style="font-size: 12px; color: #888;">Đây là email tự động, vui lòng không trả lời.</p>`;
// Sử dụng GmailApp để gửi email
const recipientsArray = recipients.split(",").map((email) => email.trim());
for (const recipient of recipientsArray) {
GmailApp.sendEmail(recipient, "Lịch Cúp Điện", "", {
htmlBody
});
}
}
/**
* Crawls and filters data from a specified URL based on given areas
* @param {string} url - The URL to fetch data from
* @param {string} areasString - Comma-separated string of area names to filter by
* @returns {Array<Object>} Array of filtered data items where each item's khuVuc property includes one of the specified areas.
* Each matching item will have an additional 'areaDetected' property added.
* @throws {Error} May throw errors related to URL fetching or HTML parsing
*/
function crawlData(url, areasString = "") {
const areas = areasString.split(",").map((area) => area.trim().toLowerCase());
const response = UrlFetchApp.fetch(url);
const html = response.getContentText();
const data = parseData(html);
if (areasString.toLocaleLowerCase() === "<all>") {
return data;
}
return data.filter((item) =>
areas.some((area) => {
const isMatch = (item.khuVuc || "").toLowerCase().includes(area);
if (isMatch) {
item.areaDetected = area;
}
return isMatch;
})
);
}
function parseData(html) {
const result = [];
// Tách các phần lịch cúp điện
const parts = html.split('<div class="lcd_detail_wrapper">').slice(1);
parts.forEach((part) => {
const item = {};
// Điện lực
const dienLucMatch = part.match(
/<span class="content_item_content_lcd_wrapper item_txt_bold">([^<]+)<\/span>/
);
if (dienLucMatch) {
item.dienLuc = dienLucMatch[1];
}
// Ngày
const ngayMatch = part.match(
/<span class="content_item_content_lcd_wrapper item_txt_bold item_txt_red">([^<]+)<\/span>/
);
if (ngayMatch) {
item.ngay = ngayMatch[1];
}
// Thời gian
const thoiGianMatch = part.match(
/T <span class="item_lcd_time">([^<]+)<\/span> đến <span class="item_lcd_time">([^<]+)<\/span>/
);
if (thoiGianMatch) {
item.thoiGian = {
from: thoiGianMatch[1],
to: thoiGianMatch[2]
};
}
// Khu vực
const khuVucMatch = part.match(
/<span class="content_item_content_lcd_wrapper">([^<]+)<\/span>/
);
if (khuVucMatch) {
item.khuVuc = khuVucMatch[1];
}
// Lý do
const lyDoMatch = part.match(
/<span class="content_item_content_lcd_wrapper">([^<]+)<\/span>/
);
if (lyDoMatch) {
item.lyDo = lyDoMatch[1];
}
// Trạng thái
const trangThaiMatch = part.match(
/<span class="content_item_content_lcd_wrapper lcd_check_trang_thai">([^<]+)<\/span>/
);
if (trangThaiMatch) {
item.trangThai = trangThaiMatch[1];
}
result.push(item);
});
return result;
}
function toUpperCaseEachWord(str) {
const words = str.split(" ");
return words
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment