Giveaway Helper

Enhances Steam key-related giveaways

  1. // ==UserScript==
  2. // @name Giveaway Helper
  3. // @namespace https://github.com/Citrinate/giveawayHelper
  4. // @description Enhances Steam key-related giveaways
  5. // @author Citrinate
  6. // @version 2.12.9
  7. // @match *://*.chubbykeys.com/giveaway.php*
  8. // @match *://*.bananagiveaway.com/giveaway/*
  9. // @match *://*.dogebundle.com/index.php?page=redeem&id=*
  10. // @match *://*.dupedornot.com/giveaway*
  11. // @match *://*.embloo.net/task/*
  12. // @match *://*.gamecode.win/giveaway/*
  13. // @match *://*.gamehag.com/giveaway/*
  14. // @match *://*.gleam.io/*
  15. // @match *://*.grabfreegame.com/giveaway/*
  16. // @match *://*.hrkgame.com/en/giveaway/get-free-game/
  17. // @match *://*.keychampions.net/view.php?gid=*
  18. // @match *://*.marvelousga.com/giveaway/*
  19. // @match *://*.prys.ga/giveaway/?id=*
  20. // @match *://*.simplo.gg/index.php?giveaway=*
  21. // @match *://*.steamfriends.info/free-steam-key/
  22. // @match *://*.treasuregiveaways.com/*.php*
  23. // @match *://*.whosgamingnow.net/giveaway/*
  24. // @connect steamcommunity.com
  25. // @connect steampowered.com
  26. // @connect twitter.com
  27. // @connect twitch.tv
  28. // @match https://syndication.twitter.com/
  29. // @match https://player.twitch.tv/
  30. // @grant GM_getValue
  31. // @grant GM.getValue
  32. // @grant GM_setValue
  33. // @grant GM.setValue
  34. // @grant GM_deleteValue
  35. // @grant GM.deleteValue
  36. // @grant GM_addStyle
  37. // @grant GM_xmlhttpRequest
  38. // @grant GM.xmlHttpRequest
  39. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
  40. // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js
  41. // @run-at document-end
  42. // ==/UserScript==
  43.  
  44. (function() {
  45.  
  46. /**
  47. *
  48. */
  49. var setup = (function() {
  50. return {
  51. /**
  52. * Determine what to do for this page based on what's defined in the "config" variable
  53. *
  54. * hostname: A string
  55. * The hostname of the site we're setting the config for. Must be the same as what's defined
  56. * as @match in the metadata block above.
  57. *
  58. * helper: An object
  59. * The class which will determine how the do/undo buttons are added to the page. Usually this will
  60. * be set to basicHelper, which simply searches for links to Steam Groups and adds buttons for
  61. * them at the top of the page.
  62. *
  63. * domMatch: An array of strings
  64. * In some cases, we don't know what page a giveaway will be on. For example, Indiegala embeds
  65. * giveaways on various parts of their site which they want to attract attention to. Instead we
  66. * need to search the page for a DOM element that only appears when there is a giveaway on that
  67. * page. If any of the elements in this array match, then the script will be run on this page.
  68. *
  69. * urlMatch: An array of regular expressions
  70. * Used in conjunction with domMatch. Used for pages on the domain that we do know are relevant
  71. * to giveaways, and we always want to run the script on. For example, the giveaway confirmation
  72. * page on Indiegala. The regular expressions will be tested against the url of the pages, and if
  73. * any of them match, the script will be run on this page.
  74. *
  75. * cache: Boolean
  76. * For use with basicHelper. Some sites will remove links to Steam groups after the entry has
  77. * been completed. Set this to true so that any groups we find will be saved and presented later.
  78. *
  79. * offset: Array of integers
  80. * For use with basicHelper. Used to correct instances where the script's UI blocks parts of a
  81. * site. Offsets the UI by X number of pixels in the order of [top, left, right].
  82. * Directions that shouldn't be offset should be set to 0.
  83. *
  84. * zIndex: Integer
  85. * For use with basicHelper. Used to correct instances where the site's UI might overlay the
  86. * the script's UI and will be blocked by it.
  87. *
  88. * requires: An object: {twitch: Boolean}
  89. * For use with basicHelper. Some sites may have links asking you to follow a twitch channel, but
  90. * don't verify that you've done so. In these cases there's no need to display a "follow/unfollow"
  91. * button. For sites that do verify, set the value to true.
  92. *
  93. * redirect_urls: A function which returns a jQuery object
  94. * For use with basicHelper. Used on sites which may hide URLs behind a redirection link.
  95. * The jQuery object should contain the anchors that contain these links, and should be specific
  96. * enough so that it only contains links we know must be resolved.
  97. *
  98. * redirect_url_extract: A function which returns a string
  99. * For use with basicHelper and redirect_urls. Used in instances where redirections are used, but
  100. * the links can't be found within anchors. This function is used to extract the url from whatever
  101. * elements the redirect_urls function returns.
  102. *
  103. * onLoad: A function
  104. * For use with basicHelper. A function that executes after the page loads.
  105. *
  106. */
  107. run: function() {
  108. var found = false,
  109. config = [
  110. {
  111. hostname: "chubbykeys.com",
  112. helper: basicHelper,
  113. cache: false
  114. },
  115. {
  116. hostname: "bananagiveaway.com",
  117. helper: basicHelper,
  118. cache: true,
  119. redirect_urls: function() {
  120. return $("li:contains('Join')")
  121. .find("button:nth-child(1)");
  122. },
  123. redirect_url_extract: function(element) {
  124. return element.attr("onclick").replace("window.open('", "").replace("')", "");
  125. }
  126. },
  127. {
  128. hostname: "dogebundle.com",
  129. helper: basicHelper,
  130. cache: true,
  131. offset: [50, 0, 0]
  132. },
  133. {
  134. hostname: "dupedornot.com",
  135. helper: basicHelper,
  136. cache: false,
  137. requires: {twitch: true}
  138. },
  139. {
  140. hostname: "embloo.net",
  141. helper: basicHelper,
  142. cache: true
  143. },
  144. {
  145. hostname: "gamecode.win",
  146. helper: basicHelper,
  147. cache: true,
  148. requires: {twitch: true}
  149. },
  150. {
  151. hostname: "gamehag.com",
  152. helper: basicHelper,
  153. cache: true,
  154. offset: [80, 0, 300],
  155. zIndex: 80,
  156. redirect_urls: function() {
  157. return $(".element-list .task-content:contains('Steam Community group')")
  158. .find("a[href*='/giveaway/click/']");
  159. }
  160. },
  161. {
  162. hostname: "gleam.io",
  163. helper: gleamHelper,
  164. cache: false
  165. },
  166. {
  167. hostname: "grabfreegame.com",
  168. helper: basicHelper,
  169. cache: true,
  170. offset: [56, 0, 0],
  171. redirect_urls: function() {
  172. return $("li p:contains('Steam Group')").parent()
  173. .find("button:contains('To do')");
  174. },
  175. redirect_url_extract: function(element) {
  176. return element.attr("onclick").replace("window.open('", "").replace("')", "");
  177. }
  178. },
  179. {
  180. hostname: "hrkgame.com",
  181. helper: basicHelper,
  182. cache: false
  183. },
  184. {
  185. hostname: "keychampions.net",
  186. helper: basicHelper,
  187. cache: true,
  188. offset: [0, 120, 0]
  189. },
  190. {
  191. hostname: "marvelousga.com",
  192. helper: basicHelper,
  193. cache: false,
  194. zIndex: 1,
  195. requires: {twitch: true}
  196. },
  197. {
  198. hostname: "prys.ga",
  199. helper: basicHelper,
  200. cache: false,
  201. offset: [50, 0, 0],
  202. zIndex: 1029
  203. },
  204. {
  205. hostname: "simplo.gg",
  206. helper: basicHelper,
  207. cache: true
  208. },
  209. {
  210. hostname: "steamfriends.info",
  211. helper: basicHelper,
  212. cache: false
  213. },
  214. {
  215. hostname: "treasuregiveaways.com",
  216. helper: basicHelper,
  217. cache: true,
  218. offset: [50, 0, 0],
  219. zIndex: 1029
  220. },
  221. {
  222. hostname: "whosgamingnow.net",
  223. helper: basicHelper,
  224. cache: true
  225. }
  226. ];
  227.  
  228. for(var i = 0; i < config.length; i++) {
  229. var site = config[i];
  230.  
  231. if(document.location.hostname.split(".").splice(-2).join(".") == site.hostname) {
  232. found = true;
  233.  
  234. // determine whether to run the script based on the content of the page
  235. if(typeof site.domMatch !== "undefined" ||
  236. typeof site.urlMatch !== "undefined"
  237. ) {
  238. var match_found = false;
  239.  
  240. // check the DOM for matches as defined by domMatch
  241. if(typeof site.domMatch !== "undefined") {
  242. for(var k = 0; k < site.domMatch.length; k++) {
  243. if($(site.domMatch[k]).length !== 0) {
  244. match_found = true;
  245. break;
  246. }
  247. }
  248. }
  249.  
  250. // check the URL for matches as defined by urlMatch
  251. if(typeof site.urlMatch !== "undefined") {
  252. for(var l = 0; l < site.urlMatch.length; l++) {
  253. var reg = new RegExp(site.urlMatch[l]);
  254.  
  255. if(reg.test(location.href)) {
  256. match_found = true;
  257. break;
  258. }
  259. }
  260. }
  261.  
  262. if(!match_found) break;
  263. }
  264.  
  265. giveawayHelperUI.loadUI(site.zIndex, site.onLoad);
  266. site.helper.init(site.cache, site.cache_id, site.offset, site.requires, site.redirect_urls,
  267. site.redirect_url_extract);
  268. }
  269. }
  270.  
  271. if(!found) {
  272. commandHub.init();
  273. }
  274. }
  275. };
  276. })();
  277.  
  278. /**
  279. *
  280. */
  281. var gleamHelper = (function() {
  282. var gleam = null,
  283. authentications = { steam: false, twitter: false, twitch: false };
  284.  
  285. /**
  286. * Check to see what accounts the user has linked to gleam
  287. */
  288. function checkAuthentications() {
  289. if(gleam.contestantState.contestant.authentications) {
  290. var authentication_data = gleam.contestantState.contestant.authentications;
  291.  
  292. for(var i = 0; i < authentication_data.length; i++) {
  293. var current_authentication = authentication_data[i];
  294. authentications[current_authentication.provider == "twitchtv" ? "twitch" : current_authentication.provider] = current_authentication;
  295. }
  296. }
  297. }
  298.  
  299. /**
  300. * Decide what to do for each of the entries
  301. */
  302. function handleEntries() {
  303. var entries = $(".entry-method");
  304.  
  305. for(var i = 0; i < entries.length; i++) {
  306. var entry_element = entries[i],
  307. entry = unsafeWindow.angular.element(entry_element).scope();
  308.  
  309. switch(entry.entry_method.entry_type) {
  310. case "steam_join_group":
  311. createSteamButton(entry, entry_element);
  312. break;
  313.  
  314. case "twitter_follow":
  315. case "twitter_retweet":
  316. case "twitter_tweet":
  317. case "twitter_hashtags":
  318. //createTwitterButton(entry, entry_element);
  319. break;
  320.  
  321. case "twitchtv_follow":
  322. createTwitchButton(entry, entry_element);
  323. break;
  324.  
  325. default:
  326. break;
  327. }
  328. }
  329. }
  330.  
  331. /**
  332. *
  333. */
  334. function handleReward() {
  335. var temp_interval = setInterval(function() {
  336. if(gleam.bestCouponCode() !== null) {
  337. clearInterval(temp_interval);
  338. SteamHandler.getInstance().findKeys(addRedeemButton, gleam.bestCouponCode(), false);
  339. }
  340. }, 100);
  341. }
  342.  
  343. /**
  344. * Places the button onto the page
  345. */
  346. function addButton(entry_element) {
  347. return function(new_button) {
  348. new_button.addClass("btn btn-embossed btn-info");
  349. $(entry_element).find(">a").first().append(new_button);
  350. };
  351. }
  352.  
  353. /**
  354. *
  355. */
  356. function addRedeemButton(new_button) {
  357. new_button.find("button").first().addClass("btn btn-embossed btn-success");
  358. $(".redeem-container").first().after(new_button);
  359. }
  360.  
  361. /**
  362. * Returns true when an entry has been completed
  363. */
  364. function isCompleted(entry) {
  365. return function() {
  366. return gleam.isEntered(entry.entry_method) && !gleam.canEnter(entry.entry_method);
  367. };
  368. }
  369.  
  370. /**
  371. *
  372. */
  373. function createSteamButton(entry, entry_element) {
  374. SteamHandler.getInstance().handleEntry({
  375. group_name: entry.entry_method.config3.toLowerCase(),
  376. group_id: entry.entry_method.config4
  377. },
  378. addButton(entry_element),
  379. false,
  380. authentications.steam === false ? false : {
  381. user_id: authentications.steam.uid
  382. }
  383. );
  384. }
  385.  
  386. /**
  387. *
  388. */
  389. function createTwitterButton(entry, entry_element) {
  390. // Don't do anything for a tweet entry that's already been completed
  391. if(isCompleted(entry)() &&
  392. (entry.entry_method.entry_type == "twitter_tweet" ||
  393. entry.entry_method.entry_type == "twitter_hashtags")) {
  394.  
  395. return;
  396. }
  397.  
  398. TwitterHandler.getInstance().handleEntry({
  399. action: entry.entry_method.entry_type,
  400. id: entry.entry_method.config1
  401. },
  402. addButton(entry_element),
  403. isCompleted(entry),
  404. false,
  405. authentications.twitter === false ? false : {
  406. user_id: authentications.twitter.uid,
  407. user_handle: authentications.twitter.reference
  408. }
  409. );
  410. }
  411.  
  412. /**
  413. *
  414. */
  415. function createTwitchButton(entry, entry_element) {
  416. TwitchHandler.getInstance().handleEntry(
  417. entry.entry_method.config1,
  418. addButton(entry_element),
  419. isCompleted(entry),
  420. false,
  421. authentications.twitch === false ? false : {
  422. user_handle: authentications.twitch.reference
  423. }
  424. );
  425. }
  426.  
  427. return {
  428. /**
  429. *
  430. */
  431. init: function() {
  432. MKY.addStyle(`
  433. .${giveawayHelperUI.gh_button} {
  434. bottom: 0px;
  435. height: 32px;
  436. margin: auto;
  437. padding: 6px;
  438. position: absolute;
  439. right: 64px;
  440. top: 0px;
  441. z-index: 9999999999;
  442. }
  443.  
  444. .${giveawayHelperUI.gh_redeem_button} {
  445. margin-bottom: 32px;
  446. position: static;
  447. }
  448. `);
  449.  
  450. // Show exact end date when hovering over any times
  451. $("[data-ends]").each(function() {
  452. $(this).attr("title", new Date(parseInt($(this).attr("data-ends")) * 1000));
  453. });
  454.  
  455. // wait for gleam to finish loading
  456. var temp_interval = setInterval(function() {
  457. if($(".popup-blocks-container") !== null) {
  458. clearInterval(temp_interval);
  459. gleam = unsafeWindow.angular.element($(".popup-blocks-container").get(0)).scope();
  460.  
  461. // wait for gleam to fully finish loading
  462. var another_temp_interval = setInterval(function() {
  463. if(typeof gleam.campaign.entry_count !== "undefined") {
  464. clearInterval(another_temp_interval);
  465. checkAuthentications();
  466. handleReward();
  467.  
  468. if(!gleam.showPromotionEnded()) {
  469. handleEntries();
  470. }
  471. }
  472. }, 100);
  473. }
  474. }, 100);
  475. }
  476. };
  477. })();
  478.  
  479. /**
  480. *
  481. */
  482. var basicHelper = (function() {
  483. return {
  484. /**
  485. *
  486. */
  487. init: function(do_cache, cache_id, offset, requires, redirect_urls, redirect_url_extract) {
  488. if(typeof do_cache !== "undefined" && do_cache) {
  489. if(typeof cache_id === "undefined") {
  490. cache_id = document.location.hostname + document.location.pathname + document.location.search;
  491. }
  492.  
  493. cache_id = `cache_${CryptoJS.MD5(cache_id)}`;
  494. } else {
  495. do_cache = false;
  496. }
  497.  
  498. giveawayHelperUI.defaultButtonSetup(offset);
  499.  
  500. // Some sites load the giveaway data dynamically. Check every second for changes
  501. setInterval(function() {
  502. // Add Steam buttons
  503. SteamHandler.getInstance().findGroups(
  504. giveawayHelperUI.addButton,
  505. $("body").html(),
  506. true,
  507. do_cache,
  508. cache_id
  509. );
  510.  
  511. // Add Steam Key redeem buttons
  512. SteamHandler.getInstance().findKeys(giveawayHelperUI.addButton, $("body").html(), true);
  513.  
  514. if(typeof requires !== "undefined") {
  515. if(typeof requires.twitch !== "undefined" && requires.twitch === true) {
  516. // Add Twitch buttons
  517. TwitchHandler.getInstance().findChannels(
  518. giveawayHelperUI.addButton,
  519. $("body").html(),
  520. true,
  521. do_cache,
  522. `twitch_${cache_id}`
  523. );
  524. }
  525.  
  526. if(typeof requires.steam_curators !== "undefined" && requires.steam_curators === true) {
  527. // Add Steam Curator buttons
  528. SteamCuratorHandler.getInstance().findCurators(
  529. giveawayHelperUI.addButton,
  530. $("body").html(),
  531. true,
  532. do_cache,
  533. `steam_curators_${cache_id}`
  534. );
  535. }
  536. }
  537.  
  538. // Check for redirects
  539. if(typeof redirect_urls !== "undefined") {
  540. redirect_urls().each(function() {
  541. var redirect_url;
  542.  
  543. if(typeof redirect_url_extract !== "undefined") {
  544. redirect_url = redirect_url_extract($(this));
  545. } else {
  546. redirect_url = $(this).attr("href");
  547. }
  548.  
  549. giveawayHelperUI.resolveUrl(redirect_url, function(url) {
  550. // Add Steam button
  551. SteamHandler.getInstance().findGroups(
  552. giveawayHelperUI.addButton,
  553. url,
  554. true,
  555. do_cache,
  556. cache_id
  557. );
  558.  
  559. if(typeof requires !== "undefined") {
  560. if(typeof requires.twitch !== "undefined" && requires.twitch === true) {
  561. // Add Twitch button
  562. TwitchHandler.getInstance().findChannels(
  563. giveawayHelperUI.addButton,
  564. url,
  565. true,
  566. do_cache,
  567. `twitch_${cache_id}`
  568. );
  569. }
  570.  
  571. if(typeof requires.steam_curators !== "undefined" && requires.steam_curators === true) {
  572. // Steam Curator buttons
  573. SteamCuratorHandler.getInstance().findCurators(
  574. giveawayHelperUI.addButton,
  575. url,
  576. true,
  577. do_cache,
  578. `steam_curators_${cache_id}`
  579. );
  580. }
  581. }
  582. });
  583. });
  584. }
  585. }, 1000);
  586. },
  587. };
  588. })();
  589.  
  590. /**
  591. * Handles Steam group buttons
  592. */
  593. var SteamHandler = (function() {
  594. function init() {
  595. var re_group_name = /steamcommunity\.com\/groups\/([a-zA-Z0-9\-\_]{2,32})/g,
  596. re_group_id = /steamcommunity.com\/gid\/(([0-9]+)|\[g:[0-9]:([0-9]+)\])/g,
  597. re_steam_key = /([A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}|[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5})/g,
  598. redeem_key_url = "https://store.steampowered.com/account/registerkey?key=",
  599. user_id = null,
  600. session_id = null,
  601. process_url = null,
  602. active_groups = [],
  603. button_count = 1,
  604. handled_group_names = [],
  605. handled_group_ids = [],
  606. handled_keys = [],
  607. ready = false;
  608.  
  609. // Get all the user data we'll need to make join/leave group requests
  610. MKY.xmlHttpRequest({
  611. url: "https://steamcommunity.com/my/groups",
  612. method: "GET",
  613. onload: function(response) {
  614. user_id = response.responseText.match(/g_steamID = \"(.+?)\";/);
  615. session_id = response.responseText.match(/g_sessionID = \"(.+?)\";/);
  616. process_url = response.responseText.match(/steamcommunity.com\/(id\/.+?|profiles\/[0-9]+)\/friends\//);
  617. user_id = user_id === null ? null : user_id[1];
  618. session_id = session_id === null ? null : session_id[1];
  619. process_url = process_url === null ? null : "https://steamcommunity.com/" + process_url[1] + "/home_process";
  620.  
  621. $(response.responseText).find("a[href^='https://steamcommunity.com/groups/']").each(function() {
  622. var group_name = $(this).attr("href").replace("https://steamcommunity.com/groups/", "");
  623.  
  624. if(group_name.indexOf("/") == -1) {
  625. active_groups.push(group_name.toLowerCase());
  626. }
  627. });
  628.  
  629. active_groups = giveawayHelperUI.removeDuplicates(active_groups);
  630. ready = true;
  631. }
  632. });
  633.  
  634. function verifyLogin(expected_user) {
  635. if(typeof expected_user !== "undefined" && !expected_user) {
  636. // The user doesn't have a Steam account linked, do nothing
  637. } else if(user_id === null || session_id === null || process_url === null) {
  638. // We're not logged in
  639. giveawayHelperUI.showError(`You must be logged into
  640. <a href="https://steamcommunity.com/login" target="_blank">steamcommunity.com</a>`);
  641. } else if(typeof expected_user !== "undefined" && expected_user.user_id != user_id) {
  642. // We're logged in as the wrong user
  643. giveawayHelperUI.showError(`You must be logged into the linked Steam account:
  644. <a href="https://steamcommunity.com/profiles/${expected_user.user_id}" target="_blank">
  645. https://steamcommunity.com/profiles/${expected_user.user_id}</a>`);
  646. } else if(active_groups === null) {
  647. // Couldn't get user's group data
  648. giveawayHelperUI.showError("Unable to determine what Steam groups you're a member of");
  649. } else {
  650. return true;
  651. }
  652.  
  653. return false;
  654. }
  655.  
  656. /**
  657. *
  658. */
  659. function prepCreateButton(group_data, button_callback, show_name, expected_user) {
  660. if(typeof group_data.group_id == "undefined") {
  661. // Group ID is missing
  662. getGroupID(group_data.group_name, function(group_id) {
  663. group_data.group_id = group_id;
  664. createButton(group_data, button_callback, show_name, expected_user);
  665. });
  666. } else if(typeof group_data.group_name == "undefined") {
  667. // Group name is missing
  668. getGroupName(group_data.group_id, function(group_name) {
  669. group_data.group_name = group_name;
  670.  
  671. // Fetch a separate numeric group id that we'll need
  672. getGroupID(group_data.group_name, function(group_id) {
  673. group_data.group_id = group_id;
  674. createButton(group_data, button_callback, show_name, expected_user);
  675. });
  676. });
  677. } else {
  678. createButton(group_data, button_callback, show_name, expected_user);
  679. }
  680. }
  681.  
  682. /**
  683. * Create a join/leave toggle button
  684. */
  685. function createButton(group_data, button_callback, show_name, expected_user) {
  686. if(verifyLogin(expected_user)) {
  687. // Create the button
  688. var group_name = group_data.group_name,
  689. group_id = group_data.group_id,
  690. in_group = active_groups.indexOf(group_name) != -1,
  691. button_id = "steam_button_" + button_count++,
  692. label = in_group ?
  693. `Leave ${show_name ? group_name : "Group"}`
  694. : `Join ${show_name ? group_name : "Group"}`;
  695.  
  696. button_callback(
  697. giveawayHelperUI.buildButton(button_id, label, in_group, function() {
  698. toggleGroupStatus(button_id, group_name, group_id, show_name);
  699. giveawayHelperUI.showButtonLoading(button_id);
  700. })
  701. );
  702. }
  703. }
  704.  
  705.  
  706. /**
  707. * Toggle group status between "joined" and "left"
  708. */
  709. function toggleGroupStatus(button_id, group_name, group_id, show_name) {
  710. var steam_community_down_error = `
  711. The Steam Community is experiencing issues. Please handle any remaining Steam entries manually, or reload the page and try again.
  712. `;
  713.  
  714. if(active_groups.indexOf(group_name) == -1) {
  715. joinSteamGroup(group_name, group_id, function(success) {
  716. if(success) {
  717. active_groups.push(group_name);
  718. giveawayHelperUI.toggleButtonClass(button_id);
  719. giveawayHelperUI.setButtonLabel(button_id, `Leave ${show_name ? group_name : "Group"}`);
  720. } else {
  721. giveawayHelperUI.showError(steam_community_down_error);
  722. }
  723.  
  724. giveawayHelperUI.hideButtonLoading(button_id);
  725. });
  726. } else {
  727. leaveSteamGroup(group_name, group_id, function(success) {
  728. if(success) {
  729. active_groups.splice(active_groups.indexOf(group_name), 1);
  730. giveawayHelperUI.toggleButtonClass(button_id);
  731. giveawayHelperUI.setButtonLabel(button_id, `Join ${show_name ? group_name : "Group"}`);
  732. } else {
  733. giveawayHelperUI.showError(steam_community_down_error);
  734. }
  735.  
  736. giveawayHelperUI.hideButtonLoading(button_id);
  737. });
  738. }
  739. }
  740.  
  741. /**
  742. * Join a steam group
  743. */
  744. function joinSteamGroup(group_name, group_id, callback) {
  745. MKY.xmlHttpRequest({
  746. url: "https://steamcommunity.com/groups/" + group_name,
  747. method: "POST",
  748. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  749. data: $.param({ action: "join", sessionID: session_id }),
  750. onload: function(response) {
  751. MKY.xmlHttpRequest({
  752. url: "https://steamcommunity.com/my/groups",
  753. method: "GET",
  754. onload: function(response) {
  755. if(typeof callback == "function") {
  756. if($(response.responseText.toLowerCase()).find(
  757. `a[href='https://steamcommunity.com/groups/${group_name}']`).length === 0) {
  758.  
  759. // Failed to join the group, Steam Community is probably down
  760. callback(false);
  761. } else {
  762. callback(true);
  763. }
  764. }
  765. }
  766. });
  767. }
  768. });
  769. }
  770.  
  771. /**
  772. * Leave a steam group
  773. */
  774. function leaveSteamGroup(group_name, group_id, callback) {
  775. MKY.xmlHttpRequest({
  776. url: process_url,
  777. method: "POST",
  778. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  779. data: $.param({ sessionID: session_id, action: "leaveGroup", groupId: group_id }),
  780. onload: function(response) {
  781. if(typeof callback == "function") {
  782. if($(response.responseText.toLowerCase()).find(
  783. `a[href='https://steamcommunity.com/groups/${group_name}']`).length !== 0) {
  784.  
  785. // Failed to leave the group, Steam Community is probably down
  786. callback(false);
  787. } else {
  788. callback(true);
  789. }
  790. }
  791. }
  792. });
  793. }
  794.  
  795. /**
  796. * Get the numeric ID for a Steam group
  797. */
  798. function getGroupID(group_name, callback) {
  799. MKY.xmlHttpRequest({
  800. url: "https://steamcommunity.com/groups/" + group_name,
  801. method: "GET",
  802. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  803. onload: function(response) {
  804. var group_id = response.responseText.match(/OpenGroupChat\( \'([0-9]+)\'/);
  805. group_id = group_id === null ? null : group_id[1];
  806.  
  807. callback(group_id);
  808. }
  809. });
  810. }
  811.  
  812. /**
  813. * Get the name for a Steam group given the numeric ID
  814. */
  815. function getGroupName(group_id, callback) {
  816. MKY.xmlHttpRequest({
  817. url: "https://steamcommunity.com/gid/" + group_id,
  818. method: "GET",
  819. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  820. onload: function(response) {
  821. var group_name = response.finalUrl.match(/steamcommunity\.com\/groups\/([a-zA-Z0-9\-\_]{2,32})/);
  822. group_name = group_name === null ? null : group_name[1];
  823.  
  824. callback(group_name.toLowerCase());
  825. }
  826. });
  827. }
  828.  
  829. return {
  830. /**
  831. *
  832. */
  833. handleEntry: function(group_data, button_callback, show_name, expected_user) {
  834. if(ready) {
  835. prepCreateButton(group_data, button_callback, show_name, expected_user);
  836. } else {
  837. // Wait for the command hub to load
  838. var temp_interval = setInterval(function() {
  839. if(ready) {
  840. clearInterval(temp_interval);
  841. prepCreateButton(group_data, button_callback, show_name, expected_user);
  842. }
  843. }, 100);
  844. }
  845. },
  846.  
  847. /**
  848. *
  849. */
  850. findGroups: function(button_callback, target, show_name, do_cache, cache_id) {
  851. var self = this;
  852.  
  853. giveawayHelperUI.restoreCachedLinks(cache_id).then(function(group_names) {
  854. giveawayHelperUI.restoreCachedLinks(cache_id + "_ids").then(function(group_ids) {
  855. var match;
  856.  
  857. if(!do_cache) {
  858. group_names = [];
  859. group_ids = [];
  860. }
  861.  
  862. // Look for any links containing steam group names
  863. while((match = re_group_name.exec(target)) !== null) {
  864. group_names.push(match[1].toLowerCase());
  865. }
  866.  
  867. // Look for any links containing steam group ids
  868. while((match = re_group_id.exec(target)) !== null) {
  869. if(typeof match[2] !== "undefined") {
  870. group_ids.push(match[2].toLowerCase());
  871. } else {
  872. group_ids.push(match[3].toLowerCase());
  873. }
  874. }
  875.  
  876. group_names = giveawayHelperUI.removeDuplicates(group_names);
  877. group_ids = giveawayHelperUI.removeDuplicates(group_ids);
  878.  
  879. // Cache the results
  880. if(do_cache) {
  881. giveawayHelperUI.cacheLinks(group_names, cache_id);
  882. giveawayHelperUI.cacheLinks(group_ids, cache_id + "_ids");
  883. }
  884.  
  885. // Create the buttons
  886. for(var i = 0; i < group_names.length; i++) {
  887. if($.inArray(group_names[i], handled_group_names) == -1) {
  888. handled_group_names.push(group_names[i]);
  889. self.handleEntry({ group_name: group_names[i] }, button_callback, show_name);
  890. }
  891. }
  892.  
  893. for(var j = 0; j < group_ids.length; j++) {
  894. if($.inArray(group_ids[i], handled_group_ids) == -1) {
  895. handled_group_ids.push(group_ids[i]);
  896. self.handleEntry({ group_id: group_ids[j] }, button_callback, show_name);
  897. }
  898. }
  899. });
  900. });
  901. },
  902.  
  903. /**
  904. *
  905. */
  906. findKeys: function(button_callback, target, show_key) {
  907. var keys = [],
  908. match;
  909.  
  910. while((match = re_steam_key.exec(target)) !== null) {
  911. keys.push(match[1]);
  912. }
  913.  
  914. for(var i = 0; i < keys.length; i++) {
  915. if($.inArray(keys[i], handled_keys) == -1) {
  916. var steam_key = keys[i],
  917. button_id = 'redeem_' + handled_keys.length,
  918. label = show_key ? `Redeem ${steam_key}` : "Redeem Key",
  919. redeem_url = `${redeem_key_url}${steam_key}`;
  920.  
  921. handled_keys.push(steam_key);
  922. button_callback(
  923. giveawayHelperUI.buildRedeemButton(button_id, label, redeem_url)
  924. );
  925. }
  926. }
  927. }
  928. };
  929. }
  930.  
  931. var instance;
  932. return {
  933. getInstance: function() {
  934. if(!instance) instance = init();
  935. return instance;
  936. }
  937. };
  938. })();
  939.  
  940.  
  941.  
  942. /**
  943. * Handles Steam curator buttons
  944. */
  945. var SteamCuratorHandler = (function() {
  946. function init() {
  947. var re_curator_id = /steampowered.com\/curator\/([0-9]+)/g,
  948. session_id = null,
  949. active_curators = [],
  950. button_count = 1,
  951. handled_curator_ids = [],
  952. ready = false;
  953. curator_ready_status = 0;
  954.  
  955. // Get all the user data we'll need to make follow/unfollow curator requests
  956. MKY.xmlHttpRequest({
  957. url: "https://store.steampowered.com",
  958. method: "GET",
  959. onload: function(response) {
  960. session_id = response.responseText.match(/g_sessionID = \"(.+?)\";/);
  961. session_id = session_id === null ? null : session_id[1];
  962.  
  963. MKY.xmlHttpRequest({
  964. url: "https://store.steampowered.com/curators/ajaxgetcurators//?query=&start=0&count=1000&filter=mycurators",
  965. method: "GET",
  966. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  967. onload: function(response) {
  968. var curator_urls_completed = 1;
  969.  
  970. try {
  971. var data = JSON.parse(response.responseText);
  972.  
  973. if(typeof data.success != "undefined" && typeof data.pagesize != "undefined" && typeof data.total_count != "undefined" && data.success == true) {
  974. parseActiveCurators(data);
  975.  
  976. for(var i = 1; i < Math.ceil(data.total_count/data.pagesize); i++) {
  977. setTimeout(function(page_num) {
  978. if(ready) return;
  979.  
  980. MKY.xmlHttpRequest({
  981. url: "https://store.steampowered.com/curators/ajaxgetcurators//?query=&start=" + (page_num * data.pagesize) + "&count=1000&filter=mycurators",
  982. method: "GET",
  983. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  984. onload: function(response) {
  985. try {
  986. var data = JSON.parse(response.responseText);
  987.  
  988. if(typeof data.success != "undefined" && data.success == true) {
  989. parseActiveCurators(data);
  990. } else {
  991. ready = true;
  992. active_curators = null;
  993. }
  994.  
  995. curator_urls_completed++;
  996. } catch(e) {
  997. ready = true;
  998. active_curators = null;
  999. }
  1000. }
  1001. });
  1002. }, i * 500, i);
  1003. }
  1004.  
  1005. var temp_interval = setInterval(function() {
  1006. if(curator_urls_completed >= Math.ceil(data.total_count/data.pagesize)) {
  1007. clearInterval(temp_interval);
  1008. ready = true;
  1009. }
  1010. }, 100)
  1011. }
  1012. } catch(e) {
  1013. ready = true;
  1014. active_curators = null;
  1015. }
  1016. }
  1017. });
  1018. }
  1019. });
  1020.  
  1021. function verifyLogin(expected_user) {
  1022. if(typeof expected_user !== "undefined" && !expected_user) {
  1023. // The user doesn't have a Steam account linked, do nothing
  1024. } else if(session_id === null) {
  1025. // We're not logged in
  1026. giveawayHelperUI.showError(`You must be logged into
  1027. <a href="https://steamcommunity.com/login" target="_blank">steamcommunity.com</a>`);
  1028. } else if(active_curators === null) {
  1029. // Couldn't get user's group data
  1030. giveawayHelperUI.showError("Unable to determine what Steam curators you're following");
  1031. } else {
  1032. return true;
  1033. }
  1034.  
  1035. return false;
  1036. }
  1037.  
  1038. /**
  1039. *
  1040. */
  1041. function parseActiveCurators(data) {
  1042. if(typeof data.results_html == "undefined") {
  1043. curator_ready_status = 2;
  1044. active_curators = null;
  1045. return;
  1046. }
  1047.  
  1048. var re_curator_results_id = /\"clanID\":\"([0-9]+)\"/g;
  1049.  
  1050. while((match = re_curator_results_id.exec(data.results_html)) !== null) {
  1051. active_curators.push(match[1]);
  1052. }
  1053.  
  1054. return;
  1055. }
  1056.  
  1057. /**
  1058. * Create a join/leave Curator toggle button
  1059. */
  1060. function createButton(curator_id, button_callback, show_name, expected_user) {
  1061. if(verifyLogin(expected_user)) {
  1062. // Create the button
  1063. var is_following = active_curators.indexOf(curator_id) != -1,
  1064. button_id = "steam_curator_button_" + button_count++,
  1065. label = is_following ?
  1066. `Unfollow ${show_name ? curator_id : "Curator"}`
  1067. : `Follow ${show_name ? curator_id : "Curator"}`;
  1068.  
  1069. button_callback(
  1070. giveawayHelperUI.buildButton(button_id, label, is_following, function() {
  1071. toggleCuratorStatus(button_id, curator_id, show_name);
  1072. giveawayHelperUI.showButtonLoading(button_id);
  1073. })
  1074. );
  1075. }
  1076. }
  1077.  
  1078.  
  1079. /**
  1080. * Toggle steam curator status between "following" and "not following"
  1081. */
  1082. function toggleCuratorStatus(button_id, curator_id, show_name) {
  1083. var steam_community_down_error = `
  1084. The Steam Community is experiencing issues. Please handle any remaining Steam entries manually, or reload the page and try again.
  1085. `;
  1086.  
  1087. if(active_curators.indexOf(curator_id) == -1) {
  1088. followCurator(curator_id, function(success) {
  1089. if(success) {
  1090. active_curators.push(curator_id);
  1091. giveawayHelperUI.toggleButtonClass(button_id);
  1092. giveawayHelperUI.setButtonLabel(button_id, `Unfollow ${show_name ? curator_id : "Curator"}`);
  1093. } else {
  1094. giveawayHelperUI.showError(steam_community_down_error);
  1095. }
  1096.  
  1097. giveawayHelperUI.hideButtonLoading(button_id);
  1098. });
  1099. } else {
  1100. unfollowCurator(curator_id, function(success) {
  1101. if(success) {
  1102. active_curators.splice(active_curators.indexOf(curator_id), 1);
  1103. giveawayHelperUI.toggleButtonClass(button_id);
  1104. giveawayHelperUI.setButtonLabel(button_id, `Follow ${show_name ? curator_id : "Curator"}`);
  1105. } else {
  1106. giveawayHelperUI.showError(steam_community_down_error);
  1107. }
  1108.  
  1109. giveawayHelperUI.hideButtonLoading(button_id);
  1110. });
  1111. }
  1112. }
  1113.  
  1114. /**
  1115. * Follow a steam curator
  1116. */
  1117. function followCurator(curator_id, callback) {
  1118. MKY.xmlHttpRequest({
  1119. url: "https://store.steampowered.com/curators/ajaxfollow",
  1120. method: "POST",
  1121. data: $.param({ clanid: curator_id, sessionid: session_id, follow: "1" }),
  1122. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  1123. onload: function(response) {
  1124. try {
  1125. var data = JSON.parse(response.responseText);
  1126.  
  1127. if(typeof data.success.success != "undefined" && data.success.success == 1) {
  1128. callback(true);
  1129. } else {
  1130. callback(false);
  1131. }
  1132. } catch(e) {
  1133. callback(false)
  1134. }
  1135. }
  1136. });
  1137. }
  1138.  
  1139. /**
  1140. * Unfollow a steam curator
  1141. */
  1142. function unfollowCurator(curator_id, callback) {
  1143. MKY.xmlHttpRequest({
  1144. url: "https://store.steampowered.com/curators/ajaxfollow",
  1145. method: "POST",
  1146. data: $.param({ clanid: curator_id, sessionid: session_id, follow: "0" }),
  1147. headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
  1148. onload: function(response) {
  1149. try {
  1150. var data = JSON.parse(response.responseText);
  1151.  
  1152. if(typeof data.success.success != "undefined" && data.success.success == 1) {
  1153. callback(true);
  1154. } else {
  1155. callback(false);
  1156. }
  1157. } catch(e) {
  1158. callback(false)
  1159. }
  1160. }
  1161. });
  1162. }
  1163.  
  1164. return {
  1165. /**
  1166. *
  1167. */
  1168. handleEntry: function(curator_id, button_callback, show_name, expected_user) {
  1169. if(ready) {
  1170. createButton(curator_id, button_callback, show_name, expected_user);
  1171. } else {
  1172. // Wait for the command hub to load
  1173. var temp_interval = setInterval(function() {
  1174. if(ready) {
  1175. clearInterval(temp_interval);
  1176. createButton(curator_id, button_callback, show_name, expected_user);
  1177. }
  1178. }, 100);
  1179. }
  1180. },
  1181.  
  1182. /**
  1183. *
  1184. */
  1185. findCurators: function(button_callback, target, show_name, do_cache, cache_id) {
  1186. var self = this;
  1187.  
  1188. giveawayHelperUI.restoreCachedLinks(cache_id).then(function(curator_ids) {
  1189. var match;
  1190.  
  1191. if(!do_cache) {
  1192. curator_ids = [];
  1193. }
  1194.  
  1195. // Look for any links containing steam curator ids
  1196. while((match = re_curator_id.exec(target)) !== null) {
  1197. curator_ids.push(match[1].toLowerCase());
  1198. }
  1199.  
  1200. curator_ids = giveawayHelperUI.removeDuplicates(curator_ids);
  1201.  
  1202. // Cache the results
  1203. if(do_cache) {
  1204. giveawayHelperUI.cacheLinks(curator_ids, cache_id);
  1205. }
  1206.  
  1207. // Create the buttons
  1208. for(var i = 0; i < curator_ids.length; i++) {
  1209. if($.inArray(curator_ids[i], handled_curator_ids) == -1) {
  1210. handled_curator_ids.push(curator_ids[i]);
  1211. self.handleEntry(curator_ids[i], button_callback, show_name);
  1212. }
  1213. }
  1214. });
  1215. }
  1216. };
  1217. }
  1218.  
  1219. var instance;
  1220. return {
  1221. getInstance: function() {
  1222. if(!instance) instance = init();
  1223. return instance;
  1224. }
  1225. };
  1226. })();
  1227.  
  1228. /**
  1229. * Handles Twitter undo buttons
  1230. */
  1231. var TwitterHandler = (function() {
  1232. function init() {
  1233. var command_hub_url = "https://syndication.twitter.com/",
  1234. command_hub_host = "syndication.twitter.com",
  1235. auth_token = null,
  1236. csrf_token = null,
  1237. user_handle = null,
  1238. user_id = null,
  1239. start_time = +new Date(),
  1240. deleted_tweets = [], // used to make sure we dont try to delete the same (re)tweet more than once
  1241. button_count = 1,
  1242. ready_a = false;
  1243. ready_b = false;
  1244.  
  1245. // Get all the user data we'll need to undo twitter entries
  1246. commandHub.load(
  1247. command_hub_url,
  1248. command_hub_host,
  1249. function() {
  1250. return {
  1251. csrf_token: getCookie("ct0")
  1252. };
  1253. },
  1254. function(data) {
  1255. csrf_token = data.csrf_token;
  1256. ready_a = true;
  1257. }
  1258. );
  1259.  
  1260. MKY.xmlHttpRequest({
  1261. url: "https://twitter.com",
  1262. method: "GET",
  1263. onload: function(response) {
  1264. auth_token = $($(response.responseText)
  1265. .find("input[id='authenticity_token']").get(0))
  1266. .attr("value");
  1267. user_handle = $(response.responseText)
  1268. .find(".current-user a")
  1269. .attr("href");
  1270. user_id = $(response.responseText)
  1271. .find("#current-user-id")
  1272. .attr("value");
  1273.  
  1274. auth_token = typeof auth_token == "undefined" ? null : auth_token;
  1275. user_handle = typeof user_handle == "undefined" ? null : user_handle.replace("/", "");
  1276. user_id = typeof user_id == "undefined" ? null : user_id;
  1277.  
  1278. ready_b = true;
  1279. }
  1280. });
  1281.  
  1282. /**
  1283. * Get ready to create an item
  1284. */
  1285. function prepCreateButton(action_data, button_callback, ready_check, show_name, expected_user) {
  1286. // Wait until the entry is completed before showing the button
  1287. var temp_interval = setInterval(function() {
  1288. if(ready_check()) {
  1289. clearInterval(temp_interval);
  1290. createButton(action_data, button_callback, show_name, expected_user, +new Date());
  1291. }
  1292. }, 100);
  1293. }
  1294.  
  1295. /**
  1296. * Create the button
  1297. */
  1298. function createButton(action_data, button_callback, show_name, expected_user, end_time) {
  1299. if(!expected_user) {
  1300. // The user doesn't have a Twitter account linked, do nothing
  1301. } else if(auth_token === null || user_handle === null || csrf_token === null) {
  1302. // We're not logged in
  1303. giveawayHelperUI.showError(`You must be logged into
  1304. <a href="https://twitter.com/login" target="_blank">twitter.com</a>`);
  1305. } else if(expected_user.user_id != user_id) {
  1306. // We're logged in as the wrong user
  1307. giveawayHelperUI.showError(`You must be logged into the Twitter account linked to Gleam.io:
  1308. <a href="https://twitter.com/${expected_user.user_handle}" target="_blank">
  1309. https://twitter.com/${expected_user.user_handle}</a>`);
  1310. } else {
  1311. // Create the button
  1312. var button_id = "twitter_button_" + button_count++;
  1313.  
  1314. if(action_data.action == "twitter_follow") {
  1315. // Unfollow button
  1316. var twitter_handle = action_data.id;
  1317.  
  1318. button_callback(
  1319. giveawayHelperUI.buildButton(button_id, `Unfollow${show_name ? ` ${twitter_handle}` : ""}`,
  1320. false,
  1321. function() {
  1322. giveawayHelperUI.removeButton(button_id);
  1323.  
  1324. // Get user's Twitter ID
  1325. getTwitterUserData(twitter_handle, function(twitter_id, is_following) {
  1326. deleteTwitterFollow(twitter_handle, twitter_id);
  1327. });
  1328. })
  1329. );
  1330. } else if(action_data.action == "twitter_retweet") {
  1331. // Delete Retweet button
  1332. button_callback(
  1333. giveawayHelperUI.buildButton(button_id, "Delete Retweet", false, function() {
  1334. giveawayHelperUI.removeButton(button_id);
  1335. deleteTwitterRetweet(action_data.id.match(/\/([0-9]+)/)[1]);
  1336. })
  1337. );
  1338. } else if(action_data.action == "twitter_tweet" || action_data.action == "twitter_hashtags") {
  1339. // Delete Tweet button
  1340. button_callback(
  1341. giveawayHelperUI.buildButton(button_id, "Delete Tweet", false, function() {
  1342. giveawayHelperUI.removeButton(button_id);
  1343.  
  1344. /* We don't have an id for the tweet, so instead delete the first tweet we can find
  1345. that was posted after we handled the entry, but before it was marked completed. */
  1346. getTwitterTweet(end_time, function(tweet_id) {
  1347. if(tweet_id === false) {
  1348. giveawayHelperUI.showError(`Failed to find
  1349. <a href="https://twitter.com/${user_handle}" target="_blank">Tweet</a>`);
  1350. } else {
  1351. deleteTwitterTweet(tweet_id);
  1352. }
  1353. });
  1354. })
  1355. );
  1356. }
  1357. }
  1358. }
  1359.  
  1360. /**
  1361. * @return {String} twitter_id - Twitter id for this handle
  1362. * @return {Boolean} is_following - True for "following", false for "not following"
  1363. */
  1364. function getTwitterUserData(twitter_handle, callback) {
  1365. MKY.xmlHttpRequest({
  1366. url: "https://twitter.com/" + twitter_handle,
  1367. method: "GET",
  1368. onload: function(response) {
  1369. var twitter_id = $($(response.responseText.toLowerCase()).find(
  1370. `[data-screen-name='${twitter_handle.toLowerCase()}'][data-user-id]`).get(0)).attr(
  1371. "data-user-id"),
  1372. is_following = $($(response.responseText.toLowerCase()).find(
  1373. `[data-screen-name='${twitter_handle.toLowerCase()}'][data-you-follow]`).get(0)).attr(
  1374. "data-you-follow");
  1375.  
  1376. if(typeof twitter_id !== "undefined" && typeof is_following !== "undefined") {
  1377. callback(twitter_id, is_following !== "false");
  1378. } else {
  1379. callback(null, null);
  1380. }
  1381. }
  1382. });
  1383. }
  1384.  
  1385. /**
  1386. * We don't have an id for the tweet, so instead delete the first tweet we can find
  1387. * that was posted after we handled the entry, but before it was marked completed.
  1388. *
  1389. * @param {Number} end_time - Unix timestamp in ms
  1390. * @return {Array|Boolean} tweet_id - The oldest (re)tweet id between start and end time, false if not found
  1391. */
  1392. function getTwitterTweet(end_time, callback) {
  1393. /* Tweets are instantly posted to our profile, but there's a delay before they're made
  1394. public (a few seconds). Increase the range by a few seconds to compensate. */
  1395. end_time += (60 * 1000);
  1396.  
  1397. MKY.xmlHttpRequest({
  1398. url: "https://twitter.com/" + user_handle,
  1399. method: "GET",
  1400. onload: function(response) {
  1401. var found_tweet = false,
  1402. now = +new Date();
  1403.  
  1404. // reverse the order so that we're looking at oldest to newest
  1405. $($(response.responseText.toLowerCase()).find(
  1406. `a[href*='${user_handle.toLowerCase()}/status/']`).get().reverse()).each(function() {
  1407.  
  1408. var tweet_time = $(this).find("span").attr("data-time-ms"),
  1409. tweet_id = $(this).attr("href").match(/\/([0-9]+)/);
  1410.  
  1411. if(typeof tweet_time != "undefined" && tweet_id !== null) {
  1412. if(deleted_tweets.indexOf(tweet_id[1]) == -1 &&
  1413. tweet_time > start_time &&
  1414. (tweet_time < end_time || tweet_time > now)) {
  1415.  
  1416. // return the first match
  1417. found_tweet = true;
  1418. deleted_tweets.push(tweet_id[1]);
  1419. callback(tweet_id[1]);
  1420. return false;
  1421. }
  1422. }
  1423. });
  1424.  
  1425. // couldn't find any tweets between the two times
  1426. if(!found_tweet) {
  1427. callback(false);
  1428. }
  1429. }
  1430. });
  1431. }
  1432.  
  1433. /**
  1434. * Unfollow a twitter user
  1435. */
  1436. function deleteTwitterFollow(twitter_handle, twitter_id) {
  1437. if(twitter_id === null) {
  1438. giveawayHelperUI.showError(`Failed to unfollow Twitter user:
  1439. <a href="https://twitter.com/${twitter_handle}" target="_blank">${twitter_handle}</a>`);
  1440. } else {
  1441. MKY.xmlHttpRequest({
  1442. url: "https://api.twitter.com/1.1/friendships/destroy.json",
  1443. method: "POST",
  1444. headers: {
  1445. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  1446. "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw",
  1447. "x-csrf-token": csrf_token,
  1448. },
  1449. data: $.param({ user_id: twitter_id }),
  1450. onload: function(response) {
  1451. if(response.status != 200) {
  1452. giveawayHelperUI.showError(`Failed to unfollow Twitter user:
  1453. <a href="https://twitter.com/${twitter_handle}" target="_blank">
  1454. ${twitter_handle}
  1455. </a>`);
  1456. }
  1457. }
  1458. });
  1459. }
  1460. }
  1461.  
  1462. /**
  1463. * Delete a tweet
  1464. * @param {Array} tweet_id - A single tweet ID
  1465. */
  1466. function deleteTwitterTweet(tweet_id) {
  1467. MKY.xmlHttpRequest({
  1468. url: "https://twitter.com/i/tweet/destroy",
  1469. method: "POST",
  1470. headers: {
  1471. "Origin": "https://twitter.com",
  1472. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  1473. },
  1474. data: $.param({ _method: "DELETE", authenticity_token: auth_token, id: tweet_id }),
  1475. onload: function(response) {
  1476. if(response.status != 200) {
  1477. giveawayHelperUI.showError(`Failed to delete
  1478. <a href="https://twitter.com/${user_handle}" target="_blank">Tweet}</a>`);
  1479. }
  1480. }
  1481. });
  1482. }
  1483.  
  1484. /**
  1485. * Delete a retweet
  1486. * @param {Array} tweet_id - A single retweet ID
  1487. */
  1488. function deleteTwitterRetweet(tweet_id) {
  1489. MKY.xmlHttpRequest({
  1490. url: "https://api.twitter.com/1.1/statuses/unretweet.json",
  1491. method: "POST",
  1492. headers: {
  1493. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  1494. "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw",
  1495. "x-csrf-token": csrf_token,
  1496. },
  1497. data: $.param({ _method: "DELETE", id: tweet_id }),
  1498. onload: function(response) {
  1499. if(response.status != 200) {
  1500. giveawayHelperUI.showError(`Failed to delete
  1501. <a href="https://twitter.com/${user_handle}" target="_blank">Retweet</a>`);
  1502. }
  1503. }
  1504. });
  1505. }
  1506.  
  1507. return {
  1508. /**
  1509. *
  1510. */
  1511. handleEntry: function(action_data, button_callback, ready_check, show_name, expected_user) {
  1512. if(ready_a && ready_b) {
  1513. prepCreateButton(action_data, button_callback, ready_check, show_name, expected_user);
  1514. } else {
  1515. // Wait for the command hub to load
  1516. var temp_interval = setInterval(function() {
  1517. if(ready_a && ready_b) {
  1518. clearInterval(temp_interval);
  1519. prepCreateButton(action_data, button_callback, ready_check, show_name, expected_user);
  1520. }
  1521. }, 100);
  1522. }
  1523. }
  1524. };
  1525. }
  1526.  
  1527. var instance;
  1528. return {
  1529. getInstance: function() {
  1530. if(!instance) instance = init();
  1531. return instance;
  1532. }
  1533. };
  1534. })();
  1535.  
  1536. /**
  1537. * Handles all Twitch entries that may need to interact with Twitch
  1538. */
  1539. var TwitchHandler = (function() {
  1540. function init() {
  1541. var command_hub_url = "https://player.twitch.tv/",
  1542. command_hub_host = "player.twitch.tv",
  1543. user_handle = null,
  1544. api_token = null,
  1545. button_count = 1,
  1546. following_status = {},
  1547. handled_channels = [],
  1548. ready = false;
  1549.  
  1550. // Get all the user data we'll need to undo twitch entries
  1551. commandHub.load(
  1552. command_hub_url,
  1553. command_hub_host,
  1554. function() {
  1555. return {
  1556. user_handle: getCookie("login"),
  1557. api_token: getCookie("auth-token")
  1558. };
  1559. },
  1560. function(data) {
  1561. user_handle = data.user_handle;
  1562. api_token = data.api_token;
  1563. ready = true;
  1564. }
  1565. );
  1566.  
  1567. /**
  1568. * Get ready to create an item
  1569. */
  1570. function prepCreateButton(twitch_handle, button_callback, ready_check, show_name, expected_user) {
  1571. // Wait until the entry is completed before showing the button
  1572. var temp_interval = setInterval(function() {
  1573. if(ready_check === null || ready_check()) {
  1574. clearInterval(temp_interval);
  1575. createButton(twitch_handle, button_callback, show_name, expected_user, ready_check === null);
  1576. }
  1577. }, 100);
  1578. }
  1579.  
  1580. /**
  1581. * Create the button
  1582. */
  1583. function createButton(twitch_handle, button_callback, show_name, expected_user, toggle_button) {
  1584. if(typeof expected_user !== "undefined" && !expected_user) {
  1585. // The user doesn't have a Twitter account linked, do nothing
  1586. } else if(user_handle === null || api_token === null) {
  1587. // We're not logged in
  1588. giveawayHelperUI.showError(`You must be logged into
  1589. <a href="https://www.twitch.tv/login" target="_blank">twitch.tv</a>`);
  1590. } else if(typeof expected_user !== "undefined" && expected_user.user_handle != user_handle) {
  1591. // We're logged in as the wrong user
  1592. giveawayHelperUI.showError(`You must be logged into the Twitch account linked to Gleam.io:
  1593. <a href="https://twitch.tv/${expected_user.user_handle}" target="_blank">
  1594. https://twitch.tv/${expected_user.user_handle}</a>`);
  1595. } else {
  1596. // Create the button
  1597. var button_id = "twitch_button_" + button_count++;
  1598.  
  1599. if(toggle_button) {
  1600. getTwitchUserData(twitch_handle, function(is_following) {
  1601. var label = is_following ? `Unfollow ${twitch_handle}` : `Follow ${twitch_handle}`;
  1602.  
  1603. following_status[twitch_handle] = is_following;
  1604.  
  1605. button_callback(
  1606. giveawayHelperUI.buildButton(button_id, label, is_following, function() {
  1607. toggleFollowStatus(button_id, twitch_handle);
  1608. giveawayHelperUI.showButtonLoading(button_id);
  1609. })
  1610. );
  1611. });
  1612. } else {
  1613. var label = `Unfollow${(show_name ? ` ${twitch_handle}` : "")}`;
  1614.  
  1615. button_callback(
  1616. giveawayHelperUI.buildButton(button_id, label, false, function() {
  1617. giveawayHelperUI.removeButton(button_id);
  1618. deleteTwitchFollow(twitch_handle);
  1619. })
  1620. );
  1621. }
  1622. }
  1623. }
  1624.  
  1625. /**
  1626. *
  1627. */
  1628. function deleteTwitchFollow(twitch_handle, callback) {
  1629. MKY.xmlHttpRequest({
  1630. url: "https://api.twitch.tv/kraken/users/" + user_handle + "/follows/channels/" + twitch_handle,
  1631. method: "DELETE",
  1632. headers: { "Authorization": "OAuth " + api_token },
  1633. onload: function(response) {
  1634. if(response.status != 204 && response.status != 200) {
  1635. giveawayHelperUI.showError(`Failed to unfollow Twitch user:
  1636. <a href="https://twitch.tv/${twitch_handle}" target="_blank">${twitch_handle}</a>`);
  1637.  
  1638. if(typeof callback == "function") callback(false);
  1639. } else {
  1640. if(typeof callback == "function") callback(true);
  1641. }
  1642. }
  1643. });
  1644. }
  1645.  
  1646. /**
  1647. *
  1648. */
  1649. function twitchFollow(twitch_handle, callback) {
  1650. MKY.xmlHttpRequest({
  1651. url: "https://api.twitch.tv/kraken/users/" + user_handle + "/follows/channels/" + twitch_handle,
  1652. method: "PUT",
  1653. headers: { "Authorization": "OAuth " + api_token },
  1654. onload: function(response) {
  1655. if(response.status != 204 && response.status != 200) {
  1656. giveawayHelperUI.showError(`Failed to follow Twitch user:
  1657. <a href="https://twitch.tv/${twitch_handle}" target="_blank">${twitch_handle}</a>`);
  1658.  
  1659. callback(false);
  1660. } else {
  1661. callback(true);
  1662. }
  1663. }
  1664. });
  1665. }
  1666.  
  1667. /**
  1668. * @return {Boolean} is_follow - True for "following", false for "not following"
  1669. */
  1670. function getTwitchUserData(twitch_handle, callback) {
  1671. MKY.xmlHttpRequest({
  1672. url: "https://api.twitch.tv/kraken/users/" + user_handle + "/follows/channels/" + twitch_handle,
  1673. method: "GET",
  1674. headers: { "Authorization": "OAuth " + api_token },
  1675. onload: function(response) {
  1676. if(response.status === 404) {
  1677. callback(false);
  1678. } else if(response.status != 204 && response.status != 200) {
  1679. giveawayHelperUI.showError(`Failed to determine follow status of Twtich user`);
  1680. } else {
  1681. callback(true);
  1682. }
  1683. }
  1684. });
  1685. }
  1686.  
  1687. /**
  1688. *
  1689. */
  1690. function toggleFollowStatus(button_id, twitch_handle) {
  1691. if(following_status[twitch_handle]) {
  1692. deleteTwitchFollow(twitch_handle, function(success) {
  1693. if(success) {
  1694. following_status[twitch_handle] = false;
  1695. giveawayHelperUI.toggleButtonClass(button_id);
  1696. giveawayHelperUI.setButtonLabel(button_id, `Follow ${twitch_handle}`);
  1697. }
  1698.  
  1699. giveawayHelperUI.hideButtonLoading(button_id);
  1700. });
  1701. } else {
  1702. twitchFollow(twitch_handle, function(success) {
  1703. if(success) {
  1704. following_status[twitch_handle] = true;
  1705. giveawayHelperUI.toggleButtonClass(button_id);
  1706. giveawayHelperUI.setButtonLabel(button_id, `Unfollow ${twitch_handle}`);
  1707. }
  1708.  
  1709. giveawayHelperUI.hideButtonLoading(button_id);
  1710. });
  1711. }
  1712. }
  1713.  
  1714. return {
  1715. /**
  1716. *
  1717. */
  1718. handleEntry: function(twitch_handle, button_callback, ready_check, show_name, expected_user) {
  1719. if(ready) {
  1720. prepCreateButton(twitch_handle, button_callback, ready_check, show_name, expected_user);
  1721. } else {
  1722. // Wait for the command hub to load
  1723. var temp_interval = setInterval(function() {
  1724. if(ready) {
  1725. clearInterval(temp_interval);
  1726. prepCreateButton(twitch_handle, button_callback, ready_check, show_name, expected_user);
  1727. }
  1728. }, 100);
  1729. }
  1730. },
  1731.  
  1732. /**
  1733. *
  1734. */
  1735. findChannels: function(button_callback, target, show_name, do_cache, cache_id) {
  1736. var self = this;
  1737.  
  1738. giveawayHelperUI.restoreCachedLinks(cache_id).then(function(channels) {
  1739. var re = /twitch\.tv\/([a-zA-Z0-9_]{2,25})/g,
  1740. match;
  1741.  
  1742. if(!do_cache) {
  1743. channels = [];
  1744. }
  1745.  
  1746. while((match = re.exec(target)) !== null) {
  1747. channels.push(match[1].toLowerCase());
  1748. }
  1749.  
  1750. channels = giveawayHelperUI.removeDuplicates(channels);
  1751. if(do_cache) giveawayHelperUI.cacheLinks(channels, cache_id);
  1752.  
  1753. for(var i = 0; i < channels.length; i++) {
  1754. if(channels[i] == "login") continue;
  1755.  
  1756. if($.inArray(channels[i], handled_channels) == -1) {
  1757. handled_channels.push(channels[i]);
  1758. self.handleEntry(channels[i], button_callback, null, show_name);
  1759. }
  1760. }
  1761. });
  1762. }
  1763. };
  1764. }
  1765.  
  1766. var instance;
  1767. return {
  1768. getInstance: function() {
  1769. if(!instance) instance = init();
  1770. return instance;
  1771. }
  1772. };
  1773. })();
  1774.  
  1775. /**
  1776. *
  1777. */
  1778. var giveawayHelperUI = (function() {
  1779. var active_errors = [],
  1780. active_buttons = {},
  1781. gh_main_container = randomString(10),
  1782. gh_button_container = randomString(10),
  1783. gh_button_title = randomString(10),
  1784. gh_button_loading = randomString(10),
  1785. gh_spin = randomString(10),
  1786. gh_notification_container = randomString(10),
  1787. gh_notification = randomString(10),
  1788. gh_error = randomString(10),
  1789. gh_close = randomString(10),
  1790. main_container = $("<div>", { class: gh_main_container }),
  1791. button_container = $("<span>"),
  1792. resolved_urls = [],
  1793. offset_top = 0;
  1794.  
  1795. /**
  1796. * Generate a random alphanumeric string
  1797. * http://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript
  1798. */
  1799. function randomString(length) {
  1800. var result = '';
  1801. var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  1802.  
  1803. for(var i = length; i > 0; --i) {
  1804. result += chars[Math.floor(Math.random() * chars.length)];
  1805. }
  1806.  
  1807. return result;
  1808. }
  1809.  
  1810. /**
  1811. * Push the page down to make room for notifications
  1812. */
  1813. function updateTopMargin() {
  1814. var new_margin_top = main_container.outerHeight() + main_container.position().top - offset_top;
  1815.  
  1816. $("html").css("margin-top", main_container.is(":visible") ? new_margin_top : 0);
  1817. }
  1818.  
  1819. return {
  1820. gh_button: randomString(10),
  1821. gh_button_on: randomString(10),
  1822. gh_button_off: randomString(10),
  1823. gh_redeem_button: randomString(10),
  1824.  
  1825. /**
  1826. * Print the UI
  1827. */
  1828. loadUI: function(zIndex, onLoad) {
  1829. zIndex = typeof zIndex == "undefined" ? 9999999999 : zIndex;
  1830.  
  1831. if(typeof onLoad == "function") onLoad();
  1832.  
  1833. MKY.addStyle(`
  1834. html {
  1835. overflow-y: scroll !important;
  1836. }
  1837.  
  1838. .${gh_main_container} {
  1839. font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
  1840. font-size: 16.5px;
  1841. left: 0px;
  1842. line-height: 21px;
  1843. position: fixed;
  1844. text-align: left;
  1845. top: 0px;
  1846. right: 0px;
  1847. z-index: ${zIndex};
  1848. }
  1849.  
  1850. .${gh_button_container} {
  1851. background-color: #000;
  1852. border-top: 1px solid rgba(52, 152, 219, .5);
  1853. box-shadow: 0px 2px 10px rgba(0, 0, 0, .5);
  1854. box-sizing: border-box;
  1855. color: #3498db;
  1856. padding: 8px;
  1857. }
  1858.  
  1859. .${gh_button_title} {
  1860. font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
  1861. padding: 10px 15px;
  1862. margin-right:8px;
  1863. }
  1864.  
  1865. .${gh_button_loading} {
  1866. -webkit-animation: ${gh_spin} 2s infinite linear;
  1867. animation: ${gh_spin} 2s infinite linear;
  1868. display: inline-block;
  1869. font: normal normal normal 14px/1;
  1870. transform-origin: 45% 55%;
  1871. }
  1872.  
  1873. .${gh_button_loading}:before {
  1874. content: "\\21B7";
  1875. }
  1876.  
  1877. @-webkit-keyframes ${gh_spin} {
  1878. 0% {
  1879. -webkit-transform:rotate(0deg);
  1880. transform:rotate(0deg);
  1881. }
  1882. 100%{
  1883. -webkit-transform:rotate(359deg);
  1884. transform:rotate(359deg);
  1885. }
  1886. }
  1887.  
  1888. @keyframes ${gh_spin} {
  1889. 0% {
  1890. -webkit-transform:rotate(0deg);
  1891. transform:rotate(0deg);
  1892. }
  1893. 100% {
  1894. -webkit-transform:rotate(359deg);
  1895. transform:rotate(359deg);
  1896. }
  1897. }
  1898.  
  1899. .${gh_notification} {
  1900. box-sizing: border-box;
  1901. padding: 8px;
  1902. }
  1903.  
  1904. .${gh_error} {
  1905. background: #f2dede;
  1906. box-shadow: 0px 2px 10px rgba(231, 76, 60, .5);
  1907. color: #a94442;
  1908. }
  1909.  
  1910. .${gh_error} a {
  1911. color: #a94442;
  1912. font-weight: 700;
  1913. }
  1914.  
  1915. .${gh_close} {
  1916. color: #000;
  1917. background: 0 0;
  1918. border: 0;
  1919. cursor: pointer;
  1920. display: block;
  1921. float: right;
  1922. font-size: 21px;
  1923. font-weight: 700;
  1924. height: auto;
  1925. line-height: 1;
  1926. margin: 0px;
  1927. opacity: .2;
  1928. padding: 0px;
  1929. text-shadow: 0 1px 0 #fff;
  1930. width: auto;
  1931. }
  1932.  
  1933. .${gh_close}:hover {
  1934. opacity: .5;
  1935. }
  1936. `);
  1937.  
  1938. $("body").append(main_container);
  1939. },
  1940.  
  1941. /**
  1942. *
  1943. */
  1944. defaultButtonSetup: function(offset) {
  1945. MKY.addStyle(`
  1946. .${this.gh_button} {
  1947. background-image:none;
  1948. border: 1px solid transparent;
  1949. border-radius: 3px !important;
  1950. cursor: pointer;
  1951. display: inline-block;
  1952. font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
  1953. font-size: 12px;
  1954. font-weight: 400;
  1955. height: 30px;
  1956. line-height: 1.5 !important;
  1957. margin: 4px 8px 4px 0px;
  1958. padding: 5px 10px;
  1959. text-align: center;
  1960. vertical-align: middle;
  1961. white-space: nowrap;
  1962. }
  1963.  
  1964. .${this.gh_button}:active {
  1965. box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
  1966. outline: 0;
  1967. }
  1968.  
  1969. .${this.gh_button_on} {
  1970. background-color: #337ab7;
  1971. border-color: #2e6da4;
  1972. color: #fff;
  1973. }
  1974.  
  1975. .${this.gh_button_on}:hover {
  1976. background-color: #286090;
  1977. border-color: #204d74;
  1978. color: #fff;
  1979. }
  1980.  
  1981. .${this.gh_button_off} {
  1982. background-color: #d9534f;
  1983. border-color: #d43f3a;
  1984. color: #fff;
  1985. }
  1986.  
  1987. .${this.gh_button_off}:hover {
  1988. background-color: #c9302c;
  1989. border-color: #ac2925;
  1990. color: #fff;
  1991. }
  1992.  
  1993. .${this.gh_redeem_button} {
  1994. background-color: #5cb85c;
  1995. border-color: #4cae4c;
  1996. color: #fff;
  1997. }
  1998. `);
  1999.  
  2000. if(typeof offset !== "undefined") {
  2001. main_container.css({top: offset[0], left: offset[1], right: offset[2]});
  2002. offset_top = offset[0];
  2003. }
  2004.  
  2005. main_container.append(
  2006. $("<div>", { class: gh_button_container }).append(
  2007. $("<span>", { class: gh_button_title }).html("Giveaway Helper v" + MKY.info.script.version)
  2008. ).append(button_container)
  2009. );
  2010.  
  2011. updateTopMargin();
  2012. },
  2013.  
  2014. /**
  2015. *
  2016. */
  2017. addButton: function(new_button) {
  2018. button_container.append(new_button);
  2019. new_button.width(new_button.outerWidth());
  2020. updateTopMargin();
  2021. },
  2022.  
  2023. /**
  2024. *
  2025. */
  2026. buildButton: function(button_id, label, button_on, click_function) {
  2027. var new_button =
  2028. $("<button>", { type: "button",
  2029. class: `${this.gh_button} ${button_on ? this.gh_button_on : this.gh_button_off}`
  2030. }).append(
  2031. $("<span>").append(label)).append(
  2032. $("<span>", { class: gh_button_loading, css: { display: "none" }})
  2033. ).click(function(e) {
  2034. e.stopPropagation();
  2035. if(!active_buttons[button_id].find(`.${gh_button_loading}`).is(":visible")) {
  2036. click_function();
  2037. }
  2038. });
  2039.  
  2040. active_buttons[button_id] = new_button;
  2041. return new_button;
  2042. },
  2043.  
  2044. /**
  2045. *
  2046. */
  2047. buildRedeemButton: function(button_id, label, redeem_url) {
  2048. var new_button =
  2049. $("<a>", { href: redeem_url, target: "_blank" }).append(
  2050. $("<button>", { type: "button",
  2051. class: `${this.gh_button} ${this.gh_redeem_button}`
  2052. }).append(
  2053. $("<span>").append(label)
  2054. )
  2055. );
  2056.  
  2057. active_buttons[button_id] = new_button;
  2058. return new_button;
  2059. },
  2060.  
  2061.  
  2062.  
  2063. /**
  2064. *
  2065. */
  2066. removeButton: function(button_id) {
  2067. active_buttons[button_id].remove();
  2068. delete active_buttons[button_id];
  2069. },
  2070.  
  2071. /**
  2072. *
  2073. */
  2074. setButtonLabel: function(button_id, label, color) {
  2075. active_buttons[button_id].find("span").first().text(label);
  2076.  
  2077. if(color !== undefined) {
  2078. active_buttons[button_id].css("background-color", color);
  2079. active_buttons[button_id].css("border-color", color);
  2080. }
  2081. },
  2082.  
  2083. /**
  2084. *
  2085. */
  2086. toggleButtonClass: function(button_id) {
  2087. active_buttons[button_id].toggleClass(this.gh_button_on);
  2088. active_buttons[button_id].toggleClass(this.gh_button_off);
  2089. },
  2090.  
  2091. /**
  2092. *
  2093. */
  2094. showButtonLoading: function(button_id) {
  2095. active_buttons[button_id].find("span").first().hide();
  2096. active_buttons[button_id].find(`.${gh_button_loading}`).show();
  2097. },
  2098.  
  2099. /**
  2100. *
  2101. */
  2102. hideButtonLoading: function(button_id) {
  2103. active_buttons[button_id].find("span").first().show();
  2104. active_buttons[button_id].find(`.${gh_button_loading}`).hide();
  2105. },
  2106.  
  2107. /**
  2108. * Print an error
  2109. */
  2110. showError: function(msg) {
  2111. // Don't print the same error multiple times
  2112. if(active_errors.indexOf(msg) != -1) return;
  2113.  
  2114. var self = this;
  2115.  
  2116. active_errors.push(msg);
  2117. main_container.append(
  2118. $("<div>", { class: `${gh_notification} ${gh_error}` }).append(
  2119. $("<button>", { class: gh_close}).append(
  2120. $("<span>").html("&times;")
  2121. ).click(function() {
  2122. $(this).unbind("click");
  2123. $(this).parent().slideUp(400, function() {
  2124. active_errors.splice(active_errors.indexOf(msg), 1);
  2125. $(this).remove();
  2126. updateTopMargin();
  2127. });
  2128. })
  2129. ).append(
  2130. $("<strong>").html("Giveaway Helper Error: ")
  2131. ).append(msg)
  2132. );
  2133.  
  2134. updateTopMargin();
  2135. },
  2136.  
  2137. /**
  2138. * Remove duplicate items from an array
  2139. */
  2140. removeDuplicates: function(arr) {
  2141. var out = [];
  2142.  
  2143. for(var i = 0; i < arr.length; i++) {
  2144. if (out.indexOf(arr[i]) == -1) {
  2145. out.push(arr[i]);
  2146. }
  2147. }
  2148.  
  2149. return out;
  2150. },
  2151.  
  2152. /**
  2153. * Some sites remove links to a group after you get your reward, remember which links we've seen where
  2154. */
  2155. cacheLinks: function(data, id) {
  2156. MKY.setValue(id, JSON.stringify(data));
  2157. },
  2158.  
  2159. /**
  2160. *
  2161. */
  2162. restoreCachedLinks: function(id) {
  2163. return MKY.getValue(id, JSON.stringify([])).then(function(value) {
  2164. return JSON.parse(value);
  2165. });
  2166. },
  2167.  
  2168. /**
  2169. *
  2170. */
  2171. resolveUrl: function(url, callback) {
  2172. var self = this,
  2173. cached_url_id = `cache_${MKY.info.script.version.replace(/\./g,"_")}_${CryptoJS.MD5(url)}`;
  2174.  
  2175. self.restoreCachedLinks(cached_url_id).then(function(value){
  2176. if(value.length !== 0) {
  2177. callback(value[0]);
  2178. } else {
  2179. self.cacheLinks([false], cached_url_id);
  2180.  
  2181. MKY.xmlHttpRequest({
  2182. url: url,
  2183. method: "GET",
  2184. onload: function(response) {
  2185. if(response.status == 200 && response.finalUrl !== null) {
  2186. self.cacheLinks([response.finalUrl], cached_url_id);
  2187. }
  2188.  
  2189. self.restoreCachedLinks(cached_url_id).then(function(final_url){
  2190. callback(final_url);
  2191. });
  2192. }
  2193. });
  2194. }
  2195. });
  2196. }
  2197. };
  2198. })();
  2199.  
  2200. /**
  2201. * Used to communicate with and run code on a different domain
  2202. * Usualy with the intent to grab necessary cookies
  2203. */
  2204. var commandHub = (function() {
  2205. /**
  2206. * http://stackoverflow.com/a/15724300
  2207. */
  2208. function getCookie(name) {
  2209. var value = "; " + document.cookie,
  2210. parts = value.split("; " + name + "=");
  2211.  
  2212. if(parts.length == 2) {
  2213. return parts.pop().split(";").shift();
  2214. } else {
  2215. return null;
  2216. }
  2217. }
  2218.  
  2219. return {
  2220. /**
  2221. * Load an iframe so that we can run code on a different domain
  2222. * @param {String} url - The url to be loaded into the iframe
  2223. * @param {Function} data_func - The code that we're going to run inside the iframe
  2224. * @param {Function} callback - Runs after data_func returns
  2225. */
  2226. load: function(url, hostname, data_func, callback) {
  2227. var command_hub = document.createElement('iframe');
  2228.  
  2229. command_hub.style.display = "none";
  2230. command_hub.src = url;
  2231. document.body.appendChild(command_hub);
  2232.  
  2233. hostname = hostname.replace(/\./g, "_");
  2234.  
  2235. var funcvar = `command_hub_func_${hostname}`,
  2236. retvar = `command_hub_return_${hostname}`;
  2237.  
  2238. window.addEventListener("message", function(event) {
  2239. if(event.source == command_hub.contentWindow) {
  2240. if(event.data.status == "ready") {
  2241. // the iframe has finished loading, tell it what to do
  2242. MKY.setValue(funcvar, encodeURI(data_func.toString()));
  2243. command_hub.contentWindow.postMessage({ status: "run" }, "*");
  2244. } else if(event.data.status == "finished") {
  2245. // wait until the values have been set
  2246. var temp_interval = setInterval(function() {
  2247. MKY.getValue(retvar).then(function(value) {
  2248. if(typeof value !== "undefined") {
  2249. clearInterval(temp_interval);
  2250.  
  2251. // the iframe has finished, send the data to the callback and close the frame
  2252. document.body.removeChild(command_hub);
  2253. callback(value);
  2254. MKY.deleteValue(retvar);
  2255. }
  2256. });
  2257. }, 100);
  2258. }
  2259. }
  2260. });
  2261. },
  2262.  
  2263. /**
  2264. *
  2265. */
  2266. init: function() {
  2267. var hostname = document.location.hostname.replace(/\./g, "_"),
  2268. funcvar = `command_hub_func_${hostname}`,
  2269. retvar = `command_hub_return_${hostname}`;
  2270.  
  2271. // wait for our parent to tell us what to do
  2272. window.addEventListener("message", function(event) {
  2273. if(event.source == parent) {
  2274. if(event.data.status == "run") {
  2275. // wait until the values have been set
  2276. var temp_interval = setInterval(function() {
  2277. MKY.getValue(funcvar).then(function(value) {
  2278. if(typeof value !== "undefined") {
  2279. clearInterval(temp_interval);
  2280. MKY.setValue(retvar, eval(`(${decodeURI(value)})`)());
  2281. MKY.deleteValue(funcvar);
  2282. parent.postMessage({ status: "finished" }, "*");
  2283. }
  2284. });
  2285. }, 100);
  2286. }
  2287. }
  2288. });
  2289.  
  2290. // let the parent know the iframe is ready
  2291. parent.postMessage({status: "ready"}, "*");
  2292. }
  2293. };
  2294. })();
  2295.  
  2296. // Greasemonkey 4 polyfill
  2297. // https://arantius.com/misc/greasemonkey/imports/greasemonkey4-polyfill.js
  2298.  
  2299. var MKY = typeof GM !== "undefined" ? GM : {
  2300. 'info': GM_info,
  2301. 'addStyle': GM_addStyle,
  2302. 'xmlHttpRequest': GM_xmlhttpRequest,
  2303. 'deleteValue': GM_deleteValue,
  2304. 'setValue': GM_setValue,
  2305. 'getValue': function () {
  2306. return new Promise((resolve, reject) => {
  2307. try {
  2308. resolve(GM_getValue.apply(this, arguments));
  2309. } catch (e) {
  2310. reject(e);
  2311. }
  2312. });
  2313. }
  2314. };
  2315.  
  2316. if(typeof GM_addStyle == 'undefined' || typeof MKY.addStyle == 'undefined') {
  2317. MKY.addStyle = function(aCss) {
  2318. 'use strict';
  2319. let head = document.getElementsByTagName('head')[0];
  2320. if(head) {
  2321. let style = document.createElement('style');
  2322. style.setAttribute('type', 'text/css');
  2323. style.textContent = aCss;
  2324. head.appendChild(style);
  2325. return style;
  2326. }
  2327. return null;
  2328. };
  2329. }
  2330.  
  2331. setup.run();
  2332. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址