jimmyfk

code

Jan 1st, 2023
2,124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 99.33 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Delete steam post history developing version
  3. // @namespace    http://tampermonkey.net/
  4. // @version      0.2.2
  5. // @description  delete all post history
  6. // @author       Justman
  7. // @match        https://steamcommunity.com/*/*/commenthistory*
  8. // @match        https://steamcommunity.com/*/*/posthistory*
  9. // @icon         https://www.google.com/s2/favicons?sz=64&domain=steamcommunity.com
  10. // @grant unsafeWindow
  11. // ==/UserScript==
  12.  
  13. (function() {
  14.  
  15.     /*
  16.     Execute this code on https://steamcommunity.com/my/commenthistory
  17.  
  18.     The newest versions are available on BitBucket: https://bitbucket.org/SoftJustman/steamcommentscleaner
  19.  
  20.     Created by Justman (steamcommunity.com/id/justman666)
  21.     Specially for Miped.ru
  22. todo replace var with let or constant
  23. */
  24.  
  25.     /*
  26.         Changable constants
  27.     */
  28.     const FIRST_PAGE_TO_CLEAR = 1; // Number of first page to be cleared
  29.     let LAST_PAGE_TO_CLEAR ;
  30.     do
  31.         LAST_PAGE_TO_CLEAR = parseInt(window.prompt('Enter nmumber of pages to be deleted (-1 for all)')); // Number of last page to clear. -1 if you need to delete all of them
  32.     while (typeof LAST_PAGE_TO_CLEAR !== "number")
  33.     // Example (2, 2) will clear 2nd page only
  34.     const PRINT_EXECUTED_COMMANDS = true; // Print everything script does (It's a lot)
  35.     const style = 'color: darkorchid';
  36.  
  37.     function global() {
  38.  
  39.         const CAutoSizingTextArea = Class.create({
  40.             m_elTextArea: null,
  41.             m_nMinHeight: 20,
  42.             m_nMaxHeight: 500,
  43.             m_cCurrentSize: Number.MAX_VALUE,
  44.             m_fnChangeCallback: null,
  45.             m_nTextAreaPadding: null,
  46.             CalculatePadding: function () {
  47.                 // briefly empty the text area and set the height so we can see how much padding there is
  48.                 const strContents = this.m_elTextArea.value;
  49.                 this.m_elTextArea.value = '';
  50.                 this.m_elTextArea.style.height = this.m_nMinHeight + 'px';
  51.                 this.m_nTextAreaPadding = this.m_elTextArea.scrollHeight - this.m_nMinHeight;
  52.                 this.m_elTextArea.value = strContents;
  53.             },
  54.  
  55.             OnTextInput: function () {
  56.                 let iScrollOffset = undefined;
  57.                 const cNewLength = this.m_elTextArea.value.length;
  58.  
  59.                 // we delay this until first input as some values get reported incorrectly if the element isn't visible.
  60.                 if (this.m_nTextAreaPadding === null && $J(this.m_elTextArea).is(':visible'))
  61.                     this.CalculatePadding();
  62.  
  63.                 // force a resize
  64.                 if (cNewLength < this.m_cEntryLength) {
  65.                     // when we shrink this box, we might scroll the window.  Remember where we are so we can jump back
  66.                     iScrollOffset = window.scrollY;
  67.                     this.m_elTextArea.style.height = this.m_nMinHeight + 'px';
  68.                 }
  69.  
  70.                 if (this.m_elTextArea.scrollHeight > this.m_nMaxHeight) {
  71.                     this.m_elTextArea.style.height = this.m_nMaxHeight + 'px';
  72.                     this.m_elTextArea.style.overflow = 'auto';
  73.                 } else if (this.m_elTextArea.scrollHeight !== this.m_elTextArea.getHeight()) {
  74.                     const nHeight = Math.max(this.m_elTextArea.scrollHeight, this.m_nMinHeight);
  75.                     this.m_elTextArea.style.height = (nHeight - this.m_nTextAreaPadding) + 'px';
  76.  
  77.                     if (this.m_elTextArea.style.overflow === 'auto')
  78.                         this.m_elTextArea.style.overflow = 'hidden';
  79.                 }
  80.  
  81.                 if (this.m_fnChangeCallback)
  82.                     this.m_fnChangeCallback(this.m_elTextArea);
  83.  
  84.                 if (iScrollOffset)
  85.                     window.scrollTo(window.scrollX, iScrollOffset);
  86.  
  87.                 this.m_cEntryLength = cNewLength;
  88.             }
  89.         });
  90.  
  91.  
  92.         function UpdateParameterInCurrentURL( strParamName, strParamValue, rgRemoveParameters )
  93.         {
  94.             const path = window.location.pathname;
  95.             let query = window.location.search;
  96.             let params = {};
  97.             if ( query && query.length > 2 )
  98.                 params = $J.deparam( query.substr( 1 ) );
  99.  
  100.             if ( strParamValue === null )
  101.                 delete params[strParamName];
  102.             else
  103.                 params[strParamName] = strParamValue;
  104.  
  105.             // comment thread specific
  106.             if ( rgRemoveParameters )
  107.                 for(let i = 0; i < rgRemoveParameters.length; i++ )
  108.                     delete params[ rgRemoveParameters[i] ];
  109.  
  110.             query = $J.param( params );
  111.  
  112.             return path + ( query ? '?' + query : '' );
  113.         }
  114.  
  115.  
  116.         const g_rgCommentThreads = {};
  117.  
  118.         const CCommentThread = Class.create({
  119.  
  120.             m_strName: null,
  121.             m_strCommentThreadType: null,
  122.             m_rgCommentData: null,
  123.             m_strActionURL: null,
  124.             m_elTextArea: null,
  125.             m_cPageSize: null,
  126.             m_nQuoteBoxHeight: 40,
  127.  
  128.             m_cTotalCount: 0,
  129.             m_iCurrentPage: 0,
  130.             m_cMaxPages: 0,
  131.             m_cDropdownPages: 0,
  132.             m_bLoading: false,
  133.             m_bLoadingUserHasUpVoted: false,
  134.             m_cUpVotes: 0,
  135.  
  136.             m_bIncludeRaw: false,
  137.             m_rgRawCommentCache: null,
  138.             m_bHasPaging: true,
  139.             m_bTrackNavigation: false,  // should we track navigation in the URL?
  140.  
  141.             // these vars are id's we'll update when values change
  142.             m_votecountID: null,
  143.             m_voteupID: null,
  144.             m_commentcountID: null,
  145.  
  146.             m_oTextAreaSizer: null,
  147.  
  148.             m_bSubscribed: null,
  149.             m_$SubscribeCheckbox: null,
  150.             CheckTextAreaSize: function () {
  151.                 this.m_oTextAreaSizer.OnTextInput();
  152.             },
  153.  
  154.             OnTextInput: function (elSaveButton, elTextArea) {
  155.                 if (elSaveButton) {
  156.                     let strPrepoulatedText = $J(this.m_elTextArea).data('prepopulated-text');
  157.                     let bEnteredText = elTextArea.value.length > 0;
  158.  
  159.                     if (bEnteredText && strPrepoulatedText && !$J(this.m_elTextArea).data('replaced-prepopulated-text')) {
  160.                         strPrepoulatedText = v_trim(strPrepoulatedText).replace(/[\n\r]/g, '');
  161.                         const strEnteredText = v_trim(elTextArea.value).replace(/[\n\r]/g, '');
  162.  
  163.                         bEnteredText = strPrepoulatedText !== strEnteredText;
  164.  
  165.                         // save so we don't have to keep doing this check as they enter more text.
  166.                         if (bEnteredText)
  167.                             $J(this.m_elTextArea).data('replaced-prepopulated-text', true);
  168.                     }
  169.  
  170.                     if (bEnteredText)
  171.                         elSaveButton.show();
  172.                     else
  173.                         elSaveButton.hide();
  174.                 }
  175.             },
  176.  
  177.             GetActionURL: function (action) {
  178.                 let url = this.m_strActionURL + action + '/';
  179.                 url += this.m_rgCommentData['owner'] + '/';
  180.                 url += this.m_rgCommentData['feature'] + '/';
  181.                 return url;
  182.             },
  183.  
  184.             ParametersWithDefaults: function (params) {
  185.                 if (!params)
  186.                     params = {};
  187.  
  188.                 params['count'] = this.m_cPageSize;
  189.                 params['sessionid'] = g_sessionID;
  190.  
  191.                 if (this.m_rgCommentData['extended_data'])
  192.                     params['extended_data'] = this.m_rgCommentData['extended_data'];
  193.  
  194.                 if (this.m_rgCommentData['feature2'])
  195.                     params['feature2'] = this.m_rgCommentData['feature2'];
  196.  
  197.                 if (this.m_rgCommentData['oldestfirst'])
  198.                     params['oldestfirst'] = true;
  199.  
  200.                 if (this.m_rgCommentData['newestfirstpagination'])
  201.                     params['newestfirstpagination'] = true;
  202.  
  203.                 if (this.m_rgCommentData['lastvisit'])
  204.                     params['lastvisit'] = this.m_rgCommentData['lastvisit'];
  205.  
  206.                 if (this.m_bIncludeRaw)
  207.                     params['include_raw'] = true;
  208.  
  209.  
  210.                 return params;
  211.             },
  212.             DeleteComment: function (gidComment, bUndelete, fnOnSuccess) {
  213.                 if (this.m_bLoading)
  214.                     return;
  215.  
  216.                 const params = this.ParametersWithDefaults({
  217.                     gidcomment: gidComment,
  218.                     start: this.m_cPageSize * this.m_iCurrentPage
  219.                 });
  220.  
  221.                 if (bUndelete)
  222.                     params.undelete = 1;
  223.  
  224.                 this.m_bLoading = true;
  225.                 new Ajax.Request(this.GetActionURL('delete'), {
  226.                     method: 'post',
  227.                     parameters: params,
  228.                     onSuccess: fnOnSuccess ? fnOnSuccess : this.OnResponseDeleteComment.bind(this, ++this.m_nRenderAjaxSequenceNumber),
  229.                     onFailure: this.OnFailureDisplayError.bind(this),
  230.                     onComplete: this.OnAJAXComplete.bind(this)
  231.                 });
  232.             },
  233.  
  234.             MarkSpam: function (gidComment, authorAccountID, fnOnSuccess) {
  235.                 if (this.m_bLoading)
  236.                     return;
  237.  
  238.                 const params = this.ParametersWithDefaults({
  239.                     gidcomment: gidComment,
  240.                     author_accountid: authorAccountID,
  241.                     start: this.m_cPageSize * this.m_iCurrentPage
  242.                 });
  243.  
  244.                 const thread = this;
  245.  
  246.                 const dialog = ShowConfirmDialog('Marcar como spam', 'Esto aplicará un bloqueo de la comunidad al autor, añadirá este comentario como datos de entrenamiento para que podamos marcar este tipo de comentarios automáticamente y borrará todos los comentarios del autor. ¿Seguro que es lo que quieres hacer?', 'Marcar como spam');
  247.                 dialog.done(function () {
  248.                     thread.m_bLoading = true;
  249.                     new Ajax.Request(thread.GetActionURL('markspam'), {
  250.                         method: 'post',
  251.                         parameters: params,
  252.                         onSuccess: fnOnSuccess ? fnOnSuccess : thread.OnResponseDeleteComment.bind(thread, ++thread.m_nRenderAjaxSequenceNumber),
  253.                         onFailure: thread.OnFailureDisplayError.bind(thread),
  254.                         onComplete: thread.OnAJAXComplete.bind(thread)
  255.                     });
  256.                 });
  257.             },
  258.  
  259.             ClearContentCheckFlag: function (gidComment, fnOnSuccess) {
  260.                 if (this.m_bLoading)
  261.                     return;
  262.  
  263.                 const params = this.ParametersWithDefaults({
  264.                     gidcomment: gidComment,
  265.                     start: this.m_cPageSize * this.m_iCurrentPage
  266.                 });
  267.  
  268.                 const thread = this;
  269.  
  270.                 const dialog = ShowConfirmDialog('Quitar marca de contenido bloqueable', 'Esto quitará la marca de contenido bloqueable a este comentario y es irreversible. ¿Seguro que es lo que quieres hacer?', 'Quitar marca de contenido bloqueable');
  271.                 dialog.done(function () {
  272.                     thread.m_bLoading = true;
  273.                     new Ajax.Request(thread.GetActionURL('clearcontentcheckflag'), {
  274.                         method: 'post',
  275.                         parameters: params,
  276.                         onSuccess: fnOnSuccess ? fnOnSuccess : thread.OnResponseDeleteComment.bind(thread, ++thread.m_nRenderAjaxSequenceNumber),
  277.                         onFailure: thread.OnFailureDisplayError.bind(thread),
  278.                         onComplete: thread.OnAJAXComplete.bind(thread)
  279.                     });
  280.                 });
  281.             },
  282.  
  283.             HideAndReport: function (gidComment, bHide, fnOnSuccess) {
  284.                 if (this.m_bLoading)
  285.                     return;
  286.  
  287.                 const params = this.ParametersWithDefaults({
  288.                     gidcomment: gidComment,
  289.                     hide: bHide,
  290.                     start: this.m_cPageSize * this.m_iCurrentPage
  291.                 });
  292.  
  293.                 this.m_bLoading = true;
  294.                 new Ajax.Request(this.GetActionURL('hideandreport'), {
  295.                     method: 'post',
  296.                     parameters: params,
  297.                     onSuccess: fnOnSuccess ? fnOnSuccess : this.OnResponseHideAndReportComment.bind(this, ++this.m_nRenderAjaxSequenceNumber),
  298.                     onFailure: this.OnFailureDisplayError.bind(this),
  299.                     onComplete: this.OnAJAXComplete.bind(this)
  300.                 });
  301.             },
  302.  
  303.             OnResponseHideAndReportComment: function (nAjaxSequenceNumber, transport) {
  304.                 if (transport.responseJSON && transport.responseJSON.success)
  305.                     this.OnResponseRenderComments(CCommentThread.RENDER_GOTOCOMMENT, nAjaxSequenceNumber, transport);
  306.                 else
  307.                     this.OnFailureDisplayError(transport);
  308.             },
  309.  
  310.             DisplayEditComment: function (gidComment) {
  311.                 const elForm = $('editcommentform_' + gidComment);
  312.                 const elTextarea = $('comment_edit_text_' + gidComment);
  313.  
  314.                 const elContent = $('comment_content_' + gidComment);
  315.                 elContent.hide();
  316.  
  317.                 if (elContent.next('.forum_audit'))
  318.                     elContent.next('.forum_audit').hide();
  319.  
  320.                 $('comment_edit_' + gidComment).show();
  321.                 $('comment_edit_' + gidComment + '_error').update('');
  322.  
  323.                 if (!elTextarea.value || elTextarea.value.length === 0)
  324.                     elTextarea.value = this.m_rgRawCommentCache[gidComment].text;
  325.  
  326.                 if (!elForm.m_bEventsBound) {
  327.                     new CAutoSizingTextArea(elTextarea, 40);
  328.                     elForm.observe('submit', this.SubmitEditComment.bind(this, elForm));
  329.                     elForm.observe('reset', this.HideEditComment.bind(this, gidComment));
  330.                     elForm.m_bEventsBound = true;
  331.                 }
  332.             },
  333.  
  334.             VoteUp: function () {
  335.                 if (this.m_bLoading)
  336.                     return;
  337.  
  338.                 const params = this.ParametersWithDefaults({
  339.                     vote: this.m_bLoadingUserHasUpVoted ? 0 : 1 // flip our vote
  340.                 });
  341.  
  342.                 this.m_bLoading = true;
  343.                 new Ajax.Request(this.GetActionURL('voteup'), {
  344.                     method: 'post',
  345.                     parameters: params,
  346.                     onSuccess: this.OnResponseVoteUp.bind(this, ++this.m_nRenderAjaxSequenceNumber),
  347.                     onFailure: this.OnFailureDisplayError.bind(this),
  348.                     onComplete: this.OnAJAXComplete.bind(this)
  349.                 });
  350.             },
  351.  
  352.             UpdateAnswer: function (gidComment, bExisting) {
  353.                 // see if it's on the current page
  354.                 if (this.m_bLoading)
  355.                     return;
  356.  
  357.                 let $modal;
  358.                 if (!gidComment) {
  359.                     $modal = ShowConfirmDialog('Eliminar como respuesta', '¿Seguro que quieres desmarcar esta publicación como la respuesta a este hilo? Puedes actualizar esto en cualquier momento.', 'Eliminar como respuesta');
  360.                 } else {
  361.                     let strModalBody = 'Estás a punto de marcar este mensaje como la respuesta a este hilo. Esto indicará que la cuestión original ha sido respondida y enlazará a este mensaje específico. Puedes eliminar esta marca o indicar un mensaje diferente como respuesta en cualquier momento si cambias de opinión.';
  362.                     if (bExisting)
  363.                         strModalBody = 'Ya se ha seleccionado un mensaje diferente como respuesta a este hilo. ¿Quieres elegir este mensaje como la nueva respuesta?';
  364.  
  365.                     $modal = ShowConfirmDialog('Marcar como respuesta', strModalBody, 'Elegir respuesta');
  366.                     $modal.SetMaxWidth(500);
  367.                 }
  368.  
  369.                 const _$this = this;
  370.  
  371.                 $modal.done(function () {
  372.  
  373.                     const params = _$this.ParametersWithDefaults({
  374.                         gidcommentanswer: gidComment
  375.                     });
  376.  
  377.                     _$this.m_bLoading = true;
  378.                     new Ajax.Request(_$this.GetActionURL('updateanswer'), {
  379.                         method: 'post',
  380.                         parameters: params,
  381.                         onSuccess: function () {
  382.                             window.location.hash = 'c' + gidComment;
  383.                             window.location.reload();
  384.                         },
  385.                         onFailure: function (transport) {
  386.                             if (transport.responseJSON && transport.responseJSON.success) {
  387.                                 var strError = 'Hubo un problema al actualizar la respuesta de este tema. Error:' + transport.responseJSON.success;
  388.                                 if (transport.responseJSON.success === 15) {
  389.                                     strError = 'No tienes permiso para actualizar la respuesta de este tema.'
  390.                                 }
  391.  
  392.                                 ShowAlertDialog('Error', strError);
  393.                             }
  394.                         },
  395.                         onComplete: _$this.OnAJAXComplete.bind(_$this)
  396.                     });
  397.                 });
  398.  
  399.             },
  400.  
  401.             GetRawComment: function (gidComment) {
  402.                 return this.m_rgRawCommentCache[gidComment];
  403.             },
  404.  
  405.             GetCommentTextEntryElement: function () {
  406.                 return this.m_elTextArea;
  407.             },
  408.  
  409.             HideEditComment: function (gidComment) {
  410.                 $('comment_content_' + gidComment).show();
  411.                 $('comment_edit_' + gidComment).hide();
  412.             },
  413.  
  414.             OnResponseEditComment: function (gidComment, nAjaxSequenceNumber, transport) {
  415.                 if (transport.responseJSON && transport.responseJSON.success) {
  416.                     // no need to hide because render will replace our whole element
  417.                     this.OnResponseRenderComments(CCommentThread.RENDER_DELETEDPOST, nAjaxSequenceNumber, transport);   //display the updated comment thread
  418.                 } else {
  419.                     this.OnEditFailureDisplayError(gidComment, transport);
  420.                 }
  421.             },
  422.  
  423.             OnEditFailureDisplayError: function (gidComment, transport) {
  424.                 this.DisplayError($('comment_edit_' + gidComment + '_error'), transport);
  425.             },
  426.  
  427.             SubmitEditComment: function (elForm) {
  428.                 if (this.m_bLoading)
  429.                     return false;
  430.  
  431.                 const gidComment = elForm.elements['gidcomment'].value;
  432.                 const strComment = elForm.elements['comment'].value;
  433.  
  434.                 const params = this.ParametersWithDefaults({
  435.                     gidcomment: gidComment,
  436.                     comment: strComment,
  437.                     start: this.m_cPageSize * this.m_iCurrentPage
  438.                 });
  439.  
  440.                 this.m_bLoading = true;
  441.                 new Ajax.Request(this.GetActionURL('edit'), {
  442.                     method: 'post',
  443.                     parameters: params,
  444.                     onSuccess: this.OnResponseEditComment.bind(this, gidComment, ++this.m_nRenderAjaxSequenceNumber),
  445.                     onFailure: this.OnEditFailureDisplayError.bind(this, gidComment),
  446.                     onComplete: this.OnAJAXComplete.bind(this)
  447.                 });
  448.                 return false;
  449.             },
  450.  
  451.             OnAJAXComplete: function () {
  452.                 this.m_bLoading = false;
  453.             },
  454.             m_nRenderAjaxSequenceNumber: 0,
  455.             GoToPage: function (iPage, eRenderReason) {
  456.                 if (iPage >= this.m_cMaxPages || iPage < 0 || (iPage === this.m_iCurrentPage && !this.m_bLoading))
  457.                     return;
  458.  
  459.                 const params = this.ParametersWithDefaults({
  460.                     start: this.m_cPageSize * iPage,
  461.                     totalcount: this.m_cTotalCount
  462.                 });
  463.  
  464.                 this.m_bLoading = true;
  465.                 new Ajax.Request(this.GetActionURL('render'), {
  466.                     method: 'post',
  467.                     parameters: params,
  468.                     onSuccess: this.OnResponseRenderComments.bind(this, eRenderReason || CCommentThread.RENDER_GOTOPAGE, ++this.m_nRenderAjaxSequenceNumber),
  469.                     onComplete: this.OnAJAXComplete.bind(this)
  470.                 });
  471.             },
  472.  
  473.             GoToPageWithComment: function (gidComment, eRenderReason) {
  474.                 // see if it's on the current page
  475.                 if (this.m_bLoading || $('comment_' + gidComment))
  476.                     return;
  477.  
  478.                 // nope, load
  479.                 const params = this.ParametersWithDefaults({
  480.                     gidComment: gidComment
  481.                 });
  482.  
  483.                 new Ajax.Request(this.GetActionURL('render'), {
  484.                     method: 'post',
  485.                     parameters: params,
  486.                     onSuccess: this.OnResponseRenderComments.bind(this, eRenderReason || CCommentThread.RENDER_GOTOCOMMENT, ++this.m_nRenderAjaxSequenceNumber),
  487.                     onComplete: this.OnAJAXComplete.bind(this)
  488.                 });
  489.             },
  490.             OnResponseDeleteComment: function (nAjaxSequenceNumber, transport) {
  491.                 if (transport.responseJSON && transport.responseJSON.success)
  492.                     this.OnResponseRenderComments(CCommentThread.RENDER_DELETEDPOST, nAjaxSequenceNumber, transport);
  493.                 else
  494.                     this.OnFailureDisplayError(transport);
  495.             },
  496.  
  497.             OnResponseVoteUp: function (nAjaxSequenceNumber, transport) {
  498.                 if (transport.responseJSON && transport.responseJSON.success) {
  499.                     this.OnResponseRenderComments(CCommentThread.RENDER_GOTOCOMMENT, nAjaxSequenceNumber, transport);
  500.                     this.m_bLoadingUserHasUpVoted = !this.m_bLoadingUserHasUpVoted; // we can switch this to getting from the response after 8/24/2012
  501.                     this.m_cUpVotes = transport.responseJSON.upvotes;
  502.  
  503.                     if (this.m_votecountID && $(this.m_votecountID) && transport.responseJSON.votetext) {
  504.                         $(this.m_votecountID).innerHTML = transport.responseJSON.votetext;
  505.                     }
  506.  
  507.                     if (this.m_voteupID && $(this.m_voteupID)) {
  508.                         if (this.m_bLoadingUserHasUpVoted)
  509.                             $(this.m_voteupID).addClassName('active');
  510.                         else
  511.                             $(this.m_voteupID).removeClassName('active');
  512.                     }
  513.                 } else
  514.                     this.OnFailureDisplayError(transport);
  515.             },
  516.  
  517.             OnFailureDisplayError: function (transport) {
  518.                 this.DisplayError($('commentthread_' + this.m_strName + '_entry_error'), transport);
  519.             },
  520.  
  521.             DisplayError: function (elError, transport) {
  522.                 let strMessage = 'Lo sentimos, ha ocurrido un error: ';
  523.                 if (transport.responseJSON && transport.responseJSON.error)
  524.                     strMessage += transport.responseJSON.error;
  525.                 else
  526.                     strMessage += 'Se ha producido un error de comunicación con la red. Inténtalo de nuevo más tarde.';
  527.  
  528.                 elError.update(strMessage);
  529.                 elError.show();
  530.             },
  531.  
  532.             OnResponseRenderComments: function (eRenderReason, nAjaxSequenceNumber, transport) {
  533.                 if (this.m_nRenderAjaxSequenceNumber !== nAjaxSequenceNumber)
  534.                     return;
  535.  
  536.                 if (transport.responseJSON) {
  537.                     const response = transport.responseJSON;
  538.                     this.m_cTotalCount = response.total_count;
  539.                     this.m_cMaxPages = Math.ceil(response.total_count / response.pagesize);
  540.                     this.m_iCurrentPage = Math.floor(response.start / response.pagesize);
  541.  
  542.                     if (response.comments_raw)
  543.                         this.m_rgRawCommentCache = response.comments_raw;
  544.  
  545.                     if (this.m_commentcountID && $(this.m_commentcountID))
  546.                         $(this.m_commentcountID).innerHTML = this.m_cTotalCount;
  547.  
  548.                     if (this.m_cTotalCount <= response.start && this.m_iCurrentPage > 0) {
  549.                         // this page is no logner valid, flip back a page (deferred so that the AJAX handler exits and reset m_bLoading)
  550.                         this.GoToPage.bind(this, this.m_iCurrentPage - 1).defer();
  551.                         return;
  552.                     }
  553.  
  554.                     if (this.m_bTrackNavigation && window.history && window.history.pushState) {
  555.                         const params = window.location.search.length ? $J.deparam(window.location.search.substr(1)) : {};
  556.                         if ((!params['ctp'] && this.m_iCurrentPage !== 0) || (params['ctp'] && params['ctp'] !== this.m_iCurrentPage + 1)) {
  557.                             let fnStateUpdate = window.history.pushState.bind(window.history);
  558.                             let url = UpdateParameterInCurrentURL('ctp', this.m_iCurrentPage == 0 ? null : this.m_iCurrentPage + 1, ['tscn']);
  559.                             if (eRenderReason === CCommentThread.RENDER_GOTOPAGE_HASHCHANGE || eRenderReason === CCommentThread.RENDER_GOTOCOMMENT_HASHCHANGE) {
  560.                                 fnStateUpdate = window.history.replaceState.bind(window.history);
  561.                                 if (eRenderReason === CCommentThread.RENDER_GOTOCOMMENT_HASHCHANGE)
  562.                                     url += window.location.hash;
  563.                             }
  564.                             fnStateUpdate({comment_thread_page: this.m_iCurrentPage}, '', url);
  565.                         }
  566.                     }
  567.  
  568.                     this.DoTransitionToNewPosts(response, eRenderReason);
  569.  
  570.                     // if we're viewing the most recent page of comments, refresh notifications
  571.                     if ((!this.m_rgCommentData['oldestfirst'] && this.m_iCurrentPage === 0) ||
  572.                         this.m_rgCommentData['oldestfirst'] && (this.m_iCurrentPage + 1) * this.m_cPageSize > this.m_cTotalCount) {
  573.                         RefreshNotificationArea();
  574.                     }
  575.  
  576.                     this.UpdatePagingDisplay();
  577.                 }
  578.             },
  579.  
  580.             DoTransitionToNewPosts: function (response, eRenderReason) {
  581.                 const strNewHTML = response.comments_html;
  582.  
  583.                 const elPosts = $('commentthread_' + this.m_strName + '_posts');
  584.                 const elContainer = $('commentthread_' + this.m_strName + '_postcontainer');
  585.                 elContainer.style.height = elContainer.getHeight() + 'px';
  586.                 elContainer.style.overflow = 'hidden';
  587.  
  588.                 const bNewPost = (eRenderReason === CCommentThread.RENDER_NEWPOST);
  589.  
  590.                 if (bNewPost && this.m_cTotalCount <= this.m_cPageSize && !this.m_rgCommentData['oldestfirst'] && !this.m_rgCommentData['newestfirstpagination']) {
  591.                     elContainer.style.position = 'relative';
  592.                     elPosts.style.position = 'absolute';
  593.                     elPosts.style.left = '0px';
  594.                     elPosts.style.right = '0px';
  595.                     elPosts.style.bottom = '0px';
  596.                 } else {
  597.                     elPosts.style.position = 'static';
  598.                 }
  599.  
  600.                 elPosts.update(strNewHTML);
  601.  
  602.                 ScrollToIfNotInView($('commentthread_' + this.m_strName + '_area'), 40, 20);
  603.  
  604.                 if (elContainer.effect)
  605.                     elContainer.effect.cancel();
  606.  
  607.                 (function () {
  608.                     elContainer.effect = new Effect.Morph(elContainer, {
  609.                         style: 'height: ' + elPosts.getHeight() + 'px',
  610.                         duration: 0.25,
  611.                         afterFinish: function () {
  612.                             elPosts.style.position = 'static';
  613.                             elContainer.style.height = 'auto';
  614.                             elContainer.style.overflow = '';
  615.                         }
  616.                     });
  617.                 }).defer();
  618.             },
  619.  
  620.             UpdatePagingDisplay: function () {
  621.                 let iPage;
  622.                 let lastPageLink;
  623.                 let firstPageLink;
  624.                 let cPageLinksAheadBehind;
  625.                 if (!this.m_bHasPaging)
  626.                     return;
  627.  
  628.                 const strPrefix = 'commentthread_' + this.m_strName;
  629.  
  630.                 // this element not displayed on the forum topic page
  631.                 $(strPrefix + '_totalcount') && $(strPrefix + '_totalcount').update(v_numberformat(this.m_cTotalCount));
  632.  
  633.                 const rgPagingControls = [strPrefix + '_page', strPrefix + '_fpage'];
  634.                 for (let i = 0; i < rgPagingControls.length; i++) {
  635.                     const strPagePrefix = rgPagingControls[i];
  636.  
  637.                     // these elements are displayed on the forum topic page
  638.                     $(strPagePrefix + 'total') && $(strPagePrefix + 'total').update(v_numberformat(this.m_cTotalCount));
  639.                     $(strPagePrefix + 'start') && $(strPagePrefix + 'start').update(v_numberformat(this.m_iCurrentPage * this.m_cPageSize + 1));
  640.                     $(strPagePrefix + 'end') && $(strPagePrefix + 'end').update(Math.min((this.m_iCurrentPage + 1) * this.m_cPageSize, this.m_cTotalCount));
  641.  
  642.                     if ($(strPagePrefix + 'ctn')) {
  643.                         if (this.m_cTotalCount > 0)
  644.                             $(strPagePrefix + 'ctn').show();
  645.                         else
  646.                             $(strPagePrefix + 'ctn').hide();
  647.                     }
  648.  
  649.  
  650.                     if (this.m_cMaxPages <= 1) {
  651.                         $(strPagePrefix + 'controls').hide();
  652.                     } else {
  653.                         $(strPagePrefix + 'controls').show();
  654.                         if (this.m_iCurrentPage > 0) {
  655.                             $(strPagePrefix + 'btn_prev').removeClassName('disabled');
  656.                             if (this.m_bTrackNavigation)
  657.                                 $(strPagePrefix + 'btn_prev').href = UpdateParameterInCurrentURL('ctp',
  658.                                     this.m_iCurrentPage === 1 ? null : this.m_iCurrentPage,
  659.                                     ['tscn']);
  660.                         } else {
  661.                             $(strPagePrefix + 'btn_prev').addClassName('disabled');
  662.                             if (this.m_bTrackNavigation)
  663.                                 $(strPagePrefix + 'btn_prev').href = 'javascript:void(0);';
  664.                         }
  665.  
  666.                         if (this.m_iCurrentPage < this.m_cMaxPages - 1) {
  667.                             $(strPagePrefix + 'btn_next').removeClassName('disabled');
  668.                             if (this.m_bTrackNavigation)
  669.                                 $(strPagePrefix + 'btn_next').href = UpdateParameterInCurrentURL('ctp', this.m_iCurrentPage + 2, ['tscn']);
  670.                         } else {
  671.                             $(strPagePrefix + 'btn_next').addClassName('disabled');
  672.                             if (this.m_bTrackNavigation)
  673.                                 $(strPagePrefix + 'btn_next').href = 'javascript:void(0);';
  674.                         }
  675.  
  676.                         const elPageLinks = $(strPagePrefix + 'links');
  677.                         elPageLinks.update('');
  678.                         // we always show first, last, + 3 page links closest to current page
  679.                         cPageLinksAheadBehind = 2;
  680.                         firstPageLink = Math.max(this.m_iCurrentPage - cPageLinksAheadBehind, 1);
  681.                         lastPageLink = Math.min(this.m_iCurrentPage + (cPageLinksAheadBehind * 2) + (firstPageLink - this.m_iCurrentPage), this.m_cMaxPages - 2);
  682.  
  683.                         if (lastPageLink - this.m_iCurrentPage < cPageLinksAheadBehind)
  684.                             firstPageLink = Math.max(this.m_iCurrentPage - (cPageLinksAheadBehind * 2) + (lastPageLink - this.m_iCurrentPage), 1);
  685.  
  686.                         this.AddPageLink(elPageLinks, 0);
  687.                         if (firstPageLink !== 1)
  688.                             elPageLinks.insert(' ... ');
  689.  
  690.                         for (iPage = firstPageLink; iPage <= lastPageLink; iPage++) {
  691.                             this.AddPageLink(elPageLinks, iPage);
  692.                         }
  693.  
  694.                         if (lastPageLink !== this.m_cMaxPages - 2)
  695.                             elPageLinks.insert(' ... ');
  696.                         this.AddPageLink(elPageLinks, this.m_cMaxPages - 1);
  697.                     }
  698.  
  699.                     // update the dropdown list with the total.
  700.                     const $DropdownCtn = $J('#' + strPagePrefix + 'dropdown');
  701.                     let $Select = $DropdownCtn.children('select');
  702.  
  703.                     if (!$Select.length) {
  704.                         $Select = $J('<select/>');
  705.                         const _this = this;
  706.                         $Select.change(function () {
  707.                             const $Select = $J(this);
  708.                             _this.GoToPage($Select.val());
  709.                         });
  710.                         $DropdownCtn.append($Select);
  711.                     }
  712.                     $Select.empty();
  713.                     var fnAddPageDropdown = function (iDropdownPage) {
  714.                         $Select.append($J('<option/>', {'value': iDropdownPage}).text(iDropdownPage >= 999 ? v_numberformat(iDropdownPage + 1) : iDropdownPage + 1));
  715.                     };
  716.                     cPageLinksAheadBehind = 10;
  717.                     firstPageLink = Math.max(this.m_iCurrentPage - cPageLinksAheadBehind, 1);
  718.                     lastPageLink = Math.min(this.m_iCurrentPage + (cPageLinksAheadBehind * 2) + (firstPageLink - this.m_iCurrentPage), this.m_cMaxPages - 2);
  719.                     if (lastPageLink - this.m_iCurrentPage < cPageLinksAheadBehind)
  720.                         firstPageLink = Math.max(this.m_iCurrentPage - (cPageLinksAheadBehind * 2) + (lastPageLink - this.m_iCurrentPage), 1);
  721.                     fnAddPageDropdown(0);
  722.                     if (firstPageLink !== 1)
  723.                         $Select.append($J('<option/>', {'value': '', 'disabled': 1}).text('...'));
  724.                     for (iPage = firstPageLink; iPage <= lastPageLink; iPage++) {
  725.                         fnAddPageDropdown(iPage);
  726.                     }
  727.                     if (lastPageLink !== this.m_cMaxPages - 2)
  728.                         $Select.append($J('<option/>', {'value': '', 'disabled': 1}).text('...'));
  729.                     fnAddPageDropdown(this.m_cMaxPages - 1);
  730.  
  731.                     $Select.val(this.m_iCurrentPage);
  732.                 }
  733.  
  734.                 this.m_cDropdownPages = this.m_cMaxPages;
  735.             },
  736.  
  737.             AddPageLink: function (elPageLinks, iPage) {
  738.                 let el;
  739.                 if (this.m_bTrackNavigation)
  740.                     el = new Element('a', {
  741.                         'class': 'commentthread_pagelink',
  742.                         'href': UpdateParameterInCurrentURL('ctp', iPage + 1, ['tscn'])
  743.                     });
  744.                 else
  745.                     el = new Element('span', {'class': 'commentthread_pagelink'});
  746.  
  747.                 el.update((iPage + 1));
  748.  
  749.                 const fnGoToPage = this.GoToPage.bind(this, iPage);
  750.  
  751.                 if (iPage === this.m_iCurrentPage)
  752.                     el.addClassName('active');
  753.                 else
  754.                     el.observe('click', function (e) {
  755.                         e.stop();
  756.                         fnGoToPage();
  757.                     });
  758.  
  759.                 elPageLinks.insert(el);
  760.                 elPageLinks.insert(' ');
  761.             },
  762.  
  763.             Subscribe: function (fnOnSuccess, fnOnFail) {
  764.                 const params = this.ParametersWithDefaults();
  765.                 this.m_$SubscribeCheckbox.addClass('waiting');
  766.  
  767.                 const _this = this;
  768.                 $J.post(this.GetActionURL('subscribe'), params)
  769.                     .done(function () {
  770.                         _this.OnSubscriptionChange(true, fnOnSuccess);
  771.                     })
  772.                     .fail(fnOnFail)
  773.                     .always(function () {
  774.                         _this.m_$SubscribeCheckbox.removeClass('waiting')
  775.                     });
  776.             },
  777.  
  778.             Unsubscribe: function (fnOnSuccess, fnOnFail) {
  779.                 const params = this.ParametersWithDefaults();
  780.                 this.m_$SubscribeCheckbox.addClass('waiting');
  781.  
  782.                 const _this = this;
  783.                 $J.post(this.GetActionURL('unsubscribe'), params)
  784.                     .done(function () {
  785.                         _this.OnSubscriptionChange(false, fnOnSuccess);
  786.                     })
  787.                     .fail(fnOnFail)
  788.                     .always(function () {
  789.                         _this.m_$SubscribeCheckbox.removeClass('waiting')
  790.                     });
  791.             },
  792.  
  793.             m_rgSubscriptionUpdateHandlers: null,
  794.             OnSubscriptionChange: function (bSubscribed, fnProxy, transport) {
  795.                 this.m_bSubscribed = bSubscribed;
  796.  
  797.                 if (fnProxy)
  798.                     fnProxy(transport);
  799.  
  800.                 this.UpdateSubscriptionDisplay();
  801.             },
  802.  
  803.             UpdateSubscriptionDisplay: function () {
  804.                 if (this.m_rgSubscriptionUpdateHandlers && this.m_rgSubscriptionUpdateHandlers.length) {
  805.                     for (let i = 0; i < this.m_rgSubscriptionUpdateHandlers.length; i++)
  806.                         (this.m_rgSubscriptionUpdateHandlers[i])();
  807.                 }
  808.  
  809.                 if (this.m_bSubscribed)
  810.                     this.m_$SubscribeCheckbox.addClass('checked');
  811.                 else
  812.                     this.m_$SubscribeCheckbox.removeClass('checked');
  813.  
  814.                 const elForumSubscribe = $('forum_subscribe_' + this.m_rgCommentData['feature2']);
  815.                 const elForumUnsubscribe = $('forum_unsubscribe_' + this.m_rgCommentData['feature2']);
  816.                 if (elForumSubscribe && elForumUnsubscribe) {
  817.                     if (this.m_bSubscribed) {
  818.                         elForumSubscribe.hide();
  819.                         elForumUnsubscribe.show();
  820.                     } else {
  821.                         elForumSubscribe.show();
  822.                         elForumUnsubscribe.hide();
  823.                     }
  824.                 }
  825.             }
  826.         });
  827.         CCommentThread.RENDER_NEWPOST = 1;
  828.         CCommentThread.RENDER_GOTOPAGE = 2;
  829.         CCommentThread.RENDER_GOTOCOMMENT = 3;
  830.         CCommentThread.RENDER_DELETEDPOST = 4;
  831.         CCommentThread.RENDER_GOTOPAGE_HASHCHANGE = 5;
  832.         CCommentThread.RENDER_GOTOCOMMENT_HASHCHANGE = 6;
  833.  
  834.         // static accessor
  835.         CCommentThread.DeleteComment = function( id, gidcomment )
  836.         {
  837.             if ( g_rgCommentThreads[id] )
  838.                 g_rgCommentThreads[id].DeleteComment( gidcomment );
  839.         };
  840.         CCommentThread.UnDeleteComment = function( id, gidcomment )
  841.         {
  842.             if ( g_rgCommentThreads[id] )
  843.                 g_rgCommentThreads[id].DeleteComment( gidcomment, true );
  844.         };
  845.         CCommentThread.MarkSpam = function( id, gidcomment, authorAccountID )
  846.         {
  847.             if ( g_rgCommentThreads[id] )
  848.                 g_rgCommentThreads[id].MarkSpam( gidcomment, authorAccountID );
  849.         };
  850.         CCommentThread.ClearContentCheckFlag = function( id, gidcomment )
  851.         {
  852.             if ( g_rgCommentThreads[id] )
  853.                 g_rgCommentThreads[id].ClearContentCheckFlag( gidcomment );
  854.         };
  855.         // static accessor
  856.         CCommentThread.EditComment = function( id, gidcomment )
  857.         {
  858.             if ( g_rgCommentThreads[id] )
  859.                 g_rgCommentThreads[id].DisplayEditComment( gidcomment );
  860.         };
  861.         // static accessor
  862.         CCommentThread.VoteUp = function( id )
  863.         {
  864.             if ( g_rgCommentThreads[id] )
  865.                 g_rgCommentThreads[id].VoteUp();
  866.         };
  867.         // static accessor
  868.         CCommentThread.UpdateAnswer = function( id, gidcomment, bExisting )
  869.         {
  870.             if ( g_rgCommentThreads[id] )
  871.                 g_rgCommentThreads[id].UpdateAnswer( gidcomment, bExisting );
  872.         };
  873.         CCommentThread.FormattingHelpPopup = function( strCommentThreadType )
  874.         {
  875.             $J.get( 'https://steamcommunity.com/comment/' + strCommentThreadType + '/formattinghelp', {ajax:1} ).done( function(data) {
  876.                 ShowAlertDialog( 'Formato de texto', data );
  877.             });
  878.         };
  879.         CCommentThread.ShowDeletedComment = function( id, gidcomment )
  880.         {
  881.             const elComment = $('comment_' + gidcomment);
  882.             var elDeletedComment = $('deleted_comment_' + gidcomment );
  883.             elComment.show();
  884.             elDeletedComment.hide();
  885.         };
  886.         CCommentThread.HideAndReport = function( id, gidcomment, bHide )
  887.         {
  888.             if ( g_rgCommentThreads[id] )
  889.                 g_rgCommentThreads[id].HideAndReport( gidcomment, bHide );
  890.         };
  891.         CCommentThread.ShowHiddenComment = function( id, gidcomment )
  892.         {
  893.             const elComment = $('comment_' + gidcomment);
  894.             const elHiddenComment = $('hidden_comment_' + gidcomment);
  895.             elComment.show();
  896.             elHiddenComment.hide();
  897.         };
  898.  
  899.  
  900.         function levenshtein( a, b )
  901.         {
  902.             const alen = a.length;
  903.             const blen = b.length;
  904.             if (alen === 0) return blen;
  905.             if (blen === 0) return alen;
  906.             let tmp, i, j, prev, val, row, ma, mb, mc, md, bprev;
  907.  
  908.             if (alen > blen) {
  909.                 tmp = a;
  910.                 a = b;
  911.                 b = tmp;
  912.             }
  913.  
  914.             row = new Int8Array(alen+1);
  915.             // init the row
  916.             for (i = 0; i <= alen; i++) {
  917.                 row[i] = i;
  918.             }
  919.  
  920.             // fill in the rest
  921.             for (i = 1; i <= blen; i++) {
  922.                 prev = i;
  923.                 bprev = b[i - 1]
  924.                 for (j = 1; j <= alen; j++) {
  925.                     if (bprev === a[j - 1]) {
  926.                         val = row[j-1];
  927.                     } else {
  928.                         ma = prev+1;
  929.                         mb = row[j]+1;
  930.                         mc = ma - ((ma - mb) & ((mb - ma) >> 7));
  931.                         md = row[j-1]+1;
  932.                         val = mc - ((mc - md) & ((md - mc) >> 7));
  933.                     }
  934.                     row[j - 1] = prev;
  935.                     prev = val;
  936.                 }
  937.                 row[alen] = prev;
  938.             }
  939.             return row[alen];
  940.         }
  941.  
  942.  
  943.         let CGameSelector = Class.create({
  944.             bHaveSuggestions: false,
  945.             elInput: null,
  946.             elSuggestionsCtn: null,
  947.             elSuggestions: null,
  948.             fnOnClick: null,
  949.  
  950.             elFocus: null,
  951.             nAppIDFocus: 0,
  952.             ShowSuggestions: function () {
  953.                 if (!this.elSuggestionsCtn.visible() && this.bHaveSuggestions) {
  954.                     AlignMenu(this.elInput, this.elSuggestionsCtn, 'left', 'bottom', true);
  955.                     ShowWithFade(this.elSuggestionsCtn);
  956.                 }
  957.             },
  958.  
  959.             HideSuggestions: function () {
  960.                 HideWithFade(this.elSuggestionsCtn);
  961.             },
  962.             ReceiveGameSelectResponse: function (value, transport) {
  963.                 if (this.elInput.value === value) {
  964.  
  965.                     const json = transport.responseJSON;
  966.                     this.UpdateListWithOptions(json);
  967.  
  968.                 }
  969.             },
  970.  
  971.             UpdateListWithOptions: function (rgOptions) {
  972.                 this.elSuggestions.update('');
  973.                 this.elFocus = null;
  974.                 if (rgOptions && rgOptions.length) {
  975.                     for (let i = 0; i < rgOptions.length; i++) {
  976.                         const elSuggestion = new Element('div', {'class': 'game_suggestion popup_menu_item'});
  977.                         $J(elSuggestion).text(rgOptions[i].name);
  978.  
  979.                         elSuggestion.appid = rgOptions[i].appid;
  980.                         elSuggestion.fnOnSelect = this.fnOnClick.bind(null, this, rgOptions[i]);
  981.                         elSuggestion.observe('click', elSuggestion.fnOnSelect);
  982.                         elSuggestion.observe('mouseover', this.SetFocus.bind(this, elSuggestion));
  983.  
  984.                         this.elSuggestions.insert({bottom: elSuggestion});
  985.  
  986.                         if (this.nAppIDFocus === elSuggestion.appid)
  987.                             this.SetFocus(elSuggestion);
  988.                     }
  989.                     this.bHaveSuggestions = true;
  990.                     this.ShowSuggestions();
  991.                 } else {
  992.                     this.bHaveSuggestions = false;
  993.                     this.HideSuggestions();
  994.                 }
  995.             },
  996.  
  997.  
  998.             SetFocus: function (elSuggestion) {
  999.                 if (this.elFocus)
  1000.                     this.elFocus.removeClassName('focus');
  1001.  
  1002.                 this.elFocus = elSuggestion;
  1003.                 this.nAppIDFocus = elSuggestion.appid;
  1004.                 elSuggestion.addClassName('focus');
  1005.             }
  1006.  
  1007.  
  1008.         });
  1009.         Class.create(CGameSelector, {
  1010.             OnGameSelectTextEntry: function (elInput, value) {
  1011.                 if (value) {
  1012.                     new Ajax.Request('https://steamcommunity.com/workshop/ajaxfindworkshops/?searchText=' + encodeURIComponent(value), {
  1013.                         method: 'get',
  1014.                         onSuccess: this.ReceiveGameSelectResponse.bind(this, value)
  1015.                     });
  1016.                 } else {
  1017.                     this.elSuggestions.update('');
  1018.                     this.bHaveSuggestions = false;
  1019.                 }
  1020.             }
  1021.         });
  1022.         CGameSelectorOwnedGames = Class.create( CGameSelector, {
  1023.  
  1024.             m_bOwnedGamesReady: false,
  1025.  
  1026.             initialize: function( $super, elInput, elSuggestionsCtn, elSuggestions, fnOnClick )
  1027.             {
  1028.                 $super( elInput, elSuggestionsCtn, elSuggestions, fnOnClick );
  1029.                 CGameSelectorOwnedGames.LoadOwnedGames( this.OnOwnedGamesReady.bind( this ) );
  1030.             },
  1031.  
  1032.             OnOwnedGamesReady: function()
  1033.             {
  1034.                 this.m_bOwnedGamesReady = true;
  1035.                 this.OnGameSelectTextEntry( this.elInput, this.elInput.value );
  1036.             },
  1037.  
  1038.             OnGameSelectTextEntry: function( elInput, value )
  1039.             {
  1040.                 if ( value )
  1041.                 {
  1042.                     if ( !this.m_bOwnedGamesReady )
  1043.                     {
  1044.                         this.elSuggestions.update( '<div style="text-align: center; width: 200px; padding: 5px 0;"><img src="https://community.cloudflare.steamstatic.com/public/images/login/throbber.gif"></div>' );
  1045.                         this.bHaveSuggestions = true;
  1046.                         this.ShowSuggestions();
  1047.                     }
  1048.                     else
  1049.                     {
  1050.                         const strSearchString = value.toLocaleLowerCase();
  1051.                         const rgTerms = strSearchString.split(' ');
  1052.                         const rgRegex = [];
  1053.                         for (let iTerm = 0; iTerm < rgTerms.length; iTerm++ )
  1054.                         {
  1055.                             const term = V_EscapeRegExp(rgTerms[iTerm], 'i');
  1056.                             rgRegex.push( new RegExp( term ) );
  1057.                         }
  1058.                         let rgMatchingGames = [];
  1059.                         for (let i = 0; i < CGameSelectorOwnedGames.s_rgOwnedGames.length; i++ )
  1060.                         {
  1061.                             const game = CGameSelectorOwnedGames.s_rgOwnedGames[i];
  1062.                             if ( !game.name || !game.name_normalized )
  1063.                                 continue;
  1064.  
  1065.                             let bMatch = true;
  1066.                             for (let iRegex = 0; iRegex < rgRegex.length; iRegex++ )
  1067.                             {
  1068.                                 const regex = rgRegex[iRegex];
  1069.                                 if ( !game.name_normalized.match( regex ) && !game.name.match(regex) )
  1070.                                 {
  1071.                                     bMatch = false;
  1072.                                     break;
  1073.                                 }
  1074.                             }
  1075.                             if ( bMatch )
  1076.                             {
  1077.                                 game.levenshtein = levenshtein( game.name.toLocaleLowerCase(), strSearchString );
  1078.                                 rgMatchingGames.push( game );
  1079.                             }
  1080.                         }
  1081.  
  1082.                         rgMatchingGames.sort( function( a, b ) {
  1083.                             if ( a.levenshtein === b.levenshtein )
  1084.                             {
  1085.                                 return a.name.localeCompare( b.name );
  1086.                             }
  1087.                             return a.levenshtein - b.levenshtein;
  1088.                         } );
  1089.  
  1090.                         rgMatchingGames = rgMatchingGames.slice( 0, 10 );
  1091.  
  1092.                         this.UpdateListWithOptions( rgMatchingGames );
  1093.                     }
  1094.                 }
  1095.                 else
  1096.                 {
  1097.                     this.elSuggestions.update('');
  1098.                     this.bHaveSuggestions = false;
  1099.                 }
  1100.             }
  1101.  
  1102.         } );
  1103.  
  1104.         CGameSelectorOwnedGames.s_rgOwnedGames = null;
  1105.         CGameSelectorOwnedGames.s_bLoadInFlight = false;
  1106.         CGameSelectorOwnedGames.s_rgOwnedGamesReadyCallbacks = [];
  1107.         CGameSelectorOwnedGames.s_rgParams = {};
  1108.         CGameSelectorOwnedGames.AreOwnedGamesLoaded = function()
  1109.         {
  1110.             return CGameSelectorOwnedGames.s_rgOwnedGames != null;
  1111.         };
  1112.         CGameSelectorOwnedGames.NormalizeGameNames = function( rgOwnedGames )
  1113.         {
  1114.             const regexNormalize = new RegExp(/[\s.-:!?,']+/g);
  1115.             for(let i=0; i < rgOwnedGames.length; i++ )
  1116.             {
  1117.                 const game = rgOwnedGames[i];
  1118.                 game.name_normalized = game.name.replace( regexNormalize, '' ).toLowerCase();
  1119.             }
  1120.         };
  1121.         CGameSelectorOwnedGames.LoadOwnedGames = function( fnCallback )
  1122.         {
  1123.             if ( !CGameSelectorOwnedGames.AreOwnedGamesLoaded() )
  1124.             {
  1125.                 CGameSelectorOwnedGames.s_rgOwnedGamesReadyCallbacks.push( fnCallback );
  1126.  
  1127.                 if ( CGameSelectorOwnedGames.s_bLoadInFlight )
  1128.                     return;
  1129.  
  1130.                 CGameSelectorOwnedGames.s_bLoadInFlight = true;
  1131.                 const rgParams = CGameSelectorOwnedGames.s_rgParams;
  1132.                 rgParams['sessionid'] = g_sessionID;
  1133.  
  1134.                 new Ajax.Request( 'https://steamcommunity.com/actions/GetOwnedApps/', {
  1135.                     method: 'get',
  1136.                     parameters: rgParams,
  1137.                     onSuccess: function( transport )
  1138.                     {
  1139.                         CGameSelectorOwnedGames.s_rgOwnedGames = transport.responseJSON || [];
  1140.  
  1141.                         CGameSelectorOwnedGames.NormalizeGameNames( CGameSelectorOwnedGames.s_rgOwnedGames );
  1142.                     },
  1143.                     onFailure: function()
  1144.                     {
  1145.                         CGameSelectorOwnedGames.s_rgOwnedGames = [];
  1146.                     },
  1147.                     onComplete: function()
  1148.                     {
  1149.                         for ( var i = 0; i < CGameSelectorOwnedGames.s_rgOwnedGamesReadyCallbacks.length; i++ )
  1150.                         {
  1151.                             CGameSelectorOwnedGames.s_rgOwnedGamesReadyCallbacks[i]();
  1152.                         }
  1153.                     }
  1154.                 } );
  1155.             }
  1156.             else
  1157.             {
  1158.                 // data is already ready
  1159.                 fnCallback();
  1160.             }
  1161.         };
  1162.  
  1163.         CGameSelectorProfileShowcaseGames = Class.create( CGameSelectorOwnedGames, {
  1164.             initialize: function( $super, elInput, elSuggestionsCtn, elSuggestions, fnOnClick, rgFilteredGames )
  1165.             {
  1166.                 CGameSelectorOwnedGames.s_rgParams['for_showcase'] = 1;
  1167.                 if ( rgFilteredGames )
  1168.                 {
  1169.                     CGameSelectorOwnedGames.s_rgOwnedGames = rgFilteredGames;
  1170.                     CGameSelectorOwnedGames.NormalizeGameNames( CGameSelectorOwnedGames.s_rgOwnedGames );
  1171.                 }
  1172.                 $super( elInput, elSuggestionsCtn, elSuggestions, fnOnClick );
  1173.             },
  1174.         } );
  1175.         function addEvent(el, ev, fn, useCapture)
  1176.         {
  1177.             if(el.addEventListener)
  1178.             {
  1179.                 el.addEventListener(ev, fn, useCapture);
  1180.             }
  1181.             else if(el.attachEvent)
  1182.             {
  1183.                 return el.attachEvent("on" + ev, fn);
  1184.             }
  1185.             else
  1186.             {
  1187.                 el["on"+ev] = fn;
  1188.             }
  1189.         }
  1190.         let updateInProgress = false;
  1191.         function winDim(wh, vs)
  1192.         {
  1193.             if(window.innerWidth) // most browsers - ff, safari, etc
  1194.             {
  1195.                 return (wh === 'w' ? (vs === 'v' ? window.innerWidth : window.pageXOffset) : (vs === 'v' ? window.innerHeight : window.pageYOffset));
  1196.             }
  1197.             else if(document.documentElement && document.documentElement.clientWidth) // ie strict
  1198.             {
  1199.                 return (wh === 'w' ? (vs === 'v' ? document.documentElement.clientWidth : document.documentElement.scrollLeft) : (vs === 'v' ? document.documentElement.clientHeight : document.documentElement.scrollTop));
  1200.             }
  1201.             else // ie normal
  1202.             {
  1203.                 return (wh === 'w' ? (vs === 'v' ? document.body.clientWidth : document.body.scrollLeft) : (vs === 'v' ? document.body.clientHeight : document.body.scrollTop));
  1204.             }
  1205.         }
  1206.         function getPopPos(e, pw, ph, offset)
  1207.         {
  1208.             const w = winDim('w', 'v');
  1209.             const h = winDim('h', 'v');
  1210.             const sl = winDim('w', 's');
  1211.             const st = winDim('h', 's');
  1212.             // mouse x/y within viewport
  1213.             const vmX = e.clientX;
  1214.             const vmY = e.clientY;
  1215.             // mouse x/y within document
  1216.             const smX = vmX + sl;
  1217.             const smY = vmY + st;
  1218.             const l = (pw > vmX) ? (smX + offset) : (smX - pw - offset);
  1219.             const t = (ph > vmY) ? (smY + offset) : (smY - ph - offset);
  1220.             return [t, l];
  1221.         }
  1222.         function tooltipDestroy(go)
  1223.         {
  1224.             if ( go !== 1 )
  1225.             {
  1226.                 setTimeout( "tooltipDestroy(1)", 10 );
  1227.             }
  1228.             else
  1229.             {
  1230.                 var ttEl = document.getElementById('tooltip');
  1231.                 if(ttEl)
  1232.                 {
  1233.                     ttEl.parentNode.removeChild(ttEl);
  1234.                 }
  1235.             }
  1236.         }
  1237.  
  1238.         function getElement( elementId )
  1239.         {
  1240.             var elem;
  1241.             if ( document.getElementById ) // standard compliant method
  1242.                 elem = document.getElementById( elementId );
  1243.             else if ( document.all ) // old msie versions
  1244.                 elem = document.all[ elementId ];
  1245.             else
  1246.                 elem = false;
  1247.  
  1248.             return elem;
  1249.         }
  1250.  
  1251.         function setImage( elementId, strImage )
  1252.         {
  1253.             var imageElem = getElement( elementId );
  1254.             if ( !imageElem )
  1255.                 return;
  1256.  
  1257.             imageElem.src = strImage;
  1258.         }
  1259.  
  1260.         const gShareOnSteamDialog = null;
  1261.  
  1262.         function CloseShareOnSteamDialog()
  1263.         {
  1264.             gShareOnSteamDialog.Dismiss();
  1265.         }
  1266.     }
  1267.     function forum() {
  1268.  
  1269.         let g_rgForumTopics = {};
  1270.         let g_rgForumTopicCommentThreads = {};
  1271.  
  1272.         const init = function InitializeForumTopic( rgForumData, url, gidTopic, rgRawData )
  1273.         {
  1274.             g_rgForumTopics[ gidTopic ] = new CForumTopic( rgForumData, url, gidTopic, rgRawData );
  1275.         }
  1276.  
  1277.         function Forum_DeleteTopic( gidTopic )
  1278.         {
  1279.             if ( g_rgForumTopics[ gidTopic ] )
  1280.             {
  1281.                 var Topic = g_rgForumTopics[ gidTopic ];
  1282.                 if ( Topic.BCheckPermission( 'can_moderate' ) )
  1283.                 {
  1284.                     Topic.Delete();
  1285.                 }
  1286.                 else
  1287.                 {
  1288.                     ShowConfirmDialog('Eliminar hilo ',
  1289.                         '¿Seguro que quieres eliminar este hilo? ' +
  1290.                         'Solo un moderador puede deshacer esta acción.',
  1291.                         'Eliminar hilo'
  1292.                     ).done( function() {
  1293.                         Topic.Delete();
  1294.                     });
  1295.                 }
  1296.             }
  1297.         }
  1298.  
  1299.         function Forum_PurgeTopic( gidTopic )
  1300.         {
  1301.             ShowConfirmDialog('Eliminar permanentemente ',
  1302.                 '¿Seguro que deseas eliminar permanentemente este tema? Esta acción no podrá deshacerse.',
  1303.                 'Eliminar permanentemente'
  1304.             ).done( function() {
  1305.                 Forum_SetTopicFlag( gidTopic, 'purge', true );
  1306.             });
  1307.         }
  1308.  
  1309.  
  1310.         function Forum_ClearContentCheckResult( gidTopic )
  1311.         {
  1312.             ShowConfirmDialog('Quitar marca de contenido bloqueable ',
  1313.                 '¿Seguro que quieres quitar la marca de contenido bloqueable a este tema? Esto es irreversible.',
  1314.                 'Quitar marca de contenido bloqueable'
  1315.             ).done( function() {
  1316.                 Forum_SetTopicFlag( gidTopic, 'clearcontentcheckresult', true );
  1317.             });
  1318.         }
  1319.  
  1320.         function Forum_BanCommenters( gidTopic )
  1321.         {
  1322.             ShowConfirmDialog('Bloquear comentaristas ',
  1323.                 '¿Seguro que quieres bloquear a los usuarios que han comentado más de 5 veces en este tema? Será difícil revertir esto.',
  1324.                 'Bloquear comentaristas'
  1325.             ).done( function() {
  1326.                 Forum_SetTopicFlag( gidTopic, 'bancommenters', true );
  1327.             });
  1328.         }
  1329.  
  1330.         function Forum_MarkSpam( gidTopic )
  1331.         {
  1332.             ShowConfirmDialog('Marcar como spam ',
  1333.                 'Esto eliminará los hilos seleccionados y bloqueará a los autores en el foro. ¿Seguro que es lo que quieres hacer?',
  1334.                 'Eliminar spam'
  1335.             ).done( function() {
  1336.                 Forum_SetTopicFlag( gidTopic, 'markspam', true );
  1337.             });
  1338.         }
  1339.  
  1340.  
  1341.         function Forum_ReportPost( gidTopic, author, gidComment )
  1342.         {
  1343.             if ( g_rgForumTopics[ gidTopic ] )
  1344.                 g_rgForumTopics[ gidTopic ].ReportPost( author, gidComment );
  1345.         }
  1346.  
  1347.         // block a user, with confirmation
  1348.         function Forum_BlockUser( author, strPersonaName )
  1349.         {
  1350.             ShowConfirmDialog( 'Bloquear toda comunicación',
  1351.                 'Estás a punto de bloquear toda comunicación con %s.'.replace( /%s/, strPersonaName ),
  1352.                 'Sí, bloquéala'
  1353.             ).done( function() {
  1354.                 $J.post(
  1355.                     'https://steamcommunity.com/actions/BlockUserAjax',
  1356.                     {sessionID: g_sessionID, steamid: author, block: 1 }
  1357.                 ).done( function() {
  1358.                     ShowAlertDialog( 'Bloquear toda comunicación',
  1359.                         'Has bloqueado todas las comunicaciones con este jugador.'
  1360.                     ).done( function() {
  1361.                         location.reload();
  1362.                     } );
  1363.                 } ).fail( function() {
  1364.                     ShowAlertDialog( 'Bloquear toda comunicación',
  1365.                         'Error al procesar la solicitud. Inténtalo de nuevo.'
  1366.                     );
  1367.                 } );
  1368.             } );
  1369.         }
  1370.  
  1371.         function Forum_InitTooltips()
  1372.         {
  1373.             // Override default tooltips for forums
  1374.             $J('.forum_topic[data-tooltip-forum], .forum_topic_link[data-tooltip-forum]').v_tooltip( {
  1375.                 'location':'bottom',
  1376.                 trackMouse: true,
  1377.                 'tooltipClass': 'forum_topic_tooltip',
  1378.                 offsetY: 6,
  1379.                 fadeSpeed: 0,
  1380.                 trackMouseCentered: false,
  1381.                 disableOnTouchDevice: true,
  1382.                 defaultType: 'html',
  1383.                 dataName: 'tooltipForum'
  1384.             });
  1385.  
  1386.         }
  1387.  
  1388.         function Forum_InitPostAndCommentControls( container )
  1389.         {
  1390.             $element = container ? $J( container ).find('.forum_comment_action_trigger') : $J('.forum_comment_action_trigger');
  1391.  
  1392.             $element.v_tooltip({'location':'bottom', 'destroyWhenDone': false, 'tooltipClass': 'forum_comment_action_menu', 'offsetY':0, 'offsetX': 1, 'horizontalSnap': 4, /*'tooltipParent': '#global_header .supernav_container',*/ 'correctForScreenSize': true});
  1393.         }
  1394.         Class.create( {
  1395.  
  1396.             m_strName: null,
  1397.             m_rgForumData: null,
  1398.             m_strActionURL: null,
  1399.             m_cPageSize: null,
  1400.  
  1401.             m_cTotalCount: 0,
  1402.             m_iCurrentPage: 0,
  1403.             m_iInitialPage: 0,
  1404.             m_cMaxPages: 0,
  1405.             m_bLoading: false,
  1406.             m_bSubmittingTopic: false,
  1407.             m_bNewTopicFormDisplayed: false,
  1408.             m_rgCreateTopicFlags: null,
  1409.             OnTextInput: function( elTextArea )
  1410.             {
  1411.             },
  1412.             GetActionURL: function( action )
  1413.             {
  1414.                 let url = this.m_strActionURL + action + '/';
  1415.                 if ( this.m_rgForumData['feature'] )
  1416.                     url += this.m_rgForumData['feature'] + '/';
  1417.                 return url;
  1418.             },
  1419.  
  1420.             OnAJAXComplete: function()
  1421.             {
  1422.                 this.m_bLoading = false;
  1423.             },
  1424.  
  1425.             OnLocationChange: function( hash )
  1426.             {
  1427.                 if ( hash.match( /^#p[0-9]+$/ ) )
  1428.                 {
  1429.                     const iPage = parseInt(hash.substring(2)) - 1;
  1430.                     if ( this.m_iCurrentPage !== iPage )
  1431.                         this.GoToPage( iPage );
  1432.                 }
  1433.                 else if ( !hash )
  1434.                 {
  1435.                     // reset to initial view
  1436.                     this.GoToPage( this.m_iInitialPage );
  1437.                 }
  1438.             },
  1439.             m_nAjaxSequenceNumber: 0,
  1440.             GoToPage: function( iPage, bForce )
  1441.             {
  1442.                 if ( iPage >= this.m_cMaxPages || iPage < 0 || ( iPage == this.m_iCurrentPage && !bForce ) )
  1443.                     return;
  1444.  
  1445.                 var params = {
  1446.                     start: this.m_cPageSize * iPage,
  1447.                     count: this.m_cPageSize
  1448.                 };
  1449.                 if ( this.m_rgForumData['extended_data'] )
  1450.                     params['extended_data'] = this.m_rgForumData['extended_data'];
  1451.  
  1452.                 this.m_bLoading = true;
  1453.                 new Ajax.Request( this.GetActionURL( 'render' ), {
  1454.                     method: 'get',
  1455.                     parameters: params,
  1456.                     onSuccess: this.OnResponseRenderTopics.bind( this, ++this.m_nAjaxSequenceNumber ),
  1457.                     onComplete: this.OnAJAXComplete.bind( this )
  1458.                 });
  1459.             },
  1460.  
  1461.             OnResponseRenderTopics: function( nSequenceNumber, transport )
  1462.             {
  1463.                 if ( nSequenceNumber !== this.m_nAjaxSequenceNumber )
  1464.                 {
  1465.                     return;
  1466.                 }
  1467.  
  1468.                 if ( transport.responseJSON )
  1469.                 {
  1470.                     const response = transport.responseJSON;
  1471.                     this.m_cTotalCount = response.total_count;
  1472.                     this.m_cMaxPages = Math.ceil( response.total_count / this.m_cPageSize );
  1473.                     this.m_iCurrentPage = Math.floor( response.start / this.m_cPageSize );
  1474.  
  1475.                     if ( this.m_cTotalCount <= response.start )
  1476.                     {
  1477.                         // this page is no logner valid, flip back a page (deferred so that the AJAX handler exits and reset m_bLoading)
  1478.                         this.GoToPage.bind( this, this.m_iCurrentPage - 1 ).defer();
  1479.                         return;
  1480.                     }
  1481.  
  1482.                     const elTopics = $('forum_' + this.m_strName + '_topics');
  1483.                     $('forum_' + this.m_strName + '_topiccontainer' );
  1484.                     elTopics.update( response.topics_html );
  1485.  
  1486.  
  1487.                     if ( window.history && window.history.pushState )
  1488.                     {
  1489.                         var params = window.location.search.length ? $J.deparam( window.location.search.substr(1) ) : {};
  1490.                         var urlpage = params['fp'] ? params['fp'] - 1 : 0;
  1491.                         if ( urlpage != this.m_iCurrentPage )
  1492.                         {
  1493.                             window.history.pushState( { forum_page: this.m_iCurrentPage }, '', UpdateParameterInCurrentURL( 'fp', this.m_iCurrentPage == 0 ? null : this.m_iCurrentPage + 1 ) );
  1494.                         }
  1495.                     }
  1496.                     else if ( this.m_iCurrentPage != this.m_iInitialPage || window.location.hash.length > 1)
  1497.                     {
  1498.                         if ( this.m_iCurrentPage != this.m_iInitialPage )
  1499.                             window.location.hash = 'p' + ( this.m_iCurrentPage + 1);
  1500.                         else
  1501.                             window.location.hash = '';
  1502.                     }
  1503.  
  1504.                     ScrollToIfNotInView($('forum_' + this.m_strName + '_area' ), 40 );
  1505.  
  1506.                     this.UpdatePagingDisplay();
  1507.                     Forum_InitTooltips();
  1508.                 }
  1509.             },
  1510.  
  1511.             UpdatePagingDisplay: function()
  1512.             {
  1513.                 var strPrefix = 'forum_' + this.m_strName;
  1514.  
  1515.  
  1516.                 var rgPagingControls = [ strPrefix + '_page' , strPrefix + '_footerpage' ];
  1517.                 for ( var i = 0; i < rgPagingControls.length; i++ )
  1518.                 {
  1519.                     var strPagePrefix = rgPagingControls[i];
  1520.  
  1521.                     // we may not always show both sets of controls
  1522.                     if ( !$(strPagePrefix + 'controls' ) )
  1523.                         continue;
  1524.  
  1525.                     $(strPagePrefix + 'total').update( v_numberformat( this.m_cTotalCount ) );
  1526.                     $(strPagePrefix + 'start').update( v_numberformat( this.m_iCurrentPage * this.m_cPageSize + 1 ) );
  1527.                     $(strPagePrefix + 'end').update( Math.min( ( this.m_iCurrentPage + 1 ) * this.m_cPageSize, this.m_cTotalCount ) );
  1528.  
  1529.  
  1530.                     if ( this.m_cMaxPages <= 1 )
  1531.                     {
  1532.                         $(strPagePrefix + 'controls').hide();
  1533.                     }
  1534.                     else
  1535.                     {
  1536.                         $(strPagePrefix + 'controls').show();
  1537.                         if ( this.m_iCurrentPage > 0 )
  1538.                             $(strPagePrefix + 'btn_prev').removeClassName('disabled');
  1539.                         else
  1540.                             $(strPagePrefix + 'btn_prev').addClassName('disabled');
  1541.  
  1542.                         if ( this.m_iCurrentPage < this.m_cMaxPages - 1 )
  1543.                             $(strPagePrefix + 'btn_next').removeClassName('disabled');
  1544.                         else
  1545.                         {
  1546.                             $(strPagePrefix + 'btn_next').addClassName('disabled');
  1547.                             $J('#' + strPrefix + '_searchformore').show();
  1548.                         }
  1549.  
  1550.                         var elPageLinks = $(strPagePrefix + 'links');
  1551.                         elPageLinks.update('');
  1552.                         // we always show first, last, + 3 page links closest to current page
  1553.                         const cPageLinksAheadBehind = 2;
  1554.                         let firstPageLink = Math.max(this.m_iCurrentPage - cPageLinksAheadBehind, 1);
  1555.                         const lastPageLink = Math.min(this.m_iCurrentPage + (cPageLinksAheadBehind * 2) + (firstPageLink - this.m_iCurrentPage), this.m_cMaxPages - 2);
  1556.  
  1557.                         if ( lastPageLink - this.m_iCurrentPage < cPageLinksAheadBehind )
  1558.                             firstPageLink = Math.max( this.m_iCurrentPage - (cPageLinksAheadBehind*2) + ( lastPageLink - this.m_iCurrentPage ), 1 );
  1559.  
  1560.                         this.AddPageLink( elPageLinks, 0 );
  1561.                         if ( firstPageLink !== 1 )
  1562.                             elPageLinks.insert( ' ... ' );
  1563.  
  1564.                         for ( var iPage = firstPageLink; iPage <= lastPageLink; iPage++ )
  1565.                         {
  1566.                             this.AddPageLink( elPageLinks, iPage );
  1567.                         }
  1568.  
  1569.                         if ( lastPageLink != this.m_cMaxPages - 2 )
  1570.                             elPageLinks.insert( ' ... ' );
  1571.                         this.AddPageLink( elPageLinks, this.m_cMaxPages - 1 );
  1572.                     }
  1573.                 }
  1574.             },
  1575.  
  1576.             AddPageLink: function( elPageLinks, iPage )
  1577.             {
  1578.                 const el = new Element('a', {
  1579.                     'class': 'forum_paging_pagelink',
  1580.                     'href': UpdateParameterInCurrentURL('fp', iPage ? iPage + 1 : null)
  1581.                 });
  1582.                 el.update( (iPage + 1) + ' ' );
  1583.  
  1584.                 const fnGoToPage = this.GoToPage.bind(this, iPage);
  1585.  
  1586.                 if ( iPage === this.m_iCurrentPage )
  1587.                     el.addClassName( 'active' );
  1588.                 else
  1589.                     $J(el).on('click', function(e) { e.preventDefault(); fnGoToPage();  });
  1590.  
  1591.                 elPageLinks.insert( el );
  1592.             }
  1593.         } );
  1594.         g_rgForumTopics = {};
  1595.         g_rgForumTopicCommentThreads = {};
  1596.  
  1597.         function RegisterForumTopicCommentThread( gidTopic, CommentThread )
  1598.         {
  1599.             g_rgForumTopicCommentThreads[ gidTopic ] = CommentThread;
  1600.         }
  1601.         function Forum_SetTopicFlag( gidTopic, flag, value )
  1602.         {
  1603.             if ( g_rgForumTopics[ gidTopic ] )
  1604.                 g_rgForumTopics[ gidTopic ].SetTopicFlag( flag, value );
  1605.         }
  1606.         function ShowForumSuccessDialog( strTitle, strDetails )
  1607.         {
  1608.             ShowForumSuccessDialogWithDetailTitle( strTitle, null, strDetails );
  1609.         }
  1610.  
  1611.         function ShowForumSuccessDialogWithDetailTitle( strTitle, strDetailTitle, strDetails )
  1612.         {
  1613.             const $Content = $J('<div/>');
  1614.  
  1615.             if ( strDetailTitle )
  1616.                 $Content.append( $J('<h2/>', {'class': 'forum_modal_detailtitle'} ).html( strDetailTitle ) );
  1617.  
  1618.             $Content.append( $J('<div/>' ).html(strDetails) );
  1619.             const Modal = ShowAlertDialog(strTitle, $Content);
  1620.             Modal.GetContent().css( 'width', '400px' );
  1621.  
  1622.         }
  1623.         Class.create({
  1624.  
  1625.             m_rgForumData: null,
  1626.             m_strActionURL: null,
  1627.             m_gidForumTopic: null,
  1628.             m_rgRawData: null,  //raw text and author data, for editing/quoting
  1629.  
  1630.             m_bAJAXInFlight: false,
  1631.  
  1632.             // for editing
  1633.             m_elTextArea: null,
  1634.             m_oTextAreaSizer: null,
  1635.             GetActionURL: function (action) {
  1636.                 var url = this.m_strActionURL + action + '/';
  1637.                 if (this.m_rgForumData['feature'])
  1638.                     url += this.m_rgForumData['feature'] + '/';
  1639.                 return url;
  1640.             },
  1641.  
  1642.             ParametersWithDefaults: function (params) {
  1643.                 if (!params)
  1644.                     params = {};
  1645.  
  1646.                 params['gidforumtopic'] = this.m_gidForumTopic;
  1647.                 params['sessionid'] = g_sessionID;
  1648.  
  1649.                 return params;
  1650.             },
  1651.             SetTopicFlag: function (flag, value) {
  1652.                 if (this.m_bAJAXInFlight)
  1653.                     return;
  1654.  
  1655.                 this.m_bAJAXInFlight = true;
  1656.  
  1657.                 let fnOnSuccess = function () {
  1658.                     window.location.reload();
  1659.                 };
  1660.                 if (flag === 'purge')
  1661.                     fnOnSuccess = this.RedirectToTopicListPage.bind(this);
  1662.  
  1663.                 new Ajax.Request(this.GetActionURL('moderatetopic'), {
  1664.                     method: 'post',
  1665.                     parameters: this.ParametersWithDefaults({action: 'setflag', flag: flag, value: value}),
  1666.                     onSuccess: fnOnSuccess,
  1667.                     onFailure: this.OnModeratorActionFailed.bind(this)
  1668.                 });
  1669.             },
  1670.             OnReportPostSuccess: function () {
  1671.                 ShowForumSuccessDialogWithDetailTitle('Denunciar', 'Gracias por informar', 'Tu denuncia ha sido enviada a los moderadores para su revisión. Te lo haremos saber si tomamos medidas.');
  1672.             },
  1673.             RedirectToTopicListPage: function () {
  1674.                 window.location = this.m_rgForumData['forum_url'];
  1675.             },
  1676.  
  1677.             OnModeratorActionFailed: function (transport) {
  1678.                 this.m_bAJAXInFlight = false;
  1679.                 if (transport.responseJSON && 'msg' in transport.responseJSON) {
  1680.                     ShowForumSuccessDialog('', 'Error al cambiar el tema. Mensaje de error:' +
  1681.                         "<br><br>" + transport.responseJSON.msg);
  1682.                 } else {
  1683.                     ShowForumSuccessDialog('', 'No se ha podido modificar el tema. Por favor, inténtalo de nuevo más tarde.');
  1684.                 }
  1685.             },
  1686.  
  1687.             RecordTopicViewed: function (timelastpost) {
  1688.                 // fire and forget (success sets a cookie)
  1689.                 new Ajax.Request(this.GetActionURL('recordtopicviewed'), {
  1690.                     method: 'post',
  1691.                     parameters: this.ParametersWithDefaults({timelastpost: timelastpost ? timelastpost + 1 : 0})
  1692.                 });
  1693.             },
  1694.  
  1695.             Subscribe: function (fnExternalOnSuccess) {
  1696.                 if (g_rgForumTopicCommentThreads[this.m_gidForumTopic]) {
  1697.                     var fnSuccess = function () {
  1698.                         ShowForumSuccessDialogWithDetailTitle('Suscribirse a discusión', 'Has sido suscrito a esta discusión.', 'Recibirás una notificación de comentario cada vez que alguien responda a esta discusión.');
  1699.                         fnExternalOnSuccess();
  1700.                     };
  1701.                     var fnFail = ShowForumSuccessDialog.bind(null, 'Suscribirse a discusión', 'Lo sentimos, no podemos suscribirte a esta discusión. Por favor, inténtalo de nuevo más tarde.');
  1702.                     g_rgForumTopicCommentThreads[this.m_gidForumTopic].Subscribe(fnSuccess, fnFail);
  1703.                 }
  1704.             },
  1705.  
  1706.             Unsubscribe: function (fnExternalOnSuccess) {
  1707.                 if (g_rgForumTopicCommentThreads[this.m_gidForumTopic]) {
  1708.                     var fnSuccess = function () {
  1709.                         ShowForumSuccessDialogWithDetailTitle('Anular suscripción a discusión', 'Tu suscripción a esta discusión ha sido anulada.', 'Ya no recibirás más notificaciones de comentarios de esta discusión.');
  1710.                         fnExternalOnSuccess();
  1711.                     };
  1712.                     var fnFail = ShowForumSuccessDialog.bind(null, 'Anular suscripción a discusión', 'Lo sentimos, no hemos podido anular la suscripción a esta discusión. Por favor, inténtalo de nuevo más tarde.');
  1713.                     g_rgForumTopicCommentThreads[this.m_gidForumTopic].Unsubscribe(fnSuccess, fnFail);
  1714.                 }
  1715.             },
  1716.             BCheckPermission: function (strPermissionName) {
  1717.                 return this.m_rgForumData.permissions && this.m_rgForumData.permissions[strPermissionName] === 1;
  1718.             }
  1719.  
  1720.         });
  1721.         Class.create(CCommentThread, {
  1722.  
  1723.             m_iInitialPage: 0,
  1724.  
  1725.             initialize: function ($super, type, name, rgCommentData, url, quoteBoxHeight) {
  1726.                 $super(type, name, rgCommentData, url, quoteBoxHeight);
  1727.  
  1728.                 this.m_iInitialPage = this.m_iCurrentPage;
  1729.  
  1730.                 // watch for incoming # urls
  1731.                 BindOnHashChange(this.OnLocationChange.bind(this));
  1732.                 this.OnLocationChange(window.location.hash);
  1733.  
  1734.                 if (this.m_rgCommentData && this.m_rgCommentData.lastvisit) {
  1735.                     var rgNewComments = $$('.commentthread_newcomment');
  1736.                     if (rgNewComments.length > 0) {
  1737.                         ScrollToIfNotInView(rgNewComments[0], 5000, 36);
  1738.                     }
  1739.                 }
  1740.  
  1741.                 RegisterForumTopicCommentThread(this.m_rgCommentData['feature2'], this);
  1742.             },
  1743.  
  1744.             GetForumTopic: function () {
  1745.                 return g_rgForumTopics[this.m_rgCommentData.feature2];
  1746.             },
  1747.  
  1748.             OnLocationChange: function (hash) {
  1749.                 if (hash.match(/^#c[0-9]+$/)) {
  1750.                     var gidComment = hash.substring(2);
  1751.                     if (!$('comment_' + gidComment))
  1752.                         this.GoToPageWithComment(gidComment, CCommentThread.RENDER_GOTOCOMMENT_HASHCHANGE);
  1753.                     else
  1754.                         this.UpdatePermLinkHighlight();
  1755.                 } else if (hash.match(/^#p[0-9]+$/)) {
  1756.                     var iPage = parseInt(hash.substring(2)) - 1;
  1757.                     if (this.m_iCurrentPage !== iPage)
  1758.                         this.GoToPage(iPage, CCommentThread.RENDER_GOTOPAGE_HASHCHANGE);
  1759.                 } else if (!hash) {
  1760.                     // reset to initial view
  1761.                     //this.GoToPage( this.m_iInitialPage );
  1762.                     this.UpdatePermLinkHighlight();
  1763.                 }
  1764.             },
  1765.  
  1766.             UpdatePermLinkHighlight: function () {
  1767.                 const elContainer = $('commentthread_' + this.m_strName + '_posts');
  1768.                 elContainer.childElements().invoke('removeClassName', 'permlinked');
  1769.  
  1770.                 const hash = window.location.hash;
  1771.                 if (hash && hash.length > 2 && hash.match(/#c[0-9]+/)) {
  1772.                     const elComment = $('comment_' + hash.substring(2));
  1773.                     const elDeletedComment = $('deleted_comment_' + hash.substring(2));
  1774.                     if (elComment) {
  1775.                         if (elDeletedComment) {
  1776.                             elDeletedComment.hide();
  1777.                             elComment.show();
  1778.                         }
  1779.                         elComment.addClassName('permlinked');
  1780.                         ScrollToIfNotInView.defer(elComment, 5000, 36);
  1781.                     }
  1782.                 }
  1783.             },
  1784.  
  1785.             /* override to get rid of animation */
  1786.             DoTransitionToNewPosts: function (response, eRenderReason) {
  1787.                 var strNewHTML = response.comments_html;
  1788.  
  1789.                 var elPosts = $('commentthread_' + this.m_strName + '_posts');
  1790.                 var elContainer = $('commentthread_' + this.m_strName + '_postcontainer');
  1791.                 var elArea = $('commentthread_' + this.m_strName + '_area');
  1792.  
  1793.                 var topic = g_rgForumTopics[this.m_rgCommentData['feature2']];
  1794.  
  1795.                 var bNewPost = (eRenderReason === CCommentThread.RENDER_NEWPOST);
  1796.  
  1797.                 // new posts get an animation, anything else just gets snapped in
  1798.                 elContainer.style.height = elContainer.getHeight() + 'px';
  1799.  
  1800.                 elPosts.update(strNewHTML);
  1801.                 Forum_InitPostAndCommentControls(elPosts);
  1802.  
  1803.                 if (eRenderReason === CCommentThread.RENDER_GOTOPAGE || eRenderReason === CCommentThread.RENDER_NEWPOST) {
  1804.                     if ((!window.history || !window.history.pushState) && (this.m_iCurrentPage !== this.m_iInitialPage || window.location.hash.length > 1))
  1805.                         window.location.hash = 'p' + (this.m_iCurrentPage + 1);
  1806.                 } else
  1807.                     this.UpdatePermLinkHighlight();
  1808.  
  1809.                 if (bNewPost) {
  1810.                     if (elContainer.effect)
  1811.                         elContainer.effect.cancel();
  1812.  
  1813.                     window.setTimeout(function () {
  1814.                         elContainer.effect = new Effect.Morph(elContainer, {
  1815.                             style: 'height: ' + elPosts.getHeight() + 'px',
  1816.                             duration: 0.25,
  1817.                             afterFinish: function () {
  1818.                                 elPosts.style.position = 'static';
  1819.                                 elContainer.style.height = 'auto';
  1820.                             }
  1821.                         });
  1822.                     }, 10);
  1823.                 } else {
  1824.                     elContainer.style.height = '';
  1825.                     if (eRenderReason !== CCommentThread.RENDER_GOTOCOMMENT && eRenderReason !== CCommentThread.RENDER_GOTOCOMMENT_HASHCHANGE && eRenderReason !== CCommentThread.RENDER_DELETEDPOST && eRenderReason !== CCommentThread.RENDER_GOTOPAGE_HASHCHANGE)
  1826.                         ScrollToIfNotInView.defer(elArea, 80, 40);
  1827.                 }
  1828.  
  1829.                 if (this.m_iCurrentPage === this.m_cMaxPages - 1) {
  1830.                     if (topic)
  1831.                         topic.RecordTopicViewed(response.timelastpost);
  1832.                 }
  1833.  
  1834.             },
  1835.  
  1836.  
  1837.             DeleteComment: function ($super, gidComment, bUndelete, fnOnSuccess) {
  1838.                 if (fnOnSuccess) {
  1839.                     // special global reports mode
  1840.                     $super(gidComment, bUndelete, fnOnSuccess);
  1841.                     return;
  1842.                 }
  1843.                 const Topic = this.GetForumTopic();
  1844.                 const bIsModerator = Topic && Topic.BCheckPermission('can_moderate');
  1845.  
  1846.                 const fnOnConfirm = $super.bind(this, gidComment, bUndelete);
  1847.                 if (!bUndelete && !bIsModerator) {
  1848.                     ShowConfirmDialog('', '¿Seguro que quieres borrar este mensaje?', 'Borrar')
  1849.                         .done(function () {
  1850.                             fnOnConfirm();
  1851.                         });
  1852.                 } else {
  1853.                     // no confirmation for undeleting or for moderators (who can just undo the delete if they decide it was a bad idea
  1854.                     fnOnConfirm();
  1855.                 }
  1856.             }
  1857.  
  1858.         });
  1859.         function Forum_SetMoveTopicClan( clanidowner, appidowner )
  1860.         {
  1861.             const form = $('forum_movetopic_form');
  1862.             let rgReloadParams = null;
  1863.             if ( clanidowner )
  1864.             {
  1865.                 if ( form.elements['destination_clanidowner'].value !== clanidowner )
  1866.                 {
  1867.                     form.elements['destination_clanidowner'].value = clanidowner;
  1868.                     form.elements['destination_appidowner'].value = '';
  1869.                     rgReloadParams = { destination_clanid: clanidowner };
  1870.                 }
  1871.             }
  1872.             else if ( appidowner )
  1873.             {
  1874.                 if ( form.elements['destination_appidowner'].value !== appidowner )
  1875.                 {
  1876.                     form.elements['destination_clanidowner'].value = '';
  1877.                     form.elements['destination_appidowner'].value = appidowner;
  1878.                     rgReloadParams = { destination_appid: appidowner };
  1879.                 }
  1880.             }
  1881.  
  1882.             if ( rgReloadParams )
  1883.             {
  1884.                 $(form.elements['destination_gidforum']).hide();
  1885.                 $('forum_movetopic_destination_throbber').show();
  1886.                 $('forum_movetopic_destinationclan') && $('forum_movetopic_destinationclan').update('&nbsp;');
  1887.                 $('forum_movetopic_destination_unavailable').hide();
  1888.                 rgReloadParams['sessionid'] = g_sessionID;
  1889.  
  1890.                 new Ajax.Request( 'https://steamcommunity.com/forum/0/General/gettopicdestinations/', {
  1891.                     method: 'get',
  1892.                     parameters: rgReloadParams,
  1893.                     onSuccess: Forum_OnMoveTopicDestinations.bind( null, clanidowner, appidowner ),
  1894.                     onFailure: Forum_OnMoveTopicDestinationsFailed.bind( null, clanidowner, appidowner )
  1895.                 } );
  1896.             }
  1897.         }
  1898.  
  1899.         function Forum_OnMoveTopicSelectGame( GameSelector, rgAppData )
  1900.         {
  1901.             $('associate_game').value='';
  1902.  
  1903.             Forum_SetMoveTopicClan( false, rgAppData.appid );
  1904.         }
  1905.  
  1906.         function Forum_OnMoveTopicDestinations( clanidowner, appidowner, transport )
  1907.         {
  1908.             const form = $('forum_movetopic_form');
  1909.             if ( ( !clanidowner || form.elements['destination_clanidowner'].value === clanidowner ) &&
  1910.                 ( !appidowner || form.elements['destination_appidowner'].value === appidowner ) )
  1911.             {
  1912.                 $('forum_movetopic_destination_throbber').hide();
  1913.                 const $Select = $J(form.elements['destination_gidforum']);
  1914.                 $Select.html('');
  1915.                 const rgForums = transport.responseJSON.rgForums;
  1916.                 if ( rgForums && rgForums.length > 0 )
  1917.                 {
  1918.                     for (let i = 0; i < rgForums.length; i++ )
  1919.                     {
  1920.                         const rgForum = rgForums[i];
  1921.                         const $Option = $J('<option/>', {value: rgForum.gidforum});
  1922.                         $Option.text( rgForum.name );
  1923.                         $Select.append( $Option );
  1924.                     }
  1925.                 }
  1926.                 else
  1927.                 {
  1928.                     Forum_OnMoveTopicDestinationsFailed( clanidowner, appidowner, transport );
  1929.                     return;
  1930.                 }
  1931.  
  1932.                 const elName = $('forum_movetopic_destinationclan');
  1933.                 if ( elName )
  1934.                     elName.update( transport.responseJSON.strName );
  1935.  
  1936.                 $Select.show();
  1937.                 $Select.focus();
  1938.                 $('submit_move_topic_button').show();
  1939.             }
  1940.         }
  1941.  
  1942.         function Forum_OnMoveTopicDestinationsFailed( clanidowner, appidowner, transport )
  1943.         {
  1944.             const form = $('forum_movetopic_form');
  1945.             if ( ( !clanidowner || form.elements['destination_clanidowner'].value === clanidowner ) &&
  1946.                 ( !appidowner || form.elements['destination_appidowner'].value === appidowner ) )
  1947.             {
  1948.                 $('forum_movetopic_destination_throbber').hide();
  1949.                 $(form.elements['destination_gidforum']).hide();
  1950.                 $('forum_movetopic_destination_unavailable').show();
  1951.                 $('submit_move_topic_button').hide();
  1952.             }
  1953.         }
  1954.         function Forum_InitExpiryOptions( $Select )
  1955.         {
  1956.             var nLastExpiryChoice = WebStorage.GetLocal( 'nForumLastExpiryChoice', false ) || 0;
  1957.             $Select.val( nLastExpiryChoice );
  1958.  
  1959.             $Select.off( 'change.ForumRememberChoice' );
  1960.             $Select.on( 'change.ForumRememberChoice', function() {
  1961.                 WebStorage.SetLocal( 'nForumLastExpiryChoice', $Select.val() );
  1962.             });
  1963.         }
  1964.         $J( function($) {
  1965.             Forum_InitPostAndCommentControls();
  1966.         });
  1967.  
  1968.     }
  1969.     /*
  1970.         Constants
  1971.     */
  1972.     const COMMENTS_PAGE = "https://steamcommunity.com/my/commenthistory"; // URL to page with comments
  1973.     const FORUM_JS_LOAD_TIMEOUT = 10000; // ms
  1974.     const USER_STEAM_PROFILE_URL = jQuery("#global_actions > a").attr("href").replace(/\/$/, ""); // https://steamcommunity.com/id/$ID
  1975.     const PAGE_REQUESTS_INTERVAL = 1000; // ms
  1976.     const TIMESTAMP_DIFF = 3; // Maximum difference between timestamps
  1977.     const REGEXP =
  1978.         {
  1979.             "body": /<body.{1,}?>([.\s\S]{1,})<\/body>/, // Body innerHTML (No depending on attributes)
  1980.             "link": /^(.{1,}?)(?:\?(.{1,}?))?$/, // Link -> url + data
  1981.             "initFTopic": /InitializeForumTopic\s?\([^"]+?(?:"[^"]+?"[^"]+?)+?[^"]+?\);/, // Protected from ); in quotes
  1982.             "initCThread": /InitializeCommentThread\s?\([^"]+?(?:"[^"]+?"[^"]+?)+?[^"]+?\);/ // Protected from ); in quotes
  1983.         };
  1984.  
  1985.     /*
  1986.         Utility
  1987.     */
  1988.     let pages_amount = jQuery(".pageLinks .pagelink:last").html(); // Amount of pages
  1989.     if (!pages_amount) {
  1990.         pages_amount = 1;
  1991.     } else {
  1992.         pages_amount = +(pages_amount);
  1993.     }
  1994.     const pages = []; // Array with page bodies
  1995.     const comments = []; // Array with comments to clean
  1996.  
  1997.     const execute = function (txt) // Replacer to eval to be able to see commands printed
  1998.     {
  1999.         if (PRINT_EXECUTED_COMMANDS) {
  2000.             console.log('%c ' + txt, style);
  2001.         }
  2002.         eval(txt);
  2003.     };
  2004.  
  2005.     /*
  2006.         Functions
  2007.     */
  2008.     const loadPagesFrom = function (fromPageNumber, callback) // Loading page body with comments and adding it to pages as div
  2009.     {
  2010.         if (fromPageNumber > pages_amount || (LAST_PAGE_TO_CLEAR !== -1 && fromPageNumber > LAST_PAGE_TO_CLEAR)) {
  2011.             console.log("%c    Loaded pages", style);
  2012.             console.log('%c ' + pages, style);
  2013.             if (typeof callback == "function") {
  2014.                 callback();
  2015.             }
  2016.             return;
  2017.         }
  2018.         console.log("%c    Loading page #" + fromPageNumber, style);
  2019.         if (location.search.match(new RegExp("p=" + fromPageNumber)) || fromPageNumber === 1) {
  2020.             var elem = document.createElement("div");
  2021.             elem.innerHTML = document.body.innerHTML;
  2022.             pages.push(elem);
  2023.             console.log("%c    Loaded page #" + fromPageNumber, style);
  2024.             loadPagesFrom(fromPageNumber + 1, callback);
  2025.             return;
  2026.         }
  2027.  
  2028.         let search = location.search.replace(/\?/, ""); // Delete first ? if it exists
  2029.         if (search) {
  2030.             // Search is not empty
  2031.             if (search.match(/p=[0-9]+/)) {
  2032.                 // Page number is already stated (it must be 1)
  2033.                 search = search.replace(/p=[0-9]+/, "p=" + fromPageNumber)
  2034.             } else {
  2035.                 // No page number but have some search parameters
  2036.                 search += "&p=" + fromPageNumber;
  2037.             }
  2038.         } else {
  2039.             // empty search
  2040.             search = "p=" + fromPageNumber;
  2041.         }
  2042.         jQuery.ajax({
  2043.             "success": function (data, status) {
  2044.                 const inner = data.match(REGEXP.body)[1];
  2045.                 const elem = document.createElement("div");
  2046.                 elem.innerHTML = inner;
  2047.                 pages.push(elem);
  2048.                 console.log("%c    Loaded page #" + fromPageNumber, style);
  2049.                 loadPagesFrom(fromPageNumber + 1, callback);
  2050.             },
  2051.             "fail": function () {
  2052.                 console.error("    !!!Unable to load page");
  2053.             },
  2054.             "data": search,
  2055.             "method": "GET",
  2056.             "url": COMMENTS_PAGE
  2057.         });
  2058.     };
  2059.  
  2060.     const loadURLsFrom = function (fromPageNumber, callback) // Loading URLs and adding them to comments as objects
  2061.     {
  2062.         if (fromPageNumber > pages.length) {
  2063.             console.log("%c    Loaded URLs", style);
  2064.             console.log('%c '+ comments, style);
  2065.             if (typeof callback == "function") {
  2066.                 callback();
  2067.             }
  2068.             return;
  2069.         }
  2070.         jQuery(pages[fromPageNumber - 1]).find(".commenthistory_comment .comment_item_title a").each(function (index, a) {
  2071.             const info = {}; // Info about comment
  2072.             let link = a.href;
  2073.             link = link.replace(/#/, "/"); // Getting rid of # (replacable with /)
  2074.             info["isForum"] = !!link.match(/\/discussions\//);
  2075.             const m = link.match(REGEXP.link);
  2076.             info["link"] = link; // Full link
  2077.             info["url"] = m[1]; // Link with no GET arguments
  2078.             info["data"] = m[2]; // GET arguments (key=val&key=val...)
  2079.             if (info["data"] && info["data"].match(/tscn=[0-9]+/)) {
  2080.                 info["timestamp"] = info["data"].match(/tscn=([0-9]+)/)[1]; // Timestamp
  2081.             }
  2082.             if (info["isForum"]) {
  2083.                 info["isForumTopic"] = !(info["data"] && info["data"].match(/tscn=/));
  2084.             }
  2085.             info["text"] = jQuery(a).closest(".commenthistory_comment").find(".comment_text").text().replace(/\s/g, "");
  2086.             comments.push(info);
  2087.         });
  2088.         console.log("%c    Loaded URLs from page #" + (fromPageNumber + FIRST_PAGE_TO_CLEAR - 1), style);
  2089.         loadURLsFrom(fromPageNumber + 1, callback);
  2090.     };
  2091.  
  2092.     let cleaned = 0;
  2093.     const clearURLfromIndex = function (index, callback) // Clearing comment form comments array by its index
  2094.     {
  2095.         if (index >= comments.length) {
  2096.             console.log("%c    Cleared URLs", style);
  2097.             console.log('%c ' + comments, style);
  2098.             if (typeof callback == "function") {
  2099.                 window.location.hash = ""; // Removing previously set hash
  2100.                 callback();
  2101.             }
  2102.             return;
  2103.         }
  2104.         window.location.hash = "#p0"; // Can't initialize Comment thread without this hash (Crutch ._ .)
  2105.         jQuery.ajax({
  2106.             "success": function (data, status) {
  2107.                  // Comments on the page by user
  2108.                 let ifFoundComment;
  2109.                 let initCThread;
  2110.                 try {
  2111.                     comments[index]["html"] = document.createElement("div");
  2112.                     comments[index]["html"].innerHTML = data;
  2113.                     if (jQuery(comments[index]["html"]).find("#message").length) {
  2114.                         throw new Error(jQuery(comments[index]["html"]).find("#message").text());
  2115.                     }
  2116.                     if (comments[index]["isForum"]) {
  2117.                         let initFTopic = jQuery(comments[index]["html"]).find("script:contains(InitializeForumTopic)").html(); // Code to be executed
  2118.                         initFTopic = initFTopic.match(REGEXP.initFTopic)[0]; // Getting main command
  2119.                         execute(initFTopic);
  2120.                         let initCThread = jQuery(comments[index]["html"]).find("script:contains(InitializeCommentThread)").html(); // Code to be executed
  2121.                         initCThread = initCThread.match(REGEXP.initCThread)[0]; // Getting main command
  2122.                         initCThread = initCThread.replace(/\{/, "{\"no_paging\": true, "); // Adding no paging to prevent error
  2123.                         execute(initCThread);
  2124.                         if (comments[index]["isForumTopic"]) {
  2125.                             // Topic
  2126.                             var deleteTopic = jQuery(comments[index]["html"]).find(".forum_op a[href*=\"DeleteTopic\"]");
  2127.                             var argument = jQuery(deleteTopic).attr("href").match(/\(([.\s\S]{1,}?)\)/)[1].replace(/[\s'"]/g, "");
  2128.                             new Ajax.Request(
  2129.                                 g_rgForumTopics[argument].GetActionURL("deletetopic"), {
  2130.                                     "method": "POST",
  2131.                                     "parameters": g_rgForumTopics[argument].ParametersWithDefaults()
  2132.                                 });
  2133.                         } else {
  2134.                             // Message
  2135.                             const messagesByUser = jQuery(comments[index]["html"]).find(".commentthread_comment .commentthread_comment_avatar a[href*=\"" + USER_STEAM_PROFILE_URL + "\"]").closest(".commentthread_comment"); // Messages on the page by user
  2136.                             messagesByUser.each(function (i, message) {
  2137.                                 const text = jQuery(message).find(".commentthread_comment_text").text().replace(/\s/g, ""); // innerText of message
  2138.                                 const timestamp = jQuery(message).find(".commentthread_comment_timestamp").attr("data-timestamp"); // Timestamp of message
  2139.                                 if (text === comments[index]["text"] && Math.abs(+(timestamp) - +(comments[index]["timestamp"])) < TIMESTAMP_DIFF) {
  2140.                                     let deleteComment = jQuery(message).find("a[href*=\"DeleteComment\"]").attr("href");
  2141.                                     if (!deleteComment) {
  2142.                                         throw new Error("\nWas unable to find \"Delete\" button. Possibly you can't delete message from this group\n");
  2143.                                     }
  2144.                                     deleteComment = deleteComment.replace(/javascript:/, "");
  2145.                                     const arguments = deleteComment.match(/\(([.\s\S]{1,}?),([.\s\S]{1,}?)\)/); // Arguments
  2146.                                     // arguments looks like: " 'STRING' ". It may have [\s'"] in it
  2147.                                     execute("g_rgCommentThreads[" + arguments[1] + "].GetForumTopic().m_rgForumData.permissions.can_moderate = 1;"); // Need to prevent confirmation (execute is used just to be able to replace it with console.log. Also this is effective as we keep argument as it was at DeleteComment())
  2148.                                     execute("g_rgCommentThreads[" + arguments[1] + "].DeleteComment(" + arguments[2] + ");");
  2149.                                     ifFoundComment = true;
  2150.                                     return false; // break
  2151.                                 }
  2152.                             });
  2153.                         }
  2154.                     } else {
  2155.                         // Comment
  2156.                         if (comments[index]["url"].match(/\/id\//) && !jQuery(comments[index]["html"]).find(".commentthread_comments").length) {
  2157.                             throw new Error("\nCan not delete comment. As profile owner locked his page and deleted you from friends\n");
  2158.                         }
  2159.                         initCThread = jQuery(comments[index]["html"]).find("script:contains(InitializeCommentThread)").html(); // Code to be executed
  2160.                         initCThread = initCThread.match(REGEXP.initCThread)[0]; // Getting main command
  2161.                         initCThread = initCThread.replace(/\{/, "{\"no_paging\": true, "); // Adding no paging to prevent error
  2162.                         execute(initCThread);
  2163.                         const commentsByUser = jQuery(comments[index]["html"]).find(".commentthread_comment .commentthread_comment_avatar a[href*=\"" + USER_STEAM_PROFILE_URL + "\"]").closest(".commentthread_comment");
  2164.                         ifFoundComment = false;
  2165.                         commentsByUser.each(function (i, comment) {
  2166.                             const text = jQuery(comment).find(".commentthread_comment_text").text().replace(/\s/g, ""); // innerText of comment
  2167.                             const timestamp = jQuery(comment).find(".commentthread_comment_timestamp").attr("data-timestamp"); // Timestamp of comment
  2168.                             if (text === comments[index]["text"] && Math.abs(+(timestamp) - +(comments[index]["timestamp"])) < TIMESTAMP_DIFF) {
  2169.                                 const deleteComment = jQuery(comment).find("a[href*=\"DeleteComment\"]").attr("href").replace(/javascript:/, "");
  2170.                                 if (!deleteComment) {
  2171.                                     throw new Error("\nWas unable to find \"Delete\" button\n");
  2172.                                 }
  2173.                                 execute(deleteComment);
  2174.                                 ifFoundComment = true;
  2175.                                 return false; // break
  2176.                             }
  2177.                         });
  2178.                         if (!ifFoundComment) {
  2179.                             throw new Error("\nWas unable to find users comment\n");
  2180.                         }
  2181.                     }
  2182.                     console.log("%c    Cleaned comment #" + (index + 1), style);
  2183.                     cleaned++;
  2184.                 } catch (e) {
  2185.                     console.error("    !!! Can not clean comment #" + (index + 1));
  2186.                     console.error(comments[index]);
  2187.                     console.error(e);
  2188.                 }
  2189.                 setTimeout(function () {
  2190.                     clearURLfromIndex(index + 1, callback);
  2191.                 }, PAGE_REQUESTS_INTERVAL);
  2192.             },
  2193.             "fail": function () {
  2194.                 console.error("    !!!Unable to send request");
  2195.             },
  2196.             "data": comments[index]["data"],
  2197.             "method": "GET",
  2198.             "url": comments[index]["url"]
  2199.         });
  2200.     };
  2201.  
  2202.     /*
  2203.         Main
  2204.     */
  2205.     loadPagesFrom(FIRST_PAGE_TO_CLEAR, function()
  2206.     {
  2207.         loadURLsFrom(1, function()
  2208.         {
  2209.             clearURLfromIndex(0, function()
  2210.             {
  2211.                 alert("Done, comments cleaned: " + cleaned);
  2212.             });
  2213.         });
  2214.     });
  2215.  
  2216. })();
  2217.  
Advertisement
Add Comment
Please, Sign In to add comment