Greasy Fork 支持简体中文。

104.com.tw Helper

Add useful links to 104 job pages.

  1. // ==UserScript==
  2. // @name 104.com.tw Helper
  3. // @namespace https://github.com/gslin/104-helper-userscript
  4. // @description Add useful links to 104 job pages.
  5. // @include https://www.104.com.tw/*
  6. // @version 0.20241205.6
  7. // @license MIT
  8. // @grant GM_addStyle
  9. // @grant GM_getValue
  10. // @grant GM_openInTab
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_setValue
  13. // @grant GM_xmlhttpRequest
  14. // @grant unsafeWindow
  15. // @require https://update.greasyfork.org/scripts/395037/764968/MonkeyConfig%20Modern.js
  16. // @connect findbiz.nat.gov.tw
  17. // ==/UserScript==
  18.  
  19. (() => {
  20. 'use strict';
  21.  
  22. const cfg = new MonkeyConfig({
  23. menuCommand: true,
  24. params: {
  25. search_template: {
  26. type: 'text',
  27. default: 'https://www.google.com/search?q=',
  28. },
  29. },
  30. title: '104.com.tw Helper - Configure',
  31. });
  32.  
  33. const search_template = cfg.get('search_template');
  34. const search_domain = search_template.match(/^https:\/\/(.*?)\//)[1];
  35.  
  36. const append_links = async (node, company_name) => {
  37. const company_name_chinese = get_company_name_chinese(company_name);
  38. const company_name_chinese_encoded = encodeURIComponent(company_name_chinese);
  39.  
  40. const company_name_chinese_rtrim = get_company_name_chinese_rtrim(company_name);
  41. const company_name_chinese_rtrim_encoded = encodeURIComponent(company_name_chinese_rtrim);
  42.  
  43. const company_name_shorted = get_company_name_short(company_name);
  44. const company_name_shorted_encoded = encodeURIComponent(company_name_shorted);
  45.  
  46. const btn = document.createElement('button');
  47. btn.setAttribute('accesskey', 'c');
  48. btn.setAttribute('class', 'btn_open_helper_links');
  49. btn.setAttribute('style', 'display: block;');
  50. btn.innerHTML = '打開以下連結 (tabs)';
  51.  
  52. // Special workaround for 求職小幫手
  53. btn.setAttribute('onclick', 'open_helper_outbound_links();');
  54.  
  55. node.appendChild(btn);
  56.  
  57. const res = await new Promise(resolve => {
  58. const data = 'qryCond=' + company_name_chinese_rtrim_encoded + '&infoType=D&qryType=cmpyType&cmpyType=true&brCmpyType=&busmType=&factType=&lmtdType=&isAlive=all&busiItemMain=&busiItemSub=&sugCont=&sugEmail=&g-recaptcha-response=';
  59. const url = 'https://findbiz.nat.gov.tw/fts/query/QueryList/queryList.do';
  60.  
  61. const req = GM_xmlhttpRequest({
  62. anonymous: true,
  63. data: data,
  64. headers: {
  65. 'Content-Type': 'application/x-www-form-urlencoded',
  66. 'Referer': url,
  67. },
  68. method: 'POST',
  69. onerror: res => {
  70. resolve(res);
  71. },
  72. onload: res => {
  73. resolve(res);
  74. },
  75. url: url,
  76. });
  77. });
  78.  
  79. if (200 !== res.status) {
  80. const err_txt = document.createElement('p');
  81. err_txt.innerHTML = 'Get error (findbiz.nat.gov.tw): "' + res.statusText + '".';
  82. node.appendChild(err_txt);
  83. } else {
  84. const findbiz_body = document.implementation.createHTMLDocument('');
  85. findbiz_body.documentElement.innerHTML = res.responseText;
  86.  
  87. const el = document.createElement('div');
  88. el.setAttribute('style', 'background: #ddd; margin: 9px 0;');
  89. el.innerHTML = '<h2 style="display: inline-block; margin: 9px;"><a href="https://findbiz.nat.gov.tw/fts/query/QueryBar/queryInit.do">商工登記公示資料查詢服務</a>資料:</h2>';
  90. el.appendChild(document.createTextNode(`(搜尋:${company_name_chinese_rtrim})`));
  91. for (const item of findbiz_body.querySelectorAll('.panel.panel-default')) {
  92. let matches = [];
  93. while (matches = item.textContent.match(/\b(\d{7})\b/)) {
  94. const company_date_raw = (parseInt(matches[1], 10) + 19110000).toString();
  95. const company_date = company_date_raw.substring(0, 4) + '/' + company_date_raw.substring(4, 6) + '/' + company_date_raw.substring(6);
  96. item.innerHTML = item.innerHTML.replace(matches[1], company_date);
  97. }
  98.  
  99. for (const a of item.querySelectorAll('a')) {
  100. const href = a.getAttribute('href');
  101. if (href.startsWith('/fts')) {
  102. a.setAttribute('href', 'https://findbiz.nat.gov.tw' + href);
  103. }
  104. }
  105. el.appendChild(item);
  106. }
  107. node.appendChild(el);
  108. }
  109.  
  110. /* links */
  111. const search_blacklist = [
  112. '104.com.tw',
  113. '1111.com.tw',
  114. '518.com.tw',
  115. 'cakeresume.com',
  116. 'indeed.com',
  117. 'interview.tw',
  118. 'recruit.net',
  119. 'salary.tw',
  120. 'wajob.cc',
  121. 'www.ptt.cc',
  122. 'yes123.com.tw',
  123. 'yourator.co',
  124. ];
  125. let search_link = search_template + '"' + company_name_shorted_encoded + '"+' + company_name_chinese_encoded + '+~面試';
  126. for (const site of search_blacklist) {
  127. search_link += '+-site:' + site;
  128. }
  129. const search_el = gen_el(search_link, `去搜尋引擎看看 (${search_domain})`);
  130. node.appendChild(search_el);
  131.  
  132. const mol_link = 'https://announcement.mol.gov.tw/?UNITNAME=' + company_name_chinese_encoded;
  133. const mol_el = gen_el(mol_link, '去勞動部看看 (announcement.mol.gov.tw)');
  134. node.appendChild(mol_el);
  135.  
  136. const linkedin_link = 'https://www.linkedin.com/search/results/all/?keywords=' + company_name_shorted_encoded;
  137. const linkedin_el = gen_el(linkedin_link, '去 LinkedIn 看看 (www.linkedin.com)');
  138. node.appendChild(linkedin_el);
  139.  
  140. const linkedin_search_link = search_template + 'site:linkedin.com+' + company_name_shorted_encoded;
  141. const linkedin_search_el = gen_el(linkedin_search_link, `去 LinkedIn 看看 (${search_domain})`);
  142. node.appendChild(linkedin_search_el);
  143.  
  144. const ptt_link = search_template + '"' + company_name_shorted_encoded + '"+' + company_name_chinese_encoded + '+~面試+site:www.ptt.cc';
  145. const ptt_el = gen_el(ptt_link, `去 Ptt 看看 (${search_domain})`);
  146. node.appendChild(ptt_el);
  147.  
  148. const qollie_link = 'https://www.qollie.com/search?keyword=' + company_name_chinese_rtrim_encoded + '&kind=company';
  149. const qollie_el = gen_el(qollie_link, '去 Qollie 看看 (qollie.com)');
  150. node.appendChild(qollie_el);
  151. };
  152.  
  153. const get_company_name_chinese = name => {
  154. name = name.trim()
  155. .replace(/\(.*\)/, '')
  156. .trim()
  157. .replace(/[\/_]+/g, ' ')
  158. .trim()
  159. .replace(/[^ ]*[0-9.A-Z_a-z]+[^ ]*/g, ' ')
  160. .trim()
  161. .replace(/.* /g, '');
  162. return name.trim();
  163. };
  164.  
  165. const get_company_name_chinese_rtrim = name => {
  166. name = get_company_name_chinese(name);
  167. name = name.trim()
  168. .replace(/(台|臺)灣(子|分)公司$/, '')
  169. .replace(/(台|臺)灣辦事處$/, '')
  170. .replace(/在(台|臺)辦事處$/, '')
  171. .replace(/公司.*(子|分)公司$/, '公司')
  172. .replace(/(子|分)公司$/, '');
  173. return name;
  174. };
  175.  
  176. const get_company_name_short = name => {
  177. name = get_company_name_chinese(name);
  178. name = name.trim()
  179. .replace(/^財團法人/, '')
  180. .replace(/^(法|英)屬/, '')
  181. .replace(/^(馬紹爾群島|維京群島|開曼群島|澳大利亞|馬來西亞|薩摩亞|塞席爾|賽席爾|新加坡|香港|瑞士|汶萊|印度|英|美|港|日)商/, '')
  182. .replace(/(台|臺)灣(子|分)公司$/, '')
  183. .replace(/(台|臺)灣辦事處$/, '')
  184. .replace(/在(台|臺)辦事處$/, '')
  185. .replace(/公司.*(子|分)公司$/, '公司')
  186. .replace(/(子|分)公司$/, '')
  187. .replace(/股份有限公司$/, '')
  188. .replace(/有限公司$/, '')
  189. .replace(/公司$/, '');
  190. return name.trim();
  191. };
  192.  
  193. const gen_el = (url, text) => {
  194. const el = document.createElement('a');
  195. el.setAttribute('class', 'helper_outbound_link');
  196. el.setAttribute('href', url);
  197. el.innerHTML = text;
  198. return el;
  199. };
  200.  
  201. const initial_css = () => {
  202. const el = document.createElement('style');
  203. el.innerHTML = '.helper_outbound_link { clear: both; float: left; }';
  204. document.querySelector('head').appendChild(el);
  205. };
  206.  
  207. const verify_hh = (node, company_name) => {
  208. const hh_list = [
  209. 'Arbour Services Limited.',
  210. 'echoas Taiwan_愛司人才管理顧問股份有限公司',
  211. 'Morgan Philips Hong Kong Limited Taiwan Branch_香港商博禹國際顧問有限公司台灣分公司',
  212. 'ROBERT WALTERS_華德士股份有限公司',
  213. '保聖那管理顧問股份有限公司',
  214. '全球人事顧問股份有限公司',
  215. '全鼎管理顧問有限公司',
  216. '創為人力資源有限公司',
  217. '台灣米高蒲志國際股份有限公司',
  218. '台灣英創管理顧問股份有限公司',
  219. '台灣英特艾倫人力資源有限公司',
  220. '嘉迅人才管理咨詢有限公司',
  221. '寶華人力資源顧問股份有限公司',
  222. '巖愷管理顧問有限公司',
  223. '康彼斯顧問股份有限公司',
  224. '德倫思管理顧問有限公司',
  225. '怡東人事顧問股份有限公司',
  226. '新加坡商立可人事顧問有限公司台灣分公司',
  227. '新加坡商立福人事顧問有限公司台灣分公司',
  228. '新加坡商艾得克有限公司台灣分公司',
  229. '益晟人力資源有限公司',
  230. '立展企管顧問有限公司',
  231. '立樂高園股份有限公司',
  232. '立樂高園股份有限公司',
  233. '精英人力資源股份有限公司',
  234. '經緯智庫股份有限公司 (MGR Consulting Co., Ltd.)',
  235. '聚賢亞洲有限公司',
  236. '華卡企業股份有限公司',
  237. '萬寶華企業管理顧問股份有限公司',
  238. '萬豐人力資源管理顧問有限公司',
  239. '萬通國際人力開發股份有限公司',
  240. '藝珂人事顧問股份有限公司',
  241. '賜禾有限公司',
  242. '逸亞管理顧問股份有限公司',
  243. '遠誠人力資源顧問股份有限公司',
  244. '銳光管理顧問有限公司',
  245. '首席國際獵才有限公司',
  246. '香港商唐尼爾森人力資源有限公司台灣分公司',
  247. '鴻鈺人力資源管理顧問有限公司',
  248. ];
  249.  
  250. if (hh_list.indexOf(company_name) >= 0) {
  251. const el = document.createElement('div');
  252. el.setAttribute('style', 'color: darkred; font-size: 2em; line-height: 2em;');
  253. el.innerHTML = '(Company in HeadHunter List)';
  254. node.appendChild(el);
  255. }
  256. };
  257.  
  258. // Special workaround for 求職小幫手
  259. unsafeWindow.open_helper_outbound_links = () => {
  260. const links = Array.from(document.getElementsByClassName('helper_outbound_link')).reverse();
  261. for (let el of links) {
  262. GM_openInTab(el.getAttribute('href'), {active: false});
  263. };
  264. };
  265.  
  266. const pathname = document.location.pathname;
  267.  
  268. // Company page
  269. if (pathname.startsWith('/company/')) {
  270. const ob = new MutationObserver(() => {
  271. const company_el = document.querySelector('h1');
  272. if (!company_el) {
  273. return;
  274. }
  275. const company_name = company_el.textContent.trim();
  276. if ('' === company_name) {
  277. return;
  278. }
  279. console.debug('company_name = ' + company_name);
  280.  
  281. const anchor_el = document.querySelectorAll('.col.main')[1];
  282. if (!anchor_el) {
  283. return;
  284. }
  285.  
  286. initial_css();
  287.  
  288. const base_node = document.createElement('div');
  289. base_node.setAttribute('style', 'clear: both; display: table; padding: 0 1em 1em; width: 100%;');
  290.  
  291. anchor_el.insertAdjacentElement('beforebegin', base_node);
  292.  
  293. verify_hh(base_node, company_name);
  294. append_links(base_node, company_name);
  295.  
  296. ob.disconnect();
  297. });
  298. ob.observe(document, {childList: true, subtree: true});
  299.  
  300. return;
  301. }
  302.  
  303. // Job page
  304. if (pathname.startsWith('/job/')) {
  305. initial_css();
  306.  
  307. const ob = new MutationObserver(() => {
  308. const company_el = document.querySelector('a[data-gtm-head="公司名稱"]');
  309. if (!company_el) {
  310. return;
  311. }
  312. const company_name = company_el.textContent.trim();
  313. if ('' === company_name) {
  314. return;
  315. }
  316. console.debug('company_name = ' + company_name);
  317.  
  318. const base_node = document.createElement('div');
  319. base_node.setAttribute('style', 'clear: both; display: table; width: 100%;');
  320.  
  321. const anchor_el = document.querySelector('.job-header__cont');
  322. anchor_el.insertAdjacentElement('afterend', base_node);
  323.  
  324. verify_hh(base_node, company_name);
  325.  
  326. let addr = document.querySelector('i[data-gtm-content="地圖連結"]');
  327. if (addr) {
  328. addr = addr.parentNode;
  329. const location_el = document.createElement('p');
  330. location_el.setAttribute('style', 'clear: both;');
  331. location_el.textContent = addr.childNodes[0].textContent;
  332. base_node.appendChild(location_el);
  333. }
  334.  
  335. append_links(base_node, company_name);
  336.  
  337. ob.disconnect();
  338. });
  339. ob.observe(document, {childList: true, subtree: true});
  340.  
  341. return;
  342. }
  343. })();