巴哈姆特勇者福利社-跳過廣告&兌換流程自動化

在勇者福利社參加抽抽樂時,可以直接免費兌換抽獎卷而不用看廣告!

  1. // ==UserScript==
  2. // @name 巴哈姆特勇者福利社-跳過廣告&兌換流程自動化
  3. // @namespace ani20168
  4. // @version 2.3.1
  5. // @description 在勇者福利社參加抽抽樂時,可以直接免費兌換抽獎卷而不用看廣告!
  6. // @author ani20168
  7. // @homepage https://home.gamer.com.tw/profile/index.php?&owner=a20280210
  8. // @match https://fuli.gamer.com.tw/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=gamer.com.tw
  10. // @grant GM_xmlhttpRequest
  11. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. var observer;
  17. var enableAutoProcessOnBuyDPage = false;
  18. var enableAutoSkip = false;
  19.  
  20. // GM_config選單
  21. var GM_configStruct = {
  22. 'id': 'UserScriptConfig',
  23. 'title': '抽抽樂腳本設定',
  24. 'fields':
  25. {
  26. 'enableAutoProcessOnBuyDPage':
  27. {
  28. 'label': '自動送出收件資料',
  29. 'type': 'checkbox',
  30. 'default': false,
  31. 'title': '啟用後,在填寫收件人頁面會自動送出收件資料,開啟這項功能前請先確保收件人資訊完整,而且頁面上的"請幫我記住收件人資料"的確認框為打勾的狀態。'
  32. },
  33. 'enableAutoSkip':
  34. {
  35. 'label': '自動跳過廣告',
  36. 'type': 'checkbox',
  37. 'default': false,
  38. 'title': '啟用後,只要頁面是在抽抽樂的商品頁面,而且此商品目前可以透過看廣告的方式兌換抽獎機會,則會自動執行廣告跳過流程。'
  39. }
  40. },
  41. 'css':
  42. `
  43. #UserScriptConfig {
  44. font-family: 'Arial', sans-serif;
  45. background-color: #fff;
  46. color: #333;
  47. width: 300px;
  48. padding: 15px;
  49. margin: 0 auto;
  50. position: absolute;
  51. top: 50%;
  52. left: 50%;
  53. transform: translate(-50%, -50%);
  54. }
  55.  
  56. #UserScriptConfig .config_var {
  57. margin-bottom: 10px;
  58. }
  59.  
  60. #UserScriptConfig .field_label {
  61. margin-bottom: 5px;
  62. display: block;
  63. font-weight: bold;
  64. }
  65.  
  66. #UserScriptConfig input[type='checkbox'] {
  67. margin-right: 10px;
  68. transform: scale(1.2);
  69. }
  70.  
  71. #UserScriptConfig .section_header_holder {
  72. margin-bottom: 15px;
  73. }
  74.  
  75. #UserScriptConfig .config_header {
  76. font-size: 18px;
  77. margin-bottom: 15px;
  78. text-align: center;
  79. }
  80.  
  81. #UserScriptConfig .saveclose_buttons {
  82. text-align: center;
  83. padding: 8px 16px;
  84. margin:20px 40px 5px;
  85. border: 2px solid transparent;
  86. border-radius: 10px;
  87. transition: border-color 0.15s ease-in-out;
  88. }
  89.  
  90.  
  91.  
  92. #UserScriptConfig .saveclose_buttons:hover {
  93. border-color: #90ee90;
  94. }
  95.  
  96. #UserScriptConfig .reset, #UserScriptConfig .reset a {
  97. color: #999;
  98. text-decoration: none;
  99. padding: 8px 16px;
  100. display: block;
  101. margin: 10px auto 0;
  102. font-size: 12px;
  103. text-align: center;
  104. }
  105.  
  106. #UserScriptConfig .reset:hover, #UserScriptConfig .reset a:hover {
  107. color: #666;
  108. }
  109.  
  110. #UserScriptConfig .config_var {
  111. display: flex;
  112. align-items: center;
  113. justify-content: space-between;
  114. }
  115.  
  116. `,
  117. 'frameStyle':
  118. `
  119. bottom: auto; border: 2px solid #6F6F6F; display: none; height: 290px;
  120. left: 50%; margin: 0; max-height: 100%; max-width: 100%; opacity: 0;
  121. overflow: auto; padding: 0; position: fixed; right: auto; top: 50%;
  122. width: 330px; z-index: 9999;
  123. border-radius: 8px;
  124. box-shadow: 10px 10px 20px rgba(0,0,0,0.4);
  125. `
  126. ,
  127. 'events':
  128. {
  129. 'open': function() {
  130. console.log("配置界面已開啟");
  131. },
  132. 'save': function() {
  133. console.log("設定已保存");
  134. enableAutoProcessOnBuyDPage = GM_config.get('enableAutoProcessOnBuyDPage');
  135. enableAutoSkip = GM_config.get('enableAutoSkip');
  136. alert("保存完成!");
  137. },
  138. 'close': function() {
  139. console.log("配置界面已關閉");
  140. },
  141. 'init': () => {
  142. console.log("UI初始化");
  143. //獲取使用者設定
  144. enableAutoProcessOnBuyDPage = GM_config.get('enableAutoProcessOnBuyDPage');
  145. enableAutoSkip = GM_config.get('enableAutoSkip');
  146. }
  147. }
  148. };
  149.  
  150. // 初始化GM_config
  151. GM_config.init(GM_configStruct);
  152.  
  153. //處理驗證流程
  154. function handleCaptchaVerification() {
  155. var captchaFound = false; // 用於標記是否找到驗證器
  156.  
  157. // 嘗試執行Google reCAPTCHA
  158. var googleRecaptcha = document.getElementById('recaptcha');
  159. if (googleRecaptcha && typeof grecaptcha !== 'undefined' && grecaptcha.execute) {
  160. grecaptcha.execute();
  161. captchaFound = true; // 標記找到Google reCAPTCHA
  162. }
  163.  
  164. // 嘗試執行Cloudflare Turnstile
  165. if (typeof turnstile !== 'undefined' && document.querySelector('.cf-turnstile')) {
  166. turnstile.render('.cf-turnstile');
  167. captchaFound = true; // 標記找到Cloudflare Turnstile
  168. }
  169.  
  170. // 如果沒有找到任何驗證器,則提交表單
  171. if (!captchaFound) {
  172. jQuery('#buyD').submit();
  173. }
  174. }
  175.  
  176. // 檢查目前頁面是否為 message_done 頁面
  177. if (window.location.href.includes('https://fuli.gamer.com.tw/message_done.php')) {
  178. // 在 message_done 頁面上執行的操作
  179. var button = document.querySelector('button');
  180. if (button) {
  181. button.click();
  182. }
  183. }
  184.  
  185. // 等待頁面加載完畢
  186. window.addEventListener('load', function() {
  187. // 找到TOP-my元素
  188. var guiTopMyElement = document.querySelector('.TOP-my ul');
  189.  
  190. // 創建GUI按鈕的列表項
  191. var guiButtonLi = document.createElement('li');
  192. guiButtonLi.className = 'mobilehide';
  193.  
  194. // 創建GUI按鈕的連結元素
  195. var guiButtonLink = document.createElement('a');
  196. guiButtonLink.href = 'javascript:void(0)';
  197. guiButtonLink.onclick = function() {
  198. GM_config.open();
  199. };
  200.  
  201. // 為GUI按鈕添加圖標
  202. var guiButtonIcon = document.createElement('img');
  203. guiButtonIcon.src = 'https://i.imgur.com/uj2yF4e.png';
  204. guiButtonLink.appendChild(guiButtonIcon);
  205.  
  206. // 將連結元素加入到列表項中,然後將列表項加入到TOP-my區塊
  207. guiButtonLi.appendChild(guiButtonLink);
  208. guiTopMyElement.insertBefore(guiButtonLi, guiTopMyElement.firstChild); // 將GUI按鈕添加到最左側
  209.  
  210. // 檢查目前頁面是否為 buyD 頁面
  211. if (enableAutoProcessOnBuyDPage && window.location.href.includes('https://fuli.gamer.com.tw/buyD.php')) {
  212. document.getElementById('agree-confirm').checked = true;
  213. handleCaptchaVerification();
  214. return;
  215. }
  216.  
  217. //如果沒有兌換欄位
  218. var btnList = document.getElementById('buyBtnContent');
  219. if (!btnList) return;
  220. //看一下需不需要先回答問題,如果需要,回答問題並重整
  221. var questionButton = btnList.querySelector('a[onclick^="showQuestion(1);"]');
  222. if (questionButton) {
  223. getCsrfToken().then(token => {
  224. sendAnswerQuestionRequest(token);
  225. setTimeout(function() {
  226. location.reload();
  227. }, 2000);
  228. }).catch(error => {
  229. console.error('獲取 CSRF token 時發生錯誤:', error);
  230. });
  231. return;
  232. }
  233. //如果沒有看廣告兌換按鈕就return
  234. var adButton = btnList.querySelector('a[onclick^="window.FuliAd.checkAd"]');
  235. if (!adButton) return;
  236.  
  237. var newButton = document.createElement('a');
  238. newButton.className = 'btn-base c-accent-o';
  239. newButton.textContent = '跳過廣告';
  240. newButton.href = '#';
  241. newButton.style.marginLeft = '10px';
  242.  
  243. newButton.addEventListener('click', function(event) {
  244. event.preventDefault();
  245. executeAdSkippingProcess();
  246. });
  247.  
  248. btnList.appendChild(newButton);
  249.  
  250. // 如果啟用自動跳過廣告,則自動點擊新按鈕
  251. if (enableAutoSkip) {
  252. newButton.click();
  253. }
  254. });
  255.  
  256.  
  257.  
  258. function executeAdSkippingProcess() {
  259. watchAdCheck();
  260. getCsrfToken().then(token => {
  261. setTimeout(function() {
  262. sendPostRequest(token);
  263. }, 2000);
  264. }).catch(error => {
  265. console.error('獲取 CSRF token 時發生錯誤:', error);
  266. });
  267. }
  268.  
  269. // 獲取CSRF token
  270. function getCsrfToken() {
  271. return new Promise((resolve, reject) => {
  272. GM_xmlhttpRequest({
  273. method: "GET",
  274. url: "https://fuli.gamer.com.tw/ajax/getCSRFToken.php?_=1702883537159",
  275. onload: function(response) {
  276. var token = response.responseText.trim();
  277. if (token) {
  278. resolve(token);
  279. } else {
  280. reject('Token not found in response');
  281. }
  282. },
  283. onerror: function(error) {
  284. reject('Error during request: ' + error.message);
  285. }
  286. });
  287. });
  288. }
  289.  
  290. function sendAnswerQuestionRequest(csrfToken) {
  291. // 獲取網頁中的問題和答案
  292. let templateContent = document.getElementById('question-popup').innerHTML;
  293. let tempDiv = document.createElement('div');
  294. tempDiv.innerHTML = templateContent;
  295.  
  296. let questions = tempDiv.querySelectorAll('.fuli-option[data-question]');
  297. let questionNumbers = new Set();
  298. questions.forEach(question => {
  299. questionNumbers.add(question.getAttribute('data-question'));
  300. });
  301.  
  302. let answers = [];
  303. questionNumbers.forEach(questionNumber => {
  304. let firstOption = tempDiv.querySelector(`.fuli-option[data-question="${questionNumber}"]`);
  305. answers.push(firstOption.getAttribute('data-answer'));
  306. });
  307.  
  308.  
  309. // 獲取sn
  310. var urlParams = new URLSearchParams(window.location.search);
  311. var snValue = urlParams.get('sn');
  312.  
  313. // 準備payload
  314. var formData = new FormData();
  315. formData.append('sn', snValue);
  316. formData.append('token', csrfToken);
  317. answers.forEach(answer => formData.append('answer[]', answer));
  318.  
  319. // 這一段只是在看payload而已...
  320. var object = {};
  321. formData.forEach((value, key) => {
  322. if(!Reflect.has(object, key)){
  323. object[key] = value;
  324. return;
  325. }
  326. if(!Array.isArray(object[key])){
  327. object[key] = [object[key]];
  328. }
  329. object[key].push(value);
  330. });
  331. var payloadData = JSON.stringify(object);
  332.  
  333. // 發送post
  334. GM_xmlhttpRequest({
  335. method: "POST",
  336. url: "https://fuli.gamer.com.tw/ajax/answer_question.php",
  337. data: formData,
  338. onload: function(response) {
  339. console.log('Payload sent:', payloadData);
  340. console.log('回答問題post的回應:', response.responseText);
  341. }
  342. });
  343. }
  344.  
  345.  
  346.  
  347.  
  348. // 發送已看完廣告的post請求
  349. function sendPostRequest(csrfToken) {
  350. var urlParams = new URLSearchParams(window.location.search);
  351. var snValue = urlParams.get('sn');
  352. var adButton = document.querySelector('a[onclick^="window.FuliAd.checkAd"]');
  353. console.log('sn:',encodeURIComponent(snValue))
  354.  
  355. if (!snValue) {
  356. console.log('無法獲取sn參數');
  357. return;
  358. }
  359.  
  360. GM_xmlhttpRequest({
  361. method: "POST",
  362. url: "https://fuli.gamer.com.tw/ajax/finish_ad.php",
  363. headers: {
  364. "Content-Type": "application/x-www-form-urlencoded"
  365. },
  366. data: "token=" + encodeURIComponent(csrfToken) + "&area=item&sn=" + encodeURIComponent(snValue),
  367. onload: function(response) {
  368. console.log('post回應:', response.responseText);
  369. adButton.click();
  370. }
  371. });
  372. }
  373.  
  374. // 發送get檢查是否已經看過廣告
  375. function watchAdCheck() {
  376. var urlParams = new URLSearchParams(window.location.search);
  377. var snValue = urlParams.get('sn');
  378. var adButton = document.querySelector('a[onclick^="window.FuliAd.checkAd"]');
  379.  
  380. if (!snValue) {
  381. console.log('無法獲取sn參數');
  382. return;
  383. }
  384.  
  385. GM_xmlhttpRequest({
  386. method: "GET",
  387. url: "https://fuli.gamer.com.tw/ajax/check_ad.php?area=item&sn=" + encodeURIComponent(snValue),
  388. onload: function(response) {
  389. try {
  390. var responseData = JSON.parse(response.responseText);
  391. if (responseData.data && responseData.data.finished === 1) {
  392. alert('你已經看過/跳過廣告了!');
  393. adButton.click();
  394. return;
  395. }else{
  396. clickAdButton();
  397. }
  398. } catch (e) {
  399. console.error('解析回應時發生錯誤:', e);
  400. }
  401. }
  402. });
  403. }
  404.  
  405. // 自動點擊"看廣告免費兌換"按鈕
  406. function clickAdButton() {
  407. var adButton = document.querySelector('a[onclick^="window.FuliAd.checkAd"]');
  408. if (adButton) {
  409. adButton.click();
  410. startObservingDialog();
  411. }
  412. }
  413.  
  414. // 監視對話框
  415. function startObservingDialog() {
  416. var observerOptions = {
  417. childList: true,
  418. subtree: true
  419. };
  420.  
  421. observer = new MutationObserver(function(mutations, obs) {
  422. var dialog = document.querySelector('.dialogify__content');
  423. if (dialog) {
  424. var confirmButton = dialog.querySelector('.btn-box .btn-insert.btn-primary');
  425. if (confirmButton) {
  426. confirmButton.disabled = true;
  427. confirmButton.style.backgroundColor = '#e5e5e5';
  428. }
  429.  
  430. setTimeout(function() {
  431. handleDialogButtons(dialog);
  432. obs.disconnect();
  433. }, 500);
  434. }
  435. });
  436.  
  437. observer.observe(document.body, observerOptions);
  438. }
  439.  
  440. // 處理確認窗口的按鈕
  441. function handleDialogButtons(dialog) {
  442. var cancelButton = dialog.querySelector('.btn-box .btn-insert:not(.btn-primary)');
  443. var confirmButton = dialog.querySelector('.btn-box .btn-insert.btn-primary');
  444.  
  445. if (cancelButton) {
  446. setTimeout(function() {
  447. cancelButton.click();
  448. if (confirmButton) {
  449. confirmButton.disabled = false;
  450. confirmButton.style.backgroundColor = '';
  451. }
  452. }, 1000);
  453. }
  454. }
  455. })();