筆者今年高三,所以前陣子在翻找大學申請簡章、整理志願標準跟面試日期,覺得翻紙本找好麻煩,我就在今年寒假做一個大學科系查詢 LINE 機器人,沒想到公開不到一個月就有一萬位使用者,因此寫這篇文章分享我用的語法、資源、遇到的問題與解法,給板上想開發類似聊天機器人的朋友參考。
這篇文章的篇幅較長,我主要會分為想出點子的「 想法層面 」,和實際寫 code 的「 技術層面 」,如果你是單純好奇我怎麼想到這點子,或是怎麼抓到同學痛點、設計出破萬使用者的機器人,可以參考第一、第二段。
倘若你想直接看深層技術的開發過程( 包含怎麼寫程式碼 ),可以從第三段開始。當然也可以整篇都慢慢看完啦 XD 那我會很感謝你的閱讀!
大家看一下大學科系查詢 LINE 機器人的操作成果,只要傳送指定大學科系關鍵字,機器人會回傳該科系的資訊,以及校系分則連結( 裡面有學測標準等資料 ):
點擊加入行事曆,會轉到 Google 行事曆,面試日期、標題資訊都套好了,只需要按儲存:
點選收藏的話,機器人會紀錄起來,傳送「顯示收藏」即可查看之前收藏的科系:
前面加繁星兩個字,則可以查看繁星標準與轉系規定:
是不是很酷?有需要的話,歡迎點選 LINE Bot 連結加入好友,或是輸入 ID : @958pgmas ( 輸入時需包含 @ )下文我會解說我製作這機器人的過程。
會聯想到用 LINE 找大學科系資訊、製作聊天機器人,主要有 3 個原因:
想走個人申請的高三學生都會買本幾百頁的個申簡章,包含筆者也有買,去年十二月我翻簡章時就覺得很麻煩,而且輔導老師都會提醒選志願時,要避免挑到面試日期重疊的科系,更重要的是我想節省手動剩下彙整目標科系的過程。
我就一直在思考:有沒有辦法能讓同學找科系更方便?
後來我想起我曾經做過一個線上課程預約的 LINE 群組機器人,那就再製作個聊天機器人,讓大家在熟悉的 LINE 上找大學科系資訊!
在 2021 一月學測考完、開始放寒假後,我就著手設計大學科系查詢 LINE 機器人的 side project,第一步我先發想這機器人要有哪些功能?我從之前發現到的 3 個問題( 動機 )來著手:
在設計機器人的功能時,我以「 解決當初觀察到的問題 」為準則,思考有什麼科技方法或工具能增加效率、減少麻煩?能否以自動化的程式取代以往得手動做的事情?
例如解決二階日期重疊問題時,我第一個念頭是用電腦、手機上的行事曆設定活動、來看有沒有重疊。
我在寫程式或教學文章時,常會思考有哪些操作對人們來說是個問題?是否有相對應的程式碼或手段能幫忙?
我不是從零開始製作一個工具出來,而是利用環境現有的資源,串接成能解決大家問題的模組成品而已。
計畫好重點功能後,我接著設計機器人的架構和搜尋流程,本段我會說明當時設計架構的思路,和現在我想到的替代方案。
做查詢資料的 LINE 機器人,需要準備 2 個部分:
而我在做這個 side project 之前,就有計劃找班上同學一起來參與,我負責程式部分,其他同學幫我彙整科系資料,讓同學也能透過我的專案累積他們的備審資料( 作爲回饋,我也教他們寫基礎程式 XD )
因此當時我選用 Google 試算表當作小型資料庫,因為它是最普遍、學生都會用的線上文書服務,而且要教同學寫 SQL 編輯資料庫會來不及。
語法部分我則選擇用 Google 開發的 App Script,它是基於 Javasript 的程式語言,內嵌許多 Google 服務 API,其中就包含讀寫 Google 文件、試算表的功能。
App Script 也跟 Google 文件一樣能在線上 IDE 編寫、執行,我就不用另外設定金鑰、串接到自己的 server,結果發現架構變成完全 Serverless 的 Google 全家桶了 XD
不過如果很要求效能的話,用真正的 SQL Database 和 GO, Java 語法去做會更好,App Script 本身執行速度,與 Google 試算表的讀寫速度表現並不是很快。
我這裡為了前期編輯的便利性而犧牲了一些效能,但後文我會說明我怎麼改善執行效能的。
搞定架構設計後,我繼續規劃搜尋流程的部分,意指推演同學用 LINE 機器人查詢科系的操作步驟,我盡量保持簡單:
OK 準備進到寫 code 的環節了!本段會說明我怎麼把前面發想的功能,以程式碼的方式實踐出來,以及一些我寫程式時的思路等等紀錄。
程式開發對我而言算是興趣而已,不是電神、職業級的那種水準,還請各位前輩讀者指教!
前期我原本是寫 Python 爬蟲去抓大學甄選入學委員會的校系分則結果,之後發現網路上已經有知名補習業者《 甄戰 》彙整好的資料,所以我的科系 data 取自這兩個來源,不是手動翻紙本收錄,同學彙整資料的同時,我就處理程式的部分。
首先對我而言,最困難的就是分析使用者的關鍵字了,因爲每個科系都有它的縮寫與簡稱,像是資訊工程學系,我們普通都講資工;企業管理學系則是企管 ⋯⋯ 要預測使用者的科系搜尋意圖真的很麻煩。
這就是我請同學來幫忙的主要原因,我請他們針對每個科系去聯想可能的縮寫,並輸入到 Google 試算表裡,真的是「 人工 」智慧分析出來的。
下圖是資料表的截圖,A 欄位是同學輸入的預測關鍵詞:
我預設同學是要搜尋某某大學的某某系,因此訊息一定包含「大學」跟「科系」兩個部分,設計的分析流程如下:
那怎麼判斷關鍵字訊息裡哪部分是大學、哪部分是科系呢?我那時用了簡單粗暴的方法,以「 大 」或「 大學 」做分水嶺,前面的是大學名稱,後面就當科系名稱,開啟該大學的工作表,接著比對關鍵字欄位裡有沒有匹配的字詞。
例如台大資工,台大( 大字之前的訊息 )就當作大學,後面的資工就是科系,機器人開啟臺灣大學的工作表,回傳資工那行的科系資訊。
App Script 程式碼實作如下,我忘記留最初用 include 比對的版本,所以先放最新版的:
// 搜尋指定大學工作表裡的科系資訊
function search_department(code, department) {
try {
let school_sheet = SpreadSheet.getSheetByName(code); // 工作表皆以各大學英文代號命名,機器人會先分析大學名稱取得英文代號
let lastrow = school_sheet.getLastRow();
var info_array = [], status = ""; // info_array 負責存該大學所有科系
var data_array = school_sheet.getRange(1, 1, lastrow, 1).getValues();
data_array = data_array.flat();
var position_array = []; // 用來儲存科系在工作表的行數
var search_data = full_search(department, data_array); // full_search 是另外寫的模糊搜尋 function,分別投入關鍵字跟要搜尋的陣列
for (var x = 0; x < search_data.length; x++) {
if (search_data[x].score <= 0.25) { // score 越小,匹配度越高,我這裡設定 0.25
position_array.push(search_data[x].refIndex + 1); // 存入匹配科系的行數,行數等於陣列 index 值 + 1
}
}
extensive_search(); // 如果陣列長度為零( 完全沒有匹配的科系 ),增加 score 分數( 降低匹配門檻 )
let time = position_array.length;
// if time = 0, 此 loop 不執行,status = ""
for (var x = 0; x < time; x++) {
let target_row = position_array[x];
status = "got";
let department_info_array = school_sheet.getRange(target_row, 2, 1, 7).getValues();
info_array = info_array.concat(department_info_array.flat());
info_array.push(target_row);
}
}
catch (e) {
if (code == "not found") {
status = "school not found";
}
else {
console.log("error" + e);
}
}
if (status == "got") {
console.log("ok");
console.log("info" + info_array);
return info_array;
}
else if (status == "school not found") { // 沒有大學部分的搜尋結果
return "school not found";
}
else { // 沒有科系的搜尋結果
return "department not found";
}
function extensive_search() {
if (position_array.length == 0) {
for (var x = 0; x < search_data.length; x++) {
if (search_data[x].score <= 0.5) {
console.log(search_data[x])
position_array.push(search_data[x].refIndex + 1);
note_message = "未找到完全相符的科系,已啟用廣泛搜尋模式,下列結果可能較不準,請確認輸入的科系名稱是否正確或避免簡寫";
}
}
}
}
}
那這樣會有個問題,假如使用者沒傳「大」字呢?像東海法律、世新廣電?
我剛開始是要求傳訊息一定要包含大字,否則當作無效訊息,後來我使用 Fuzzy Search 模糊搜尋來比對,同時解決科系縮寫的問題,詳細資訊會在下文提到。
我把在試算表撈到的所有資料塞進一個 array,然後再把 array 每七個一組,拆成二維陣列並丟到 Flex Message 的 JSON 裡,回傳給使用者:
function format_flex(code, data_array) {
try {
var departments_array = separate(data_array);
}
catch (e) {
console.log(e);
}
let result_array = [];
for (var x = 0; x < departments_array.length; x++) {
// Google Sheet 部分欄位的值是 int,全部變數都要轉為 String,否則 LINE 端會回傳錯誤
let flex_title = departments_array[x][0], flex_recruit_num = String(departments_array[x][1]);
let flex_plan_to_review = String(departments_array[x][2]), flex_recommend = String(departments_array[x][3]);
let flex_fee = String(departments_array[x][4]);
let flex_review_date = departments_array[x][5], flex_url = departments_array[x][6];
let position = departments_array[x][7], flex_gcd_url = generate_calender_url(flex_title, flex_review_date)
flex_title = flex_title.replace("\n", "");
let flex_bubble_tmp = {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "? 科系搜尋結果 " + (x + 1),
"weight": "bold",
"size": "20px",
"margin": "10px"
},
{
"type": "box",
"layout": "vertical",
"margin": "lg",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "科系名稱",
"color": "#aaaaaa",
"size": "sm",
"flex": 3
},
{
"type": "text",
"text": flex_title,
"wrap": true,
"color": "#000000",
"size": "sm",
"flex": 5
}
]
}, {
"type": "box",
"layout": "baseline",
"spacing": "sm",
"margin": "10px",
"contents": [
{
"type": "text",
"text": "招生名額",
"color": "#aaaaaa",
"size": "sm",
"flex": 3
},
{
"type": "text",
"text": flex_recruit_num,
"wrap": true,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
},
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"margin": "10px",
"contents": [
{
"type": "text",
"text": "預計甄試人數",
"color": "#aaaaaa",
"size": "sm",
"flex": 3
},
{
"type": "text",
"text": flex_plan_to_review,
"wrap": true,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
},
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"margin": "10px",
"contents": [
{
"type": "text",
"text": "離島外加名額",
"color": "#aaaaaa",
"size": "sm",
"flex": 3
},
{
"type": "text",
"text": flex_recommend,
"wrap": true,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
},
{
"type": "box",
"layout": "baseline",
"spacing": "sm",
"contents": [
{
"type": "text",
"text": "甄試日期",
"color": "#aaaaaa",
"size": "sm",
"flex": 3
},
{
"type": "text",
"text": flex_review_date,
"wrap": true,
"color": "#666666",
"size": "sm",
"flex": 5
}
]
}
]
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "uri",
"label": "查看校系分則",
"uri": flex_url
}
},
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "postback",
"label": "加入收藏",
"data": code + "-" + position + "-" + flex_title,
"displayText": "加入收藏 " + flex_title
}
},
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "uri",
"label": "加入行事曆",
"uri": flex_gcd_url
}
},
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
}
result_array.push(flex_bubble_tmp);
}
if (note_message != "") {
var flex_content = [
{
"type": "text",
"text": "行事曆日期皆以各校簡章之第一天與最後一天做設定,實際的二階日期請點擊「 查看校系分則 」確認 ??" + "\n\n" + note_message
},
{
"type": "flex",
"altText": "找到了!請看大學相關科系的搜尋結果",
"contents": {
"type": "carousel",
"contents": result_array
}
}
];
}
else {
var flex_content = [
{
"type": "text",
"text": "行事曆日期皆以各校簡章之第一天與最後一天做設定,實際的二階日期請點擊「 查看校系分則 」確認 ??"
},
{
"type": "flex",
"altText": "找到了!請看大學相關科系的搜尋結果",
"contents": {
"type": "carousel",
"contents": result_array
}
}
];
}
return flex_content;
}
function separate(data_array) {
var departments_array = [], time = data_array.length, tmp_array;
for (var x = 0; x < time; x++) {
if ((x + 1) % 8 == 0) {
tmp_array = data_array.slice(x - 7, x + 1);
departments_array.push(tmp_array);
}
}
return departments_array;
}
再來是收藏功能,我在生成一個 Flex Message 時,在加入收藏的按鈕 postback 放入觸發詞 dash 號 “-” 跟科系名稱,當機器人收到的 postback 含有 dash,就用 split 語法以 dash 把 postback 切成大學代號與科系名稱,後面方法就跟找科系一樣了:
// line bot 收到 postback 時觸發
function handle_postback() {
if (userPostback_data.includes("-") && userPostback_data.indexOf("remove") == -1) {
let status = add_save(user_id, userPostback_data);
switch (status) {
case "department saved":
reply_message = [
{
"type": "text",
"text": "已成功收藏科系",
"quickReply": {
"items": [
{
"type": "action",
"action": {
"type": "message",
"label": "顯示收藏⭐️",
"text": "顯示收藏"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "清除收藏?",
"text": "清除收藏"
}
}
]
}
}
]
break;
case "fulled":
reply_message = reply_text("收藏名單已達上限( 6 個科系 ),傳送「刪除收藏」後再重新加入。");
break;
case "repeated":
reply_message = reply_text("你已收藏過此科系嘍,傳送「顯示收藏」,我就能顯示你的收藏科系~")
break;
}
}
function add_save(user_id, department) {
const savelist = Save_SpreadSheet.getSheetByName("save_list");
let lastrow = savelist.getLastRow();
let lastcolumn = savelist.getLastColumn();
let save_limit = 6;
let target_row = 0;
var x = 1;
var userstatus = "";
console.log("start");
let id_array = savelist.getRange(1, 1, lastrow, 1).getValues();
id_array = id_array.flat();
let check_if_id = id_array.some(data => data.includes(user_id));
if (check_if_id == false) {
console.log("bad");
userstatus = "new";
}
else {
target_row = id_array.indexOf(user_id) + 1;
console.log("trow:" + target_row);
userstatus = "saved";
console.log("user found!");
}
console.log("start check");
function check_repeat() {
for (x = 3; x <= (2 + save_limit); x++) {
var tmp_cell_value = savelist.getRange(target_row, x).getValue();
if (department == tmp_cell_value) {
console.log("repeated");
return "repeated";
}
else {
if (tmp_cell_value == "") {
savelist.getRange(target_row, x).setValue(department);
console.log("department saved!!");
return ("department saved");
}
else if (savelist.getRange(target_row, save_limit + 2).getValue() != "") {
full = true;
console.log("fulled");
return ("fulled");
}
}
}
}
function add_new_user() {
console.log("start add new");
savelist.getRange(lastrow + 1, 1).setValue(user_id);
savelist.getRange(lastrow + 1, 2).setValue(department);
console.log("new save successed!");
}
try {
switch (userstatus) {
case "saved":
console.log("user was saved");
return check_repeat();
break;
case "new":
console.log("new!");
add_new_user();
break;
default:
console.log("out");
}
}
catch (e) {
console.log("error");
}
console.log("end");
}
隱私聲明:個人針對收藏資料庫有加強權限管理,只有機器人本身能去讀寫,其他幫忙的同學無法存取,我也只在網友同學回報問題時進去修正,亦不會洩漏使用者儲存的科系給任何人或第三方單位。
上面有提到:我想藉由建立行事曆的方式,幫同學檢查面試日期是否重疊,但在 LINE 裡面無法存取 iPhone 內建的行事曆。
所以我用 Google 線上行事曆來做,因為它支援 iOS /Android,安卓手機預設是用 Google Calendar,在 iOS 裝置上則會開啟網頁版。
Google Calendar 有個好處是可以透過 url 來稱建立活動,還能改參數來設定時間、標題跟說明,不用設定行事曆 API 去新增。我就寫一個動態生成行事曆 url 的 function,丟入科系名稱與時間,轉換成可點擊的連結:
// 生成 Google 行事曆連結
function generate_calender_url(department_title, review_date_text) {
review_date_text = review_date_text.replaceAll("111", "2022");
review_date_text = review_date_text.replaceAll("\n", "");
var date_array = [];
if (review_date_text.indexOf("~") != -1) {
if (review_date_text.indexOf(" ~ ") != -1) {
date_array = review_date_text.split(' ~ ');
}
else {
date_array = review_date_text.split("~");
}
}
else if (review_date_text.includes("至")) {
date_array = review_date_text.split("至");
}
else if (review_date_text.includes("/")) {
date_array = review_date_text.split(" / ");
}
else if (review_date_text == "--" || review_date_text.includes("、")) { // 沒有二階日期
return "https://www.google.com/calendar/";
}
else {
date_array.push(review_date_text);
date_array.push(review_date_text);
}
let start_date = date_array[0].replaceAll(".", "");
let end_date = date_array[1].replaceAll(".", "");
department_title = encodeURI(department_title + "二階面試");
let department_detail = encodeURI("本行事曆時間以個申簡章之第一天為主,若有多個日期則以第一天與第二天為設定,最終甄試日期請自行另外確認並修改");
gcd_url = "https://www.google.com/calendar/render?action=TEMPLATE&text=" + department_title + "&details=" + department_detail + "&dates=" + start_date + "T000000Z%2F" + end_date + "T090000Z";
//console.log(gcd_url);
return gcd_url;
}
p.s 我目前還在整理機器人完整的程式碼,等註解都寫完後,我會把 GitHub 連結公開在這裡。
結果一月底我公開之後,經過兩次的使用者暴增潮( 一天內增加快 4000 位使用者 )而程式碼無法同步處理大量的查詢訊息,要先傳給 A 結果才會處理 B 的訊息,而且 App Script 的執行速度沒有到很快,於是我後期一直在改善程式碼的執行速度。
這裡我分享 3 個我改善 Google App Script 執行速度的方法:
以往我檢查一個 Array 裡有沒有包含一個值,都是用 loop 檢查:
function contain_6_loop(){
let test_array = [1,2,3,4,5,6,7,8];
let time = test_array.length;
for(let x = 0; x < time ; x++ ){
if(test_array[x]==6){
return True;
}
return False;
}
}
console.log(contain_6_loop()) // True
後來為了加速程式碼執行速度,我研究一下,發現改用內建語法可以減少執行時間,像是可以改用 .some 來檢查,大幅改用內建語法後,程式的執行速度確實有變快一些:
let test_array = [1,2,3,4,5,6,7,8]:
let contain_6 = test_array.some(data => data.includes(6));
console.log(contain_6) // True
機器人在搜尋科系時,會頻繁的讀取試算表的資料,這樣也會花一段時間,後來我也發現 App Script 有支援快取( 不是收銀 )
快取是常被重複使用或運算,而暫存下來的資料,以我的 case 來說,假設同學 A 傳「 台大資工 」,機器人找到並回傳台大資工的訊息 JSON 後,就把這筆結果存為快取,之後同學 B 傳台大資工時,機器人就傳上次快取的訊息,不用再從頭搜尋試算表。
因為我要傳的內容不具有即時性,所以我用快取來減少搜尋資料庫的動作。倘若你要傳的內容常即時更新,像車次或虛擬貨幣價格,就建議縮短快取時效,或乾脆不要用快取以免撈到過期資料。
剛開始使用者要看收藏科系時,機器人都得不斷在資料庫檢索目標科系在工作表裡哪一行,再抓取該行的科系資料,但收藏科系很多時,就要等很久才會叫出結果。
我就想到同學收藏科系之前就會搜尋科系,而在搜尋科系的同時機器人就會檢索資料庫了,那就在搜尋時紀錄下該科系在哪一行,這樣顯示收藏時就直接抓那行的資料,原本的 postback 如下:
ntu-國立臺灣大學資訊工程學系
// Bot 會分成 "ntu" 和 "國立臺灣大學資訊工程學系",接著比對
後來我就在 postback 裡面多塞一個目標行數:
ntu-27-國立臺灣大學資訊工程學系
// Bot 直接讀取 "ntu" 工作表第 27 行的資料,省去比對時間
修改後效果顯著,顯示結果的時間從 8 秒變到 3 秒左右( 在 IDE 端編譯的數據 )
接著分享一下我在開發大學科系查詢 LINE 機器人遇到的瓶頸問題,還有後來的解法。
儘管前期我請同學幫忙新增縮寫關鍵字,但難以涵蓋同學、家長用的縮寫,因此我研究怎麼把第三方的 Fuzzy Search 模組套用到 App Script 裡。
Fuzzy Search ( 模糊搜尋 )跟用 include 比對的差別在於:前者不用完全匹配才會判定為包含,假設我要判斷資工是否為資訊工程學系,include 完全匹配的結果如下:
var dp = "資工", target = "資訊工程學系"
if(target.include(dp)){
console.log("包含!")
}
else{
console.log("不包含")
}
// 會顯示不包含,因為 target 裡面沒有資工這個字詞
如果改用模糊搜尋就不一樣了,因爲資訊工程學系裡有資、工這兩個字,系統就能判斷為包含。
我是用 Fuse.js 這個模組來做模糊搜尋的,它載入不錯,回傳也會給予各個 value 的相似度( score )所以我會依照 score 的大小來判斷是否匹配。
Google App Script 不支援 npm install 外部模組,必須用 CDN 連結方式載入
之前載入 Fuse.js 時,我是設定每搜尋一次,就從 CDN request 載入一次檔案,而 App Script 第三方 request ( UrlFetch )是有用量限制的。結果後來人數暴增時,一天內就超標了,剛學會用 cache 的我才想起來:可以快取起來,這樣未來就不用頻繁 send request 了:
var cache = CacheService.getScriptCache(); // 啟用快取功能
function full_search(word,array) {
function loadJSFromServer() {
if(cache.get("full_search")==null){
var url = 'https://cdn.jsdelivr.net/npm/fuse.js@6.5.3'; // from Fuse.js
var javascript = UrlFetchApp.fetch(url).getContentText();
cache.put("full_search",javascript,2592000)
}
else{
javascript = cache.get("full_search");
}
eval(javascript);
}
const options = {
includeScore: true
}
loadJSFromServer();
const fuse = new Fuse(array, options)
const fuse = new Fuse(array, options)
const result = fuse.search(word)
console.log(result);
return result;
}
在做「 大學科系查詢 LINE 機器人 」這個 side project 時,由於使用者遠遠傳出我的預期,我寒假花超大量的時間跟心力去製作跟維護,也學到很多部分:
我有在機器人回覆訊息裡放我的工作用 email,所以陸陸續續有收到一些同學的建議與功能許願,像是有不少同學反應想加入繁星標準,我就另外寫查繁星的功能,希望讓這個成品更貼合同學和家長的需求。
能開發出「 大學科系查詢 LINE 機器人 」真的是很特別的經驗,這是我第一次自己建立這麼多使用者的成品,在三個禮拜多內使用者就能破萬,不僅多累積了一個程式作品,我還因此受邀參加 LINE Developers 線上小聚,更重要的是我得以實踐去年的初衷,幫同學找科系更方便了。
說實話,看到使用者飆升、跟持續收到私訊意見難免有點壓力,但現在來看算是很有成就感,從想出 idea、找同學整理資料、寫程式碼….自己走完一次開發程式的路,感觸許多。
“ Work hard, be kind, and amazing things will happen ”
這是我個人奉行的準則,本次製作 LINE 機器人的經驗也是我對這句話的實踐。
我想到用 LINE 找科系的時間點,是在得知特殊選才( 不看考試成績,只看特殊能力、作品的升學管道 )只有備取結果的幾天之後,剛開始的我以為落榜之後的生活是無望的,從未想到現在還能做出一些效果不錯的作品、幫助其他同學。
想表達的是:我們在十八歲碰到的失敗,並不代表未來幾十年都是不如意的,沒有升學目標或他人評論能來定義我們的價值。其實只要努力付出,保持良善,美好的事情終究會發生。
此外這個機器人發布後,也有被其他開發者和部分品牌「 致敬 」,市面開始出現類似功能的 LINE Bot。現在的我其實很高興,因為我的初衷就是「 幫高中同學和學弟妹更方便找科系資料 」
哪怕別人借鑒我用 LINE 查大學科系標準的點子、製作類似的服務,對整體學生族群來說都是有利的,查科系申請資料不再是難事,所以致敬者也是間接幫我達成目標了,感謝他們的幫忙 XD
感謝你的閱讀!希望能幫到你開發 LINE 機器人或自己的資訊專案。如果你對我的開發歷程發表看法,或想給程式方面的建議,歡迎在本文下方留言!
大學科系查詢機器人好友連結 ??
本文原網址??
做個 LINE 機器人記錄誰 +1!群組 LINE Bot 製作教學與分享
Load External JavaScript Libraries in Google Scripts with eval() – Digital Inspiration
Cache Service | Apps Script | Google Developers
就程式面來說還有許多可以改進的地方
如var、let、const的使用,設計模式等
不過能做碼農的人多的是,能拿來解決現實問題的比例就少了
分享出來回饋社會的又更少了
個人當年高三時每天自習到晚上九點,六日不休
除了學測指考的內容與考試技巧外什麼都沒有
再看看作者已經做出萬人使用的Line bot
實在汗顏
個人在程式方面真的有很多不足的地方,這也是我今年會努力加強的目標,感謝您的指點。
個人只是恰好發現學生的某個問題,做個小工具幫忙才能有這麼大的迴響啦,我會繼續加油的!謝謝