let ip = null; fetch("https://api.ipify.org?format=json").then((response) => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }).then((res) => { ip = res.ip; }).catch((_error) => { if ("caches" in window) { caches.match("https://api.ipify.org?format=json").then((response) => { if (response) { return response.json(); } }).then((data) => { if (ip === null && data !== undefined) { ip = JSON.parse(data).ip; } }); } }); let goodFortunes = []; let badFortunes = []; let special_events = []; let fortune_generated = false; // using async and await to prevent fetching the data too late... async function fetch_data() { await fetch("./json/fortune.json") .then((response) => response.json()) .then((data) => { goodFortunes = data.goodFortunes; badFortunes = data.badFortunes; }); await fetch("./json/special.json") .then((response) => response.json()) .then((data) => { special_events = data.special_events; }); } const textColorClass = [ "good-fortune", "good-fortune", "good-fortune", "good-fortune", "good-fortune", "middle-fortune", "bad-fortune", "bad-fortune", ]; const fortuneStatus = [ "大吉", "中吉", "小吉", "吉", "末吉", "中平", "凶", "大凶", ]; const chineseMonth = [ "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二", ]; const week = ["日", "一", "二", "三", "四", "五", "六"]; const title = `今日運勢`; const allGood = `萬事皆宜`; const allBad = `諸事不宜`; // date const d = new Date(); const date = d.getDate(); const day = d.getDay(); const month = d.getMonth() + 1; const year = d.getFullYear(); function daysDiff(eventIndex) { // define the date right now and the special event date const startDate = new Date(year, month - 1, date); const endDate = new Date( special_events[eventIndex].year, special_events[eventIndex].month - 1, special_events[eventIndex].date, ); // calculate the difference in milliseconds and convert it to days const timeDiff = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)); return timeDiff; } // pre-search jquery - save to a variable to improve performance const J_l_1_event = $("#l-1-event"); const J_l_1_desc = $("#l-1-desc"); const J_l_2_event = $("#l-2-event"); const J_l_2_desc = $("#l-2-desc"); const J_r_1_event = $("#r-1-event"); const J_r_1_desc = $("#r-1-desc"); const J_r_2_event = $("#r-2-event"); const J_r_2_desc = $("#r-2-desc"); const J_ip_to_fortune = $("#ip-to-fortune"); let special = false; let special_events_index = 0; // init page async function init_page() { // fetch fortune.json and special.json await fetch_data(); // hide the elements of show fortune page $("#result-page").hide(); // show date before button pressed const showMonth = `${ chineseMonth[month - 1] + "月" }`; const showDate = `${ ("0" + date).slice(-2) }`; const showDay = `${ "星期" + week[day] }`; $("#month").html(showMonth); $("#date").html(showDate); $("#weekday").html(showDay); const showSpecialEventCount = 2; let eventIndexPtr = 0, eventIndexList = Array(showSpecialEventCount).fill(-1); // check if there is special event today for (let i = 0; i < special_events.length; i++) { if (daysDiff(i) > 0) { if ( eventIndexPtr < showSpecialEventCount && eventIndexList[eventIndexPtr] == -1 ) { eventIndexList[eventIndexPtr] = i; eventIndexPtr++; } } else if (daysDiff(i) == 0) { special = true; special_events_index = i; } } // if there is upcoming event then show for (let eventIndex = 0; eventIndex < showSpecialEventCount; eventIndex++) { if (eventIndexList[eventIndex] != -1) { const days = daysDiff(eventIndexList[eventIndex]); const upcoming_event = `距離${ special_events[eventIndexList[eventIndex]].event }還剩${days}`; $(`#upcoming-event-${eventIndex + 1}`).html(upcoming_event); } } // show special event if today is a special day if (special) { const special_event_today = `今日是${ special_events[special_events_index].event }`; $("#special-day").html(special_event_today); } const last_date_str = localStorage.getItem("last_date"); if (last_date_str !== null && last_date_str !== undefined) { const now_date = new Date(); const last_date = new Date(last_date_str); if ( now_date.getFullYear() === last_date.getFullYear() && now_date.getMonth() === last_date.getMonth() && now_date.getDate() === last_date.getDate() ) { fortune_generated = true; Appear(); } } } // event bar const good_span = (event) => `宜: ${event}`; const bad_span = (event) => `忌: ${event}`; const desc_span = (desc) => `${desc}`; function Appear() { $("#title").html(title); $("#btn").html("打卡成功"); // disable the btn $("#btn").attr("disabled", "disabled"); //change page $("#init-page").hide(); $("#result-page").show(); // some lengths const goodLen = goodFortunes.length; const badLen = badFortunes.length; const statusLen = fortuneStatus.length; let status_index = -1; let seed1 = -1; let seed2 = -1; if (!fortune_generated) { // transform ip to four numbers const num = ip.split(".").map((num) => parseInt(num)); // TODO: improve the hash process const hashDate = Math.round( Math.log10( year * ((month << (Math.log10(num[3]) + day - 1)) * (date << Math.log10(num[2] << day))), ), ); seed1 = (num[0] >> hashDate) * (num[1] >> Math.min(hashDate, 2)) + (num[2] << 1) * (num[3] >> 3) + (date << 3) * (month << hashDate) + (year * day) >> 2; seed2 = (num[0] << (hashDate + 2)) * (num[1] << hashDate) + (num[2] << 1) * (num[3] << 2) + (date << (hashDate - 1)) * (month << 4) + year >> hashDate + (date * day) >> 1; // decide the status status_index = ((seed1 + seed2) % statusLen + statusLen) % statusLen; // update last record localStorage.setItem("last_date", d.toISOString()); localStorage.setItem("last_status_index", status_index.toString()); localStorage.setItem("last_seed1", seed1.toString()); localStorage.setItem("last_seed2", seed2.toString()); } else { status_index = parseInt(localStorage.getItem("last_status_index")); seed1 = parseInt(localStorage.getItem("last_seed1")); seed2 = parseInt(localStorage.getItem("last_seed2")); } const status = `§ ${fortuneStatus[status_index]} §`; if (special) { status_index = special_events[special_events_index].status_index; const special_status = `§ ${ fortuneStatus[status_index] } §`; J_ip_to_fortune.html(special_status); } else { J_ip_to_fortune.html(status); } // make sure the events won't collide const set = new Set(); const l1 = (seed1 % goodLen + goodLen) % goodLen; set.add(goodFortunes[l1].event); let l2 = (((seed1 << 1) + date) % goodLen + goodLen) % goodLen; while (set.has(goodFortunes[l2].event)) { l2 = (l2 + 1) % goodLen; } set.add(goodFortunes[l2].event); let r1 = (((seed1 >> 1) + (d.getMonth() << 3)) % badLen + badLen) % badLen; while (set.has(badFortunes[r1].event)) { r1 = (r1 + 2) % badLen; } set.add(badFortunes[r1].event); let r2 = ((((((seed1 << 3) + (d.getFullYear() >> 5) * (date << 2)) % badLen) * seed2) >> 6) % badLen + badLen) % badLen; while (set.has(badFortunes[r2].event)) { r2 = (r2 + 1) % badLen; } // organize the stuffs below this line... const l1_desc_list = goodFortunes[l1].description; const l2_desc_list = goodFortunes[l2].description; const r1_desc_list = badFortunes[r1].description; const r2_desc_list = badFortunes[r2].description; const l_1_event = good_span(goodFortunes[l1].event); const l_1_desc = desc_span( l1_desc_list[Math.abs(seed1) % l1_desc_list.length], ); const l_2_event = good_span(goodFortunes[l2].event); const l_2_desc = desc_span( l2_desc_list[Math.abs(seed2) % l2_desc_list.length], ); const r_1_event = bad_span(badFortunes[r1].event); const r_1_desc = desc_span( r1_desc_list[Math.abs(seed1) % r1_desc_list.length], ); const r_2_event = bad_span(badFortunes[r2].event); const r_2_desc = desc_span( r2_desc_list[Math.abs(seed2) % r2_desc_list.length], ); if (special) { // instead clear variable name, use short variable name for here... cuz it's too repetitive const Data = special_events[special_events_index]; if (status_index == 0) { J_r_1_event.html(allGood); } else { J_r_1_event.html(bad_span(Data.badFortunes.r_1_event)); J_r_1_desc.html(desc_span(Data.badFortunes.r_1_desc)); J_r_2_event.html(bad_span(Data.badFortunes.r_2_event)); J_r_2_desc.html(desc_span(Data.badFortunes.r_2_desc)); if (Data.badFortunes.r_1_event.length == 0) { J_r_1_event.html(r_1_event); J_r_1_desc.html(r_1_desc); } if (Data.badFortunes.r_2_event.length == 0) { J_r_2_event.html(r_2_event); J_r_2_desc.html(r_2_desc); } } if (status_index == statusLen - 1) { J_l_1_event.html(allBad); } else { J_l_1_event.html(good_span(Data.goodFortunes.l_1_event)); J_l_1_desc.html(desc_span(Data.goodFortunes.l_1_desc)); J_l_2_event.html(good_span(Data.goodFortunes.l_2_event)); J_l_2_desc.html(desc_span(Data.goodFortunes.l_2_desc)); if (Data.goodFortunes.l_1_event.length == 0) { J_l_1_event.html(l_1_event); J_l_1_desc.html(l_1_desc); } if (Data.goodFortunes.l_2_event.length == 0) { J_l_2_event.html(l_2_event); J_l_2_desc.html(l_2_desc); } } } else { if (status_index == 0) { J_r_1_event.html(allGood); } else { J_r_1_event.html(r_1_event); J_r_1_desc.html(r_1_desc); J_r_2_event.html(r_2_event); J_r_2_desc.html(r_2_desc); } if (status_index == statusLen - 1) { J_l_1_event.html(allBad); } else { J_l_1_event.html(l_1_event); J_l_1_desc.html(l_1_desc); J_l_2_event.html(l_2_event); J_l_2_desc.html(l_2_desc); } } $("#copy-result").removeClass("d-none"); } function getLuck() { Update(); } init_page();