From pleb, 10 Months ago, written in JavaScript.
Embed
  1. // ==UserScript==
  2. // @name          SpookyX
  3. // @description   Enhances functionality of FoolFuuka boards. Developed further for more comfortable ghost-posting on the moe archives.
  4. // @author        Fiddlekins
  5. // @version       32.50
  6. // @namespace    
  7. // @include      
  8. // @include      
  9. // @include      
  10. // @include      
  11. // @include      
  12. // @include      
  13. // @include      
  14. // @include      
  15. // @include      
  16. // @include      
  17. // @include      
  18. // @include      
  19. // @include      
  20. // @include      
  21. // @include       http://**
  22. // @include       https://**
  23. // @include      
  24. // @include      
  25. // @include      
  26. // @include      
  27. // @include      
  28. // @include      
  29. // @include      
  30. // @include      
  31. // @include      
  32. // @include      
  33. // @include      
  34. // @include      
  35. // @include      
  36. // @include      
  37. // @require      
  38. // @require      
  39. // @require      
  40. // @grant         none
  41. // @updateURL    
  42. // @downloadURL  
  43. // @icon          
  44. // ==/UserScript==
  45.  
  46. if (GM_info === undefined) {
  47.         var GM_info = {script: {version: '32.50'}};
  48. }
  49.  
  50. var settings = {
  51.         "UserSettings": {
  52.                 "inlineImages": {
  53.                         "name": "Inline Images",
  54.                         "description": "Load full-size images in the thread, enable click to expand",
  55.                         "type": "checkbox",
  56.                         "value": true,
  57.                         "suboptions": {
  58.                                 "inlineVideos": {
  59.                                         "name": "Inline Videos",
  60.                                         "description": "Replace thumbnails of natively posted videos with the videos themselves",
  61.                                         "type": "checkbox",
  62.                                         "value": true,
  63.                                         "suboptions": {
  64.                                                 "firefoxCompatibility": {
  65.                                                         "name": "Firefox Compatibility Mode",
  66.                                                         "description": "Turn this on to allow you to use the controls on an expanded video without collapsing it",
  67.                                                         "type": "checkbox",
  68.                                                         "value": false
  69.                                                 }
  70.                                         }
  71.                                 },
  72.                                 "delayedLoad": {
  73.                                         "name": "Delayed Load",
  74.                                         "description": "Fullsize images are not automatically retrieved and used to replace the thumbnails. Instead this occurs on an individual basis when the thumbnails are clicked on",
  75.                                         "type": "checkbox",
  76.                                         "value": false
  77.                                 },
  78.                                 "imageHover": {
  79.                                         "name": "Image Hover",
  80.                                         "description": "Hovering over images with the mouse brings a full or window scaled version in view",
  81.                                         "type": "checkbox",
  82.                                         "value": true
  83.                                 },
  84.                                 "videoHover": {
  85.                                         "name": "Video Hover",
  86.                                         "description": "Hovering over videos with the mouse brings a full or window scaled version in view",
  87.                                         "type": "checkbox",
  88.                                         "value": true
  89.                                 },
  90.                                 "autoplayGifs": {
  91.                                         "name": "Autoplay embedded gifs",
  92.                                         "description": "Make embedded gifs play automatically",
  93.                                         "type": "checkbox",
  94.                                         "value": true
  95.                                 },
  96.                                 "autoplayVids": {
  97.                                         "name": "Autoplay embedded videos",
  98.                                         "description": "Make embedded videos play automatically (they start muted, expanding unmutes)",
  99.                                         "type": "checkbox",
  100.                                         "value": false
  101.                                 },
  102.                                 "customSize": {
  103.                                         "name": "Custom thumbnail size",
  104.                                         "description": "Specify the thumbnail dimensions",
  105.                                         "type": "checkbox",
  106.                                         "value": false,
  107.                                         "suboptions": {
  108.                                                 "widthOP": {
  109.                                                         "name": "OP image width",
  110.                                                         "description": "The maximum width of OP images in pixels",
  111.                                                         "type": "number",
  112.                                                         "value": 250
  113.                                                 },
  114.                                                 "heightOP": {
  115.                                                         "name": "OP image height",
  116.                                                         "description": "The maximum height of OP images in pixels",
  117.                                                         "type": "number",
  118.                                                         "value": 250
  119.                                                 },
  120.                                                 "width": {
  121.                                                         "name": "Post image width",
  122.                                                         "description": "The maximum width of post images in pixels",
  123.                                                         "type": "number",
  124.                                                         "value": 125
  125.                                                 },
  126.                                                 "height": {
  127.                                                         "name": "Post image height",
  128.                                                         "description": "The maximum height of post images in pixels",
  129.                                                         "type": "number",
  130.                                                         "value": 125
  131.                                                 }
  132.                                         }
  133.                                 },
  134.                                 "processSpoiler": {
  135.                                         "name": "Process spoilered images",
  136.                                         "description": "Make native spoilered images inline",
  137.                                         "type": "checkbox",
  138.                                         "value": true
  139.                                 }
  140.                         }
  141.                 },
  142.                 "embedImages": {
  143.                         "name": "Embed Media",
  144.                         "description": "Embed image (and other media) links in thread",
  145.                         "type": "checkbox",
  146.                         "value": true,
  147.                         "suboptions": {
  148.                                 "embedVideos": {
  149.                                         "name": "Embed Videos",
  150.                                         "description": "Embed video links in thread",
  151.                                         "type": "checkbox",
  152.                                         "value": true
  153.                                 },
  154.                                 "imgNumMaster": {
  155.                                         "name": "Embed Count",
  156.                                         "description": "The maximum number of images (or other media) to embed in each post",
  157.                                         "type": "number",
  158.                                         "value": 1
  159.                                 },
  160.                                 "titleYoutubeLinks": {
  161.                                         "name": "Title YouTube links",
  162.                                         "description": "Fetches the video name and alters the link text accordingly",
  163.                                         "type": "checkbox",
  164.                                         "value": true
  165.                                 }
  166.                         }
  167.                 },
  168.                 "autoHost": {
  169.                         "name": "Automatically Host Images",
  170.                         "description": "When post is submitted image links will be automatically reuploaded to Imgur in an effort to avoid having dead 4chan image links",
  171.                         "type": "select",
  172.                         "value": {
  173.                                 "value": "Reupload 4chan links",
  174.                                 "options": ["Don't reupload links", "Reupload 4chan links", "Reupload all links"]
  175.                         }
  176.                 },
  177.                 "embedGalleries": {
  178.                         "name": "Embed Galleries",
  179.                         "description": "Embed Imgur galleries into a single post for ease of image dumps",
  180.                         "type": "checkbox",
  181.                         "value": true,
  182.                         "suboptions": {
  183.                                 "showDetails": {
  184.                                         "name": "Show Details",
  185.                                         "description": "Show the title, image description and view count for embedded Imgur albums",
  186.                                         "type": "checkbox",
  187.                                         "value": true
  188.                                 }
  189.                         }
  190.                 },
  191.                 "gallery": {
  192.                         "name": "Gallery",
  193.                         "description": "Pressing G will bring up a view that displays all the images in a thread",
  194.                         "type": "checkbox",
  195.                         "value": true
  196.                 },
  197.                 "hidePosts": {
  198.                         "name": "Hide Posts",
  199.                         "description": "Allow user to hide posts manually",
  200.                         "type": "checkbox",
  201.                         "value": true,
  202.                         "suboptions": {
  203.                                 "recursiveHiding": {
  204.                                         "name": "Recursive Hiding",
  205.                                         "description": "Hide replies to hidden posts",
  206.                                         "type": "checkbox",
  207.                                         "value": true,
  208.                                         "suboptions": {
  209.                                                 "hideNewPosts": {
  210.                                                         "name": "Hide New Posts",
  211.                                                         "description": "Also hide replies to hidden posts that are fetched after page load",
  212.                                                         "type": "checkbox",
  213.                                                         "value": true
  214.                                                 }
  215.                                         }
  216.                                 }
  217.                         }
  218.                 },
  219.                 "newPosts": {
  220.                         "name": "New Posts",
  221.                         "description": "Reflect the number of new posts in the tab name",
  222.                         "type": "checkbox",
  223.                         "value": true
  224.                 },
  225.                 "favicon": {
  226.                         "name": "Favicon",
  227.                         "description": "Switch to a dynamic favicon that indicates unread posts and unread replies",
  228.                         "type": "checkbox",
  229.                         "value": true,
  230.                         "suboptions": {
  231.                                 "customFavicons": {
  232.                                         "name": "Custom Favicons",
  233.                                         "description": "If disabled SpookyX will try its hand at automatically generating suitable favicons for the site. Enabling this allows you to manually specify which favicons it should use instead",
  234.                                         "type": "checkbox",
  235.                                         "value": false,
  236.                                         "suboptions": {
  237.                                                 "unlit": {
  238.                                                         "name": "Unlit",
  239.                                                         "description": "Choose which favicon is used normally. Default is \""",
  240.                                                         "type": "text",
  241.                                                         "value": ""
  242.                                                 },
  243.                                                 "lit": {
  244.                                                         "name": "Lit",
  245.                                                         "description": "Choose which favicon is used to indicate there are unread posts. Preset numbers are 0-4, replace with link to custom image if you desire such as: \""",
  246.                                                         "type": "text",
  247.                                                         "value": "2"
  248.                                                 },
  249.                                                 "alert": {
  250.                                                         "name": "Alert",
  251.                                                         "description": "The favicon that indicates unread replies to your posts. Value is ignored if using a preset Lit favicon",
  252.                                                         "type": "text",
  253.                                                         "value": ""
  254.                                                 },
  255.                                                 "alertOverlay": {
  256.                                                         "name": "Alert Overlay",
  257.                                                         "description": "The favicon overlay that indicates unread replies. Default is \""",
  258.                                                         "type": "text",
  259.                                                         "value": ""
  260.                                                 },
  261.                                                 "notification": {
  262.                                                         "name": "Notification image",
  263.                                                         "description": "The image that is displayed in SpookyX generated notifications. 64px square is ideal. Default is \""",
  264.                                                         "type": "text",
  265.                                                         "value": ""
  266.                                                 }
  267.                                         }
  268.                                 }
  269.                         }
  270.                 },
  271.                 "labelYourPosts": {
  272.                         "name": "Label Your Posts",
  273.                         "description": "Add '(You)' to your posts and links that point to them",
  274.                         "type": "checkbox",
  275.                         "value": true
  276.                 },
  277.                 "inlineReplies": {
  278.                         "name": "Inline Replies",
  279.                         "description": "Click replies to expand them inline",
  280.                         "type": "checkbox",
  281.                         "value": true
  282.                 },
  283.                 "notifications": {
  284.                         "name": "Enable notifications",
  285.                         "description": "Browser notifications will be enabled, for example to alert you when your post has been replied to or if you encountered a posting error",
  286.                         "type": "checkbox",
  287.                         "value": true,
  288.                         "suboptions": {
  289.                                 "spoiler": {
  290.                                         "name": "Hide spoilers",
  291.                                         "description": "When creating a notification to alert you of a reply the spoilered text will be replaced with black boxes since nofications cannot hide them like normal",
  292.                                         "type": "checkbox",
  293.                                         "value": true
  294.                                 },
  295.                                 "restrict": {
  296.                                         "name": "Restrict size",
  297.                                         "description": "Firefox option only. By default there is no size limit on Firefox notifications, use this option to keep notifications at a sensible size",
  298.                                         "type": "checkbox",
  299.                                         "value": false,
  300.                                         "suboptions": {
  301.                                                 "lines": {
  302.                                                         "name": "Line count",
  303.                                                         "description": "Number of lines the notification is restricted to",
  304.                                                         "type": "number",
  305.                                                         "value": 5
  306.                                                 },
  307.                                                 "characters": {
  308.                                                         "name": "Character count",
  309.                                                         "description": "Number of characters per line the notification is restricted to",
  310.                                                         "type": "number",
  311.                                                         "value": 50
  312.                                                 }
  313.                                         }
  314.                                 }
  315.                         }
  316.                 },
  317.                 "relativeTimestamps": {
  318.                         "name": "Relative Timestamps",
  319.                         "description": "Timestamps will be replaced by elapsed time since post",
  320.                         "type": "checkbox",
  321.                         "value": true
  322.                 },
  323.                 "postQuote": {
  324.                         "name": "Post Quote",
  325.                         "description": "Clicking the post number will insert highlighted text into the reply box",
  326.                         "type": "checkbox",
  327.                         "value": true
  328.                 },
  329.                 "revealSpoilers": {
  330.                         "name": "Reveal Spoilers",
  331.                         "description": "Spoilered text will be displayed without needing to hover over it",
  332.                         "type": "checkbox",
  333.                         "value": false
  334.                 },
  335.                 "filter": {
  336.                         "name": "Filter",
  337.                         "description": "Hide undesirable posts from view",
  338.                         "type": "checkbox",
  339.                         "value": false,
  340.                         "suboptions": {
  341.                                 "filterNotifications": {
  342.                                         "name": "Filter Notifications",
  343.                                         "description": "Enabling this will stop creating reply notifications if the reply is filtered with hide or remove mode. Purge mode filtered replies will never create notifications",
  344.                                         "type": "checkbox",
  345.                                         "value": true
  346.                                 },
  347.                                 "recursiveFiltering": {
  348.                                         "name": "Recursive Filtering",
  349.                                         "description": "Posts that reply to filtered posts will also be filtered",
  350.                                         "type": "checkbox",
  351.                                         "value": false
  352.                                 }
  353.                         }
  354.                 },
  355.                 "adjustReplybox": {
  356.                         "name": "Adjust Replybox",
  357.                         "description": "Change the layout of the reply box",
  358.                         "type": "checkbox",
  359.                         "value": true,
  360.                         "suboptions": {
  361.                                 "width": {
  362.                                         "name": "Width",
  363.                                         "description": "Specify the default width of the reply field in pixels",
  364.                                         "type": "number",
  365.                                         "value": 600
  366.                                 },
  367.                                 "hideQROptions": {
  368.                                         "name": "Hide QR Options",
  369.                                         "description": "Make the reply options hidden by default in the quick reply",
  370.                                         "type": "checkbox",
  371.                                         "value": true
  372.                                 },
  373.                                 "removeReset": {
  374.                                         "name": "Remove Reset",
  375.                                         "description": "Remove the reset button from the reply box to prevent unwanted usage",
  376.                                         "type": "checkbox",
  377.                                         "value": false
  378.                                 }
  379.                         }
  380.                 },
  381.                 "postCounter": {
  382.                         "name": "Post Counter",
  383.                         "description": "Add a post counter to the reply box",
  384.                         "type": "checkbox",
  385.                         "value": true,
  386.                         "suboptions": {
  387.                                 "location": {
  388.                                         "name": "Location",
  389.                                         "description": "Specify where the post counter is placed",
  390.                                         "type": "select",
  391.                                         "value": {"value": "Header bar", "options": ["Header bar", "Reply box"]}
  392.                                 },
  393.                                 "limits": {
  394.                                         "name": "Show count limits",
  395.                                         "description": "Adds count denominators, purely aesthetic",
  396.                                         "type": "checkbox",
  397.                                         "value": false,
  398.                                         "suboptions": {
  399.                                                 "posts": {
  400.                                                         "name": "Posts",
  401.                                                         "description": "Specify the posts counter denominator",
  402.                                                         "type": "number",
  403.                                                         "value": 400
  404.                                                 },
  405.                                                 "images": {
  406.                                                         "name": "Images",
  407.                                                         "description": "Specify the images counter denominator",
  408.                                                         "type": "number",
  409.                                                         "value": 250
  410.                                                 }
  411.                                         }
  412.                                 },
  413.                                 "countUnloaded": {
  414.                                         "name": "Count unloaded posts",
  415.                                         "description": "If only viewing the last x posts in a thread use this setting for the post counter to count the total number of posts rather than just the number of posts that have been loaded",
  416.                                         "type": "checkbox",
  417.                                         "value": true
  418.                                 },
  419.                                 "countHidden": {
  420.                                         "name": "Count hidden posts",
  421.                                         "description": "Adds a counter that displays how many posts of the total count are hidden",
  422.                                         "type": "checkbox",
  423.                                         "value": true,
  424.                                         "suboptions": {
  425.                                                 "hideNullHiddenCounter": {
  426.                                                         "name": "Auto-hide null hidden counter",
  427.                                                         "description": "If there are no hidden posts the post counter will not display the hidden counter",
  428.                                                         "type": "checkbox",
  429.                                                         "value": true
  430.                                                 }
  431.                                         }
  432.                                 }
  433.                         }
  434.                 },
  435.                 "mascot": {
  436.                         "name": "Mascot",
  437.                         "description": "Place your favourite mascot on the background to keep you company!",
  438.                         "type": "checkbox",
  439.                         "value": false,
  440.                         "suboptions": {
  441.                                 "mascotImage": {
  442.                                         "name": "Mascot image",
  443.                                         "description": "Specify a link to your custom mascot or leave blank for SpookyX defaults",
  444.                                         "type": "text",
  445.                                         "value": ""
  446.                                 },
  447.                                 "corner": {
  448.                                         "name": "Corner",
  449.                                         "description": "Specify which corner to align the mascot to",
  450.                                         "type": "select",
  451.                                         "value": {
  452.                                                 "value": "Bottom Right",
  453.                                                 "options": ["Top Right", "Bottom Right", "Bottom Left", "Top Left"]
  454.                                         }
  455.                                 },
  456.                                 "zindex": {
  457.                                         "name": "Z-index",
  458.                                         "description": "Determine what page elements the mascot is in front and behind of. Default value is -1",
  459.                                         "type": "number",
  460.                                         "value": -1
  461.                                 },
  462.                                 "opacity": {
  463.                                         "name": "Opacity",
  464.                                         "description": "Specify the opacity of the mascot, ranges from 0 to 1",
  465.                                         "type": "number",
  466.                                         "value": 1
  467.                                 },
  468.                                 "clickthrough": {
  469.                                         "name": "Click-through",
  470.                                         "description": "Allow you to click through the mascot if it is on top of buttons, etc",
  471.                                         "type": "checkbox",
  472.                                         "value": true
  473.                                 },
  474.                                 "width": {
  475.                                         "name": "Width",
  476.                                         "description": "Specify the width of the mascot in pixels. Use a negative number to leave it as the image's default width",
  477.                                         "type": "number",
  478.                                         "value": -1
  479.                                 },
  480.                                 "x": {
  481.                                         "name": "Horizontal Displacement",
  482.                                         "description": "Specify horizontal displacement of the mascot in pixels",
  483.                                         "type": "number",
  484.                                         "value": 0
  485.                                 },
  486.                                 "y": {
  487.                                         "name": "Vertical Displacement",
  488.                                         "description": "Specify vertical displacement of the mascot in pixels",
  489.                                         "type": "number",
  490.                                         "value": 0
  491.                                 },
  492.                                 "mute": {
  493.                                         "name": "Mute videos",
  494.                                         "description": "If using a video for a mascot the sound will be muted",
  495.                                         "type": "checkbox",
  496.                                         "value": true
  497.                                 }
  498.                         }
  499.                 },
  500.                 "postFlow": {
  501.                         "name": "Adjust post flow",
  502.                         "description": "Change the way posts are laid out in the page",
  503.                         "type": "checkbox",
  504.                         "value": true,
  505.                         "suboptions": {
  506.                                 "leftMargin": {
  507.                                         "name": "Left margin",
  508.                                         "description": "Specify the width in pixels of the gap between the start of the posts and the left side of the screen. Negative values set it to equal the mascot width",
  509.                                         "type": "number",
  510.                                         "value": 0
  511.                                 },
  512.                                 "rightMargin": {
  513.                                         "name": "Right margin",
  514.                                         "description": "Specify the width in pixels of the gap between the end of the posts and the right side of the screen. Negative values set it to equal the mascot width",
  515.                                         "type": "number",
  516.                                         "value": 0
  517.                                 },
  518.                                 "align": {
  519.                                         "name": "Align",
  520.                                         "description": "Specify how posts are aligned",
  521.                                         "type": "select",
  522.                                         "value": {"value": "Left", "options": ["Left", "Center", "Right"]}
  523.                                 },
  524.                                 "wordBreak": {
  525.                                         "name": "Word-break",
  526.                                         "description": "Firefox runs into difficulties with breaking really long words, test the options available until you find something that works. On auto this attempts to detect browser and select the most appropriate setting",
  527.                                         "type": "select",
  528.                                         "value": {"value": "Auto", "options": ["Auto", "Break-all", "Normal", "Overflow-Wrap"]}
  529.                                 }
  530.                         }
  531.                 },
  532.                 "headerBar": {
  533.                         "name": "Adjust Headerbar behaviour",
  534.                         "description": "Determine whether the headerbar hides and how it does so",
  535.                         "type": "checkbox",
  536.                         "value": true,
  537.                         "suboptions": {
  538.                                 "behaviour": {
  539.                                         "name": "Behaviour",
  540.                                         "description": "Firefox runs into difficulties with breaking really long words, test the options available until you find something that works. On auto this attempts to detect browser and select the most appropriate setting",
  541.                                         "type": "select",
  542.                                         "value": {
  543.                                                 "value": "Collapse to button",
  544.                                                 "options": ["Always show", "Full hide", "Collapse to button"]
  545.                                         },
  546.                                         "suboptions": {
  547.                                                 "scroll": {
  548.                                                         "name": "Hide on scroll",
  549.                                                         "description": "Scrolling up will show the headerbar, scrolling down will hide it again",
  550.                                                         "if": ["Full hide", "Collapse to button"],
  551.                                                         "type": "checkbox",
  552.                                                         "value": false
  553.                                                 },
  554.                                                 "defaultHidden": {
  555.                                                         "name": "Default state hidden",
  556.                                                         "description": "Check to make the headerbar hidden or collapsed by default on pageload",
  557.                                                         "if": ["Full hide", "Collapse to button"],
  558.                                                         "type": "checkbox",
  559.                                                         "value": true
  560.                                                 },
  561.                                                 "contractedForm": {
  562.                                                         "name": "Customise contracted form",
  563.                                                         "description": "Specify what the contracted headerbar form contains",
  564.                                                         "if": ["Collapse to button"],
  565.                                                         "type": "checkbox",
  566.                                                         "value": true,
  567.                                                         "suboptions": {
  568.                                                                 "settings": {
  569.                                                                         "name": "Settings button",
  570.                                                                         "description": "Display the settings button in contracted headerbar",
  571.                                                                         "type": "checkbox",
  572.                                                                         "value": false
  573.                                                                 },
  574.                                                                 "postCounter": {
  575.                                                                         "name": "Post counter",
  576.                                                                         "description": "Display the post counter stats in contracted headerbar",
  577.                                                                         "type": "checkbox",
  578.                                                                         "value": true
  579.                                                                 }
  580.                                                         }
  581.                                                 }
  582.                                         }
  583.                                 },
  584.                                 "shortcut": {
  585.                                         "name": "Hide shortcut",
  586.                                         "description": "Pressing H will toggle the visiblity of the headerbar",
  587.                                         "type": "checkbox",
  588.                                         "value": true
  589.                                 }
  590.                         }
  591.                 },
  592.                 "removeJfont": {
  593.                         "name": "Remove Japanese Font",
  594.                         "description": "Enabling this will make the addition of japanese characters to a post cease to change the post font and size. Presumably will cause issues for people whose default font doesn't support japanese characters",
  595.                         "type": "checkbox",
  596.                         "value": false
  597.                 },
  598.                 "labelDeletions": {
  599.                         "name": "Label Deletions",
  600.                         "description": "Enabling this will add 'Deleted' to all trashcan icons that designate deleted posts to allow for easier searching",
  601.                         "type": "checkbox",
  602.                         "value": false
  603.                 }
  604.         },
  605.         "FilterSettings": {
  606.                 "name": {
  607.                         "name": "Name",
  608.                         "value": [
  609.                                 {"comment": "#/久保島のミズゴロウ/;"}
  610.                         ],
  611.                         "threadPostFunction": function(currentPost){
  612.                                 return $(currentPost).find('.post_author').html();
  613.                         },
  614.                         "responseObjFunction": function(response){
  615.                                 return response['name_processed'];
  616.                         }
  617.                 },
  618.                 "tripcode": {
  619.                         "name": "Tripcode",
  620.                         "value": [
  621.                                 {"comment": "#/!!/90sanF9F3Z/;"},
  622.                                 {"comment": "#/!!T2TCnNZDvZu/;"}
  623.                         ],
  624.                         "threadPostFunction": function(currentPost){
  625.                                 return $(currentPost).find('.post_tripcode').html();
  626.                         },
  627.                         "responseObjFunction": function(response){
  628.                                 return response['trip_processed'];
  629.                         }
  630.                 },
  631.                 "uniqueID": {
  632.                         "name": "Unique ID",
  633.                         "value": [
  634.                                 {"comment": "# Remember to escape any special characters"},
  635.                                 {"comment": "# For example these are valid:"},
  636.                                 {"comment": "#/bUAl\\+t9X/;"},
  637.                                 {"comment": "#/ID:bUAl\\+t9X/;"},
  638.                                 {"comment": "# But this fails:"},
  639.                                 {"comment": "#/bUAl+t9X/; "},
  640.                                 {"comment": "# It's also worth noting that prefixing it with 'ID:' can cause the filter to fail to accurately detect when using recursive filtering. To assure it works fully stick to just using the hash like 'bUAl+t9X'"}
  641.                         ],
  642.                         "threadPostFunction": function(currentPost){
  643.                                 return $(currentPost).find('.poster_hash').html();
  644.                         },
  645.                         "responseObjFunction": function(response){
  646.                                 return response['poster_hash_processed'];
  647.                         }
  648.                 },
  649.                 "capcode": {
  650.                         "name": "Capcode",
  651.                         "value": [
  652.                                 {"comment": "# Set a custom class for mods:"},
  653.                                 {"comment": "#/Mod$/;highlight:mod;"},
  654.                                 {"comment": "# Set a custom class for moot:"},
  655.                                 {"comment": "#/Admin$/;highlight:moot;"},
  656.                                 {"comment": "# (highlighting isn't implemented yet)"},
  657.                                 {"comment": "# For recursive filter to always work you will need to add regex lines for M, A & D for Moderators, Admins and Developers respectively"},
  658.                                 {"comment": "# e.g. /A/; will filter Admins accurately always whilst /Admin/; won't always work for recursively filtered posts"}
  659.                         ],
  660.                         "threadPostFunction": function(currentPost){
  661.                                 return $(currentPost).find('.post_level').html();
  662.                         },
  663.                         "responseObjFunction": function(response){
  664.                                 return response['capcode'];
  665.                         }
  666.                 },
  667.                 "subject": {
  668.                         "name": "Subject",
  669.                         "value": [
  670.                                 {"comment": "#/(^|[^A-z])quest([^A-z]|$)/i;boards:tg;"}
  671.                         ],
  672.                         "threadPostFunction": function(currentPost){
  673.                                 return $(currentPost).find('.post_title').html();
  674.                         },
  675.                         "responseObjFunction": function(response){
  676.                                 return response['title_processed'];
  677.                         }
  678.                 },
  679.                 "comment": {
  680.                         "name": "Comment",
  681.                         "value": [
  682.                                 {"comment": "#/daki[\\\\S]*/i; boards:tg;"}
  683.                         ],
  684.                         "threadPostFunction": function(currentPost){
  685.                                 return $(currentPost).find('.text').html();
  686.                         },
  687.                         "responseObjFunction": function(response){
  688.                                 return response['comment'];
  689.                         }
  690.                 },
  691.                 "flag": {
  692.                         "name": "Flag",
  693.                         "value": [
  694.                                 {"comment": "#Remove kebob"},
  695.                                 {"comment": "#/turkey/i;mode:remove;"}
  696.                         ],
  697.                         "threadPostFunction": function(currentPost){
  698.                                 return $(currentPost).find('.flag').attr('title');
  699.                         },
  700.                         "responseObjFunction": function(response){
  701.                                 return response['poster_country_name_processed'];
  702.                         }
  703.                 },
  704.                 "filename": {
  705.                         "name": "Filename",
  706.                         "value": [],
  707.                         "threadPostFunction": function(currentPost){
  708.                                 var combined = '';
  709.                                 if ($(currentPost).hasClass('thread')) {
  710.                                         combined = $(currentPost).find('.post_file_filename').html();
  711.                                 } else {
  712.                                         $.each($(currentPost).find('.post_file_filename'), function(){
  713.                                                 combined += this.innerHTML;
  714.                                         });
  715.                                 }
  716.                                 return combined;
  717.                         },
  718.                         "responseObjFunction": function(response){
  719.                                 if (response['media'] === null || response['media'] === undefined) {
  720.                                         return '';
  721.                                 }
  722.                                 return response['media']['media_filename_processed'];
  723.                         }
  724.                 },
  725.                 "fileurl": {
  726.                         "name": "File URL",
  727.                         "value": [
  728.                                 {"comment": "# Filter by site for example:"},
  729.                                 {"comment": "#/tumblr/;"}
  730.                         ],
  731.                         "threadPostFunction": function(currentPost){
  732.                                 var combined = '';
  733.                                 var $currentPost = $(currentPost);
  734.                                 if ($currentPost.hasClass('thread')) {
  735.                                         var $currentPostFilename = $currentPost.find('.post_file_filename');
  736.                                         if ($currentPostFilename.length) {
  737.                                                 combined = $currentPostFilename[0].href;
  738.                                         }
  739.                                 } else {
  740.                                         $.each($currentPost.find('.post_file_filename'), function(){
  741.                                                 combined += this.href;
  742.                                         });
  743.                                 }
  744.                                 return combined;
  745.                         },
  746.                         "responseObjFunction": function(response){
  747.                                 if (response['media'] === null || response['media'] === undefined) {
  748.                                         return '';
  749.                                 }
  750.                                 return response['media']['remote_media_link'];
  751.                         }
  752.                 }
  753.         }
  754. };
  755.  
  756. var defaultSettings = jQuery.extend(true, {}, settings);
  757.  
  758. var defaultMascots = [
  759.         "",
  760.         "",
  761.         "",
  762.         "",
  763.         "",
  764.         "",
  765.         "",
  766.         "",
  767.         "",
  768.         "",
  769.         "",
  770.         "",
  771.         "",
  772.         "",
  773.         "",
  774.         "",
  775.         ""
  776. ];
  777.  
  778. if (localStorage.SpookyXsettings !== undefined) {
  779.         $.extend(true, settings, JSON.parse(localStorage.SpookyXsettings));
  780. }
  781.  
  782. var newPostCount = 0;
  783. var notLoadedPostCount = 0;
  784. var DocumentTitle = document.title;
  785. var rulesBox = $(".rules_box").html();
  786. var autoplayVid = '';
  787. if (settings.UserSettings.inlineImages.suboptions.autoplayVids.value) {
  788.         autoplayVid = 'autoplay';
  789. }
  790.  
  791. var filetypes = {
  792.         IMAGES: ['jpg', 'jpeg', 'png', 'gif'],
  793.         VIDEOS: ['webm', 'mp4', 'gifv']
  794. };
  795.  
  796. var pattImageFiletypes = new RegExp('\\.(' + filetypes.IMAGES.join('|') + ')($|(\\?|:)[\\S]+$)', 'i');
  797. var pattVideoFiletypes = new RegExp('\\.(' + filetypes.VIDEOS.join('|') + ')($|(\\?|:)[\\S]+$)', 'i');
  798. var pattYoutubeLink = new RegExp('(youtube\\.com|youtu\\.be)', 'i');
  799. var pattImgGal = new RegExp('http[s]?:);
  800.  
  801. var splitURL = (document.URL).toLowerCase().split("/");
  802. var board = splitURL[3];
  803. var threadID = splitURL[4];
  804. if (threadID === "thread") {
  805.         threadID = splitURL[5];
  806. } else if (threadID === "last") {
  807.         threadID = splitURL[6];
  808. } else if (threadID !== "search" && threadID !== "reports") {
  809.         if (board === "_" || threadID === "page" || threadID === "ghost" || threadID === "" || threadID === undefined) {
  810.                 if (board !== "" && board !== undefined && board !== "_") {
  811.                         threadID = "board";
  812.                 } else {
  813.                         threadID = "other";
  814.                 }
  815.         }
  816. }
  817. var boardPatt = new RegExp("(^|,)\\s*" + board + "\\s*(,|$)");
  818.  
  819. var Page = {
  820.         is: function(type){
  821.                 if (Page.cache[type] !== undefined) {
  822.                         return Page.cache[type];
  823.                 } else {
  824.                         if (Page.hasOwnProperty(type)) {
  825.                                 Page.cache[type] = Page[type]();
  826.                                 return Page.cache[type];
  827.                         }
  828.                         var typeArray = type.split(',');
  829.                         for (var i = 0; i < typeArray.length; i++) {
  830.                                 if ((typeArray[i])) {
  831.                                         Page.cache[type] = true;
  832.                                         return true;
  833.                                 }
  834.                         }
  835.                         Page.cache[type] = false;
  836.                         return false;
  837.                 }
  838.         },
  839.         cache: {},
  840.         'thread': function(){
  841.                 return /[0-9]+/.test(threadID);
  842.         },
  843.         'board': function(){
  844.                 return /board/.test(threadID);
  845.         },
  846.         'gallery': function(){
  847.                 return /gallery/.test(threadID);
  848.         },
  849.         'other': function(){
  850.                 return /other/.test(threadID);
  851.         },
  852.         'quests': function(){
  853.                 return /quests/.test(threadID);
  854.         },
  855.         'search': function(){
  856.                 return /search/.test(threadID);
  857.         },
  858.         'statistics': function(){
  859.                 return /statistics/.test(threadID);
  860.         }
  861. };
  862.  
  863. ;
  864.  + board);
  865.  + threadID);
  866.  
  867.  
  868. // As taken from
  869. function allowMockHover(){
  870.  
  871.         // iterate over all styleSheets
  872.         for (var i = 0, l = document.styleSheets.length; i < l; i++) {
  873.                 var s = document.styleSheets[i];
  874.                 if (s.cssRules == null) continue;
  875.  
  876.                 // iterate over all rules in styleSheet
  877.                 for (var x = 0, rl = s.cssRules.length; x < rl; x++) {
  878.                         var r = s.cssRules[x];
  879.                         if (r.selectorText && r.selectorText.indexOf(':hover') >= 0) {
  880.                                 fixRule(r);
  881.                         }
  882.                 }
  883.         }
  884.  
  885. }
  886.  
  887. function fixRule(rule){
  888.  
  889.         // if the current rule has several selectors, treat them separately:
  890.         var parts = rule.selectorText.split(',');
  891.         for (var i = 0, l = parts.length; i < l; i++) {
  892.                 if (parts[i].indexOf(':hover') >= 0) {
  893.                         // update selector to be same + selector with class
  894.                         parts[i] = [parts[i], parts[i].replace(/:hover/gi, '.mock-hover')].join(',');
  895.                 }
  896.         }
  897.  
  898.         // update rule
  899.         rule.selectorText = parts.join(',');
  900. }
  901.  
  902.  
  903. var imageWidthOP = 250;
  904. var imageHeightOP = 250;
  905. var imageWidth = 125;
  906. var imageHeight = 125;
  907. if (settings.UserSettings.inlineImages.suboptions.customSize.value) {
  908.         imageWidthOP = settings.UserSettings.inlineImages.suboptions.customSize.suboptions.widthOP.value;
  909.         imageHeightOP = settings.UserSettings.inlineImages.suboptions.customSize.suboptions.heightOP.value;
  910.         imageWidth = settings.UserSettings.inlineImages.suboptions.customSize.suboptions.width.value;
  911.         imageHeight = settings.UserSettings.inlineImages.suboptions.customSize.suboptions.height.value;
  912. }
  913.  
  914. var yourPostsLookup = {};
  915. if (('board,thread')) {
  916.         var crosslinkTracker = {};
  917.         if (localStorage.crosslinkTracker !== undefined) {
  918.                 crosslinkTracker = JSON.parse(localStorage.crosslinkTracker);
  919.         }
  920.         if (crosslinkTracker[board] === undefined) {
  921.                 crosslinkTracker[board] = {};
  922.         }
  923.         crosslinkTracker[board][threadID] = {};
  924.         localStorage.crosslinkTracker = JSON.stringify(crosslinkTracker);
  925. }
  926.  
  927. var faviconUnlit;
  928. var faviconLit;
  929. var faviconAlert;
  930. var faviconNotification;
  931. var faviconState = "unlit";
  932.  
  933. function generateFavicons(){ // Generate dynamic favicons
  934.         if (settings.UserSettings.favicon.suboptions.customFavicons.value) {
  935.                 switch (settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value) {
  936.                         case "0":
  937.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value = "";
  938.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alert.value = "";
  939.                                 break;
  940.                         case "1":
  941.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value = "";
  942.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alert.value = "";
  943.                                 break;
  944.                         case "2":
  945.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value = "";
  946.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alert.value = "";
  947.                                 break;
  948.                         case "3":
  949.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value = "";
  950.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alert.value = "";
  951.                                 break;
  952.                         case "4":
  953.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value = "";
  954.                                 settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alert.value = "";
  955.                                 break;
  956.                         default:
  957.                                 break;
  958.                 }
  959.                 faviconUnlit = settings.UserSettings.favicon.suboptions.customFavicons.suboptions.unlit.value; // Store unlit favicon
  960.                 faviconLit = settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value; // Store lit favicon
  961.                 faviconAlert = settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alert.value; // Store alert favicon
  962.                 faviconNotification = settings.UserSettings.favicon.suboptions.customFavicons.suboptions.notification.value; // Store notification favicon
  963.                 setFavicon();
  964.         } else {
  965.                 var faviconCanvas = document.createElement('canvas');
  966.                 var nativeFavicon = $('<img src="/favicon.ico">');
  967.                 var overlayFavicon = $('<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL);
  968.                 var ctx = faviconCanvas.getContext('2d');
  969.                 nativeFavicon.on('load', function(e){
  970.                         faviconCanvas.height = e.target.naturalHeight;
  971.                         faviconCanvas.width = e.target.naturalWidth;
  972.                         ctx.drawImage(e.target, 0, 0); // Draw native favicon
  973.                         faviconUnlit = faviconCanvas.toDataURL('image/png'); // Store unlit favicon
  974.                         var faviconData = ctx.getImageData(0, 0, faviconCanvas.height, faviconCanvas.width);
  975.                         var meanColour = [0, 0, 0, 0];
  976.                         var scale = Math.floor(64 / faviconCanvas.width);
  977.                         var faviconNotificationData = faviconData;
  978.                         var i, len;
  979.                         if (scale - 1) { // Only upscale if scale is 2 or more
  980.                                 faviconNotificationData = new ImageData(scale * faviconCanvas.width, scale * faviconCanvas.height);
  981.                                 for (i = 0; i < scale; i++) {
  982.                                         for (var j = 0; j < scale; j++) {
  983.                                                 for (var x = 0, width = faviconCanvas.width, widths = width * 4; x < widths; x += 4) {
  984.                                                         for (var y = 0, height = faviconCanvas.height, heights = height * 4; y < heights; y += 4) {
  985.                                                                 var start = x + (y * width);
  986.                                                                 var end = (((x) * scale) + ((y) * width * scale * scale)) + (4 * i) + (4 * j * height * scale);
  987.                                                                 faviconNotificationData.data[end] = faviconData.data[start];
  988.                                                                 faviconNotificationData.data[end + 1] = faviconData.data[start + 1];
  989.                                                                 faviconNotificationData.data[end + 2] = faviconData.data[start + 2];
  990.                                                                 faviconNotificationData.data[end + 3] = faviconData.data[start + 3];
  991.                                                         }
  992.                                                 }
  993.                                         }
  994.                                 }
  995.                         }
  996.                         for (i = 0, len = faviconData.data.length; i < len; i++) {
  997.                                 meanColour[i % 4] += faviconData.data[i];
  998.                         }
  999.                         for (i = 0; i < 4; i++) {
  1000.                                 meanColour[i] /= len / 4;
  1001.                         }
  1002.                         var colour = new colz.Color(meanColour);
  1003.                         for (i = 0, len = faviconData.data.length; i < len; i += 4) {
  1004.                                 var col = new colz.Color(faviconData.data[i], faviconData.data[i + 1], faviconData.data[i + 2], faviconData.data[i + 3]);
  1005.                                 col.setLum(col.l + ((100 - col.l) * (100 - col.l)) / (100 * 1.5)); // Raise luminosity
  1006.                                 col.setSat(col.s + (100 - colour.s) * 0.3); // Raise saturation
  1007.                                 col.setHue(180 + colour.h); // Shift hue
  1008.                                 col.setLum(1.8 * (col.l - 128) + 128 + 32); // Raise contrast
  1009.                                 faviconData.data[i] = col.r;
  1010.                                 faviconData.data[i + 1] = col.g;
  1011.                                 faviconData.data[i + 2] = col.b;
  1012.                                 faviconData.data[i + 3] = col.a;
  1013.                         }
  1014.                         ctx.putImageData(faviconData, 0, 0); // Draw edited data on canvas
  1015.                         faviconLit = faviconCanvas.toDataURL('image/png'); // Store lit favicon
  1016.                         ctx.drawImage(overlayFavicon[0], 0, 0, faviconCanvas.height, faviconCanvas.width); // Draw alert symbol on canvas
  1017.                         faviconAlert = faviconCanvas.toDataURL('image/png'); // Store alert favicon
  1018.                         if (scale) {
  1019.                                 faviconCanvas.height *= scale;
  1020.                                 faviconCanvas.width *= scale;
  1021.                         }
  1022.                         ctx.putImageData(faviconNotificationData, 0, 0); // Draw faviconNotification on canvas
  1023.                         faviconNotification = faviconCanvas.toDataURL('image/png'); // Store notification favicon
  1024.                         setFavicon();
  1025.                 });
  1026.         }
  1027. }
  1028.  
  1029. function setFavicon(){
  1030.         if (faviconState === "unlit") {
  1031.                 $('#favicon').attr("href", faviconUnlit);
  1032.         } else if (faviconState === "lit") {
  1033.                 $('#favicon').attr("href", faviconLit);
  1034.         } else if (faviconState === "alert") {
  1035.                 $('#favicon').attr("href", faviconAlert);
  1036.         }
  1037. }
  1038.  
  1039. function ThreadUpdate(){
  1040.         if (settings.UserSettings.newPosts.value) {
  1041.                 newPosts();
  1042.         }
  1043. }
  1044.  
  1045. /**
  1046. * Retrieve nested item from object/array
  1047. * @param {Object|Array} obj
  1048. * @param {Array} path dot separated
  1049. * @param {*} def default value ( if result undefined )
  1050. * @returns {*}
  1051. */
  1052. function objpath(obj, path, def){
  1053.         var i, len;
  1054.  
  1055.         for (i = 0, path = path.split('.'), len = path.length; i < len; i++) {
  1056.                 if (!obj || typeof obj !== 'object') return def;
  1057.                 obj = obj[path[i]];
  1058.         }
  1059.  
  1060.         if (obj === undefined) return def;
  1061.         return obj;
  1062. }
  1063.  
  1064. shortcut = {
  1065.         'all_shortcuts': {}, the shortcuts are stored in this array
  1066.         'add': function(shortcut_combination, callback, opt){
  1067.                  a set of default options
  1068.                 var default_options = {
  1069.                         'type': 'keydown',
  1070.                         'propagate': false,
  1071.                         'disable_in_input': false,
  1072.                         'target': document,
  1073.                         'keycode': false
  1074.                 };
  1075.                 if (!opt) opt = default_options;
  1076.                 else {
  1077.                         for (var dfo in default_options) {
  1078.                                 if (default_options.hasOwnProperty(dfo) && typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];
  1079.                         }
  1080.                 }
  1081.  
  1082.                 var ele = opt.target;
  1083.                 if (typeof opt.target == 'string') ele = document.getElementById(opt.target);
  1084.                 shortcut_combination = shortcut_combination.toLowerCase();
  1085.  
  1086.                  function to be called at keypress
  1087.                 var func = function(e){
  1088.                         e = e || window.event;
  1089.  
  1090.                         if (opt.disable_in_input) {  enable shortcut keys in Input, Textarea fields
  1091.                                 var element;
  1092.                                 if (e.target) element = e.target;
  1093.                                 else if (e.srcElement) element = e.srcElement;
  1094.                                 if (element.nodeType == 3) element = element.parentNode;
  1095.  
  1096.                                 if (element.tagName == 'INPUT' || element.tagName == 'TEXTAREA' || $(element).hasClass('post_IP_name')) return;
  1097.                         }
  1098.  
  1099.                          Which key is pressed
  1100.                         var code;
  1101.                         if (e.keyCode) {
  1102.                                 code = e.keyCode;
  1103.                         } else if (e.which) {
  1104.                                 code = e.which;
  1105.                         }
  1106.                         var character = String.fromCharCode(code).toLowerCase();
  1107.  
  1108.                         if (code == 188) character = ",";  the user presses , when the type is onkeydown
  1109.                         if (code == 190) character = ".";  the user presses , when the type is onkeydown
  1110.  
  1111.                         var keys = shortcut_combination.split("+");
  1112.                          Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
  1113.                         var kp = 0;
  1114.  
  1115.                          around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
  1116.                         var shift_nums = {
  1117.                                 "`": "~",
  1118.                                 "1": "!",
  1119.                                 "2": "@",
  1120.                                 "3": "#",
  1121.                                 "4": "$",
  1122.                                 "5": "%",
  1123.                                 "6": "^",
  1124.                                 "7": "&",
  1125.                                 "8": "*",
  1126.                                 "9": "(",
  1127.                                 "0": ")",
  1128.                                 "-": "_",
  1129.                                 "=": "+",
  1130.                                 ";": ":",
  1131.                                 "'": "\"",
  1132.                                 ",": "<",
  1133.                                 ".": ">",
  1134.                                 "/": "?",
  1135.                                 "\\": "|"
  1136.                         };
  1137.                          Keys - and their codes
  1138.                         var special_keys = {
  1139.                                 'esc': 27,
  1140.                                 'escape': 27,
  1141.                                 'tab': 9,
  1142.                                 'space': 32,
  1143.                                 'return': 13,
  1144.                                 'enter': 13,
  1145.                                 'backspace': 8,
  1146.  
  1147.                                 'scrolllock': 145,
  1148.                                 'scroll_lock': 145,
  1149.                                 'scroll': 145,
  1150.                                 'capslock': 20,
  1151.                                 'caps_lock': 20,
  1152.                                 'caps': 20,
  1153.                                 'numlock': 144,
  1154.                                 'num_lock': 144,
  1155.                                 'num': 144,
  1156.  
  1157.                                 'pause': 19,
  1158.                                 'break': 19,
  1159.  
  1160.                                 'insert': 45,
  1161.                                 'home': 36,
  1162.                                 'delete': 46,
  1163.                                 'end': 35,
  1164.  
  1165.                                 'pageup': 33,
  1166.                                 'page_up': 33,
  1167.                                 'pu': 33,
  1168.  
  1169.                                 'pagedown': 34,
  1170.                                 'page_down': 34,
  1171.                                 'pd': 34,
  1172.  
  1173.                                 'left': 37,
  1174.                                 'up': 38,
  1175.                                 'right': 39,
  1176.                                 'down': 40,
  1177.  
  1178.                                 'f1': 112,
  1179.                                 'f2': 113,
  1180.                                 'f3': 114,
  1181.                                 'f4': 115,
  1182.                                 'f5': 116,
  1183.                                 'f6': 117,
  1184.                                 'f7': 118,
  1185.                                 'f8': 119,
  1186.                                 'f9': 120,
  1187.                                 'f10': 121,
  1188.                                 'f11': 122,
  1189.                                 'f12': 123
  1190.                         };
  1191.  
  1192.                         var modifiers = {
  1193.                                 shift: {wanted: false, pressed: false},
  1194.                                 ctrl: {wanted: false, pressed: false},
  1195.                                 alt: {wanted: false, pressed: false},
  1196.                                 meta: {wanted: false, pressed: false}  is Mac specific
  1197.                         };
  1198.  
  1199.                         if (e.ctrlKey) modifiers.ctrl.pressed = true;
  1200.                         if (e.shiftKey)  modifiers.shift.pressed = true;
  1201.                         if (e.altKey)  modifiers.alt.pressed = true;
  1202.                         if (e.metaKey)   modifiers.meta.pressed = true;
  1203.  
  1204.                         for (var i = 0; k = keys[i], i < keys.length; i++) {
  1205.                                
  1206.                                 if (k == 'ctrl' || k == 'control') {
  1207.                                         kp++;
  1208.                                         modifiers.ctrl.wanted = true;
  1209.  
  1210.                                 } else if (k == 'shift') {
  1211.                                         kp++;
  1212.                                         modifiers.shift.wanted = true;
  1213.  
  1214.                                 } else if (k == 'alt') {
  1215.                                         kp++;
  1216.                                         modifiers.alt.wanted = true;
  1217.                                 } else if (k == 'meta') {
  1218.                                         kp++;
  1219.                                         modifiers.meta.wanted = true;
  1220.                                 } else if (k.length > 1) {  it is a special key
  1221.                                         if (special_keys[k] == code) kp++;
  1222.  
  1223.                                 } else if (opt.keycode) {
  1224.                                         if (opt.keycode == code) kp++;
  1225.  
  1226.                                 } else {  special keys did not match
  1227.                                         if (character == k) kp++;
  1228.                                         else {
  1229.                                                 if (shift_nums[character] && e.shiftKey) {  Shift key bug created by using lowercase
  1230.                                                         character = shift_nums[character];
  1231.                                                         if (character == k) kp++;
  1232.                                                 }
  1233.                                         }
  1234.                                 }
  1235.                         }
  1236.  
  1237.                         if (kp == keys.length &&
  1238.                                 modifiers.ctrl.pressed == modifiers.ctrl.wanted &&
  1239.                                 modifiers.shift.pressed == modifiers.shift.wanted &&
  1240.                                 modifiers.alt.pressed == modifiers.alt.wanted &&
  1241.                                 modifiers.meta.pressed == modifiers.meta.wanted) {
  1242.                                 callback(e);
  1243.  
  1244.                                 if (!opt.propagate) {  the event
  1245.                                          is supported by IE - this will kill the bubbling process.
  1246.                                         e.cancelBubble = true;
  1247.                                         e.returnValue = false;
  1248.  
  1249.                                          works in Firefox.
  1250.                                         if (e.stopPropagation) {
  1251.                                                 e.stopPropagation();
  1252.                                                 e.preventDefault();
  1253.                                         }
  1254.                                         return false;
  1255.                                 }
  1256.                         }
  1257.                 };
  1258.                 this.all_shortcuts[shortcut_combination] = {
  1259.                         'callback': func,
  1260.                         'target': ele,
  1261.                         'event': opt.type
  1262.                 };
  1263.                  the function with the event
  1264.                 if (ele.addEventListener) ele.addEventListener(opt.type, func, false);
  1265.                 else if (ele.attachEvent) ele.attachEvent('on' + opt.type, func);
  1266.                 else ele['on' + opt.type] = func;
  1267.         },
  1268.  
  1269.          the shortcut - just specify the shortcut and I will remove the binding
  1270.         'remove': function(shortcut_combination){
  1271.                 shortcut_combination = shortcut_combination.toLowerCase();
  1272.                 var binding = this.all_shortcuts[shortcut_combination];
  1273.                 delete(this.all_shortcuts[shortcut_combination]);
  1274.                 if (!binding) return;
  1275.                 var type = binding.event;
  1276.                 var ele = binding.target;
  1277.                 var callback = binding.callback;
  1278.  
  1279.                 if (ele.detachEvent) ele.detachEvent('on' + type, callback);
  1280.                 else if (ele.removeEventListener) ele.removeEventListener(type, callback, false);
  1281.                 else ele['on' + type] = false;
  1282.         }
  1283. };
  1284.  
  1285. function delayedLoad(posts){
  1286.         posts.each(function(i, post){
  1287.                 ($(post).hasClass('thread') ? $(post).children('.thread_image_box').find('img') : $(post).find('img')).each(function(i, image){
  1288.                         var $image = $(image);
  1289.                         $image.data('dontHover', true); // Stop imageHover displaying the thumbnails
  1290.                         $image.on('click', function(e){ // Stop the OP from returning all the sub images and thus duplicating them
  1291.                                 if (!e.originalEvent.ctrlKey && e.which == 1) {
  1292.                                         e.preventDefault();
  1293.                                         var $target = $(e.target);
  1294.                                         $target.removeData('dontHover');
  1295.                                         $target.off('click'); // Remove event listener now that it's served its purpose
  1296.                                         inlineImages($target.closest('article'));
  1297.                                         if (!settings.UserSettings.inlineImages.suboptions.autoplayGifs.value) {
  1298.                                                 pauseGifs($target);
  1299.                                         } // Stop gifs autoplaying
  1300.                                 }
  1301.                         });
  1302.                 });
  1303.         });
  1304. }
  1305.  
  1306. function inlineImages(posts){
  1307.         posts.each(function(i, post){
  1308.                 var $post = $(post);
  1309.                 ($post.hasClass('thread') ? $post.children('.thread_image_box') : $post.find('.thread_image_box')).each(function(i, currentImage){
  1310.                         var $currentImage = $(currentImage);
  1311.                         $currentImage.find('>a').each(function(j, imgLink){
  1312.                                 var fullImage = imgLink.href;
  1313.                                 if (settings.UserSettings.inlineImages.suboptions.processSpoiler.value && $currentImage.find('.spoiler_box').length) {
  1314.                                         $(imgLink).html('<div class="spoilerText">Spoiler</div><img class="smallImage spoilerImage">');
  1315.                                         var $image = $currentImage.find('img');
  1316.                                         $('load', function(e){
  1317.                                                 $currentImage.find(".spoilerText").css({"top": (e.target.height / 2) - 6.5}); // Center spoiler text
  1318.                                         });
  1319.                                 }
  1320.                                 if (/\.webm$/i.test(fullImage)) { // Handle post webms
  1321.                                         if (settings.UserSettings.inlineImages.suboptions.inlineVideos.value) {
  1322.                                                 $currentImage.prepend('<video width="' + ($(post).hasClass('thread') ? imageWidthOP : imageWidth) + '" name="media" loop muted ' + autoplayVid + '><source src="' + fullImage + '" type="video/webm"></video>');
  1323.                                                 $(imgLink).remove();
  1324.                                                 addHover($currentImage);
  1325.                                         }
  1326.                                 } else if (!/\.(pdf|swf)$/i.test(fullImage)) {
  1327.                                         $currentImage.find('img').each(function(k, image){
  1328.                                                 var $image = $(image);
  1329.                                                 var thumbImage = $(image).attr('src');
  1330.                                                 $image.attr('src', fullImage);
  1331.                                                 $image.error(function(){ // Handle images that won't load
  1332.                                                         if (!$image.data('tried4pleb')) {
  1333.                                                                 $image.data('tried4pleb', true);
  1334.                                                                 var imgLink4pleb = fullImage.replace(', ');
  1335.                                                                 $image.attr('src', imgLink4pleb);
  1336.                                                                 $image.parent().attr('href', imgLink4pleb); // Change link
  1337.                                                         } else if (!$image.data('triedThumb')) {
  1338.                                                                 $image.data('triedThumb', true);
  1339.                                                                 if (fullImage !== thumbImage) { // If the image has a thumbnail aka was 4chan native then use that
  1340.                                                                         $image.attr('src', thumbImage);
  1341.                                                                         $image.parent().attr('href', fullImage); // Reset link if changed to 4pleb attempt
  1342.                                                                 }
  1343.                                                         }
  1344.                                                 });
  1345.                                                 addHover($currentImage);
  1346.                                         });
  1347.                                 }
  1348.                         });
  1349.                 });
  1350.         });
  1351. }
  1352.  
  1353. function getSelectionText(){
  1354.         var text = "";
  1355.         if (window.getSelection) {
  1356.                 text = window.getSelection().toString();
  1357.         } else if (document.selection && document.selection.type != "Control") {
  1358.                 text = document.selection.createRange().text;
  1359.         }
  1360.         return text;
  1361. }
  1362.  
  1363. function togglePost(postID, mode){
  1364.         var $postID = $('#' + postID);
  1365.         if (mode == "hide") {
  1366.                 $postID.css({'display': 'none'});
  1367.                 $postID.prev().css({'display': 'block'});
  1368.         } else if (mode == "show") {
  1369.                 $postID.css({'display': 'block'});
  1370.                 $postID.prev().css({'display': 'none'});
  1371.         } else {
  1372.                 $postID.toggle();
  1373.                 $postID.prev().toggle();
  1374.         }
  1375.         postCounter(); // Update hidden post counter
  1376. }
  1377.  
  1378. function recursiveToggle(postID, mode){
  1379.         var checkedPostCollection = {};
  1380.         var postList = [postID];
  1381.         for (var i = 0; i < postList.length; i++) {
  1382.                 checkedPostCollection[postList[i]] = true;
  1383.                 $('#p_b' + postList[i] + ' > a').each(function(i, backlink){
  1384.                         var backlinkID = ;
  1385.                         if (!checkedPostCollection[backlinkID]) {
  1386.                                 postList.push(backlinkID);
  1387.                         }
  1388.                 });
  1389.         }
  1390.         for (var j = 0, len = postList.length; j < len; j++) {
  1391.                 togglePost(postList[j], mode);
  1392.         }
  1393. }
  1394.  
  1395. function filter(posts){
  1396.         posts.each(function(index, currentPost){
  1397.                 var $currentPost = $(currentPost);
  1398.                 if (!/!!UG0p3gRn3T1/.test($currentPost.find('.post_tripcode').html())) {
  1399.                         if (settings.UserSettings.filter.suboptions.recursiveFiltering.value && !$currentPost.hasClass('thread')) { // Recursive filter and not OP
  1400.                                 var checkedBacklinks = {};
  1401.                                 $currentPost.find('.text .backlink').each(function(i, backlink){
  1402.                                         if (!checkedBacklinks[backlink.dataset.board + ]) { // Prevent reprocessing duplicate links
  1403.                                                 checkedBacklinks[backlink.dataset.board + ] = true;
  1404.                                                 var backlinkPost = $('#' + );
  1405.                                                 if (backlink.dataset.board === board && backlinkPost.length) { // If linked post is present in thread
  1406.                                                         if ((':visible')) { // If linked post is visible
  1407.                                                                 if (backlinkPost.hasClass('shitpost')) { // If linked post is a shitpost
  1408.                                                                         $currentPost.addClass('shitpost');
  1409.                                                                 }
  1410.                                                         } else { // Linked post isn't visible
  1411.                                                                 if (backlinkPost.prev().is(':visible')) { // If the hide post stub is visible (and thus the linked post is hidden)
  1412.                                                                         togglePost(, "hide");
  1413.                                                                 } else { // The linked post has been filtered with mode remove
  1414.                                                                         $currentPost.hide();
  1415.                                                                 }
  1416.                                                         }
  1417.                                                 } else { // Linked post isn't present in thread
  1418.                                                         $.ajax({
  1419.                                                                 url: "/_/api/chan/post/",
  1420.                                                                 data: {"board": backlink.dataset.board, "num": },
  1421.                                                                 type: "GET"
  1422.                                                         }).done(function(response){
  1423.                                                                 processPosts(checkFilter(response, false), $currentPost, currentPost);
  1424.                                                                 $currentPost.find('.backlink_list .backlink').each(function(j, replyBacklink){ // Filter replies
  1425.                                                                         filter($('#' + ));
  1426.                                                                 });
  1427.                                                         });
  1428.                                                 }
  1429.                                         }
  1430.                                 });
  1431.                         }
  1432.                         if ($(currentPost).length) { // If after all that the post hasn't been purged
  1433.                                 processPosts(checkFilter(currentPost, true), $currentPost, currentPost);
  1434.                         }
  1435.                 }
  1436.         });
  1437. }
  1438.  
  1439. function processPosts(type, $currentPost, currentPost){
  1440.         switch (type) {
  1441.                 case 1:
  1442.                         $currentPost.addClass('shitpost');
  1443.                         break;
  1444.                 case 2:
  1445.                         togglePost(, 'hide');
  1446.                         break;
  1447.                 case 3:
  1448.                         $currentPost.hide();
  1449.                         break;
  1450.                 case 4:
  1451.                         $currentPost.prev().remove();
  1452.                         $currentPost.remove();
  1453.         }
  1454. }
  1455.  
  1456. function checkFilter(input, inThreadPost){
  1457.         var output = 0;
  1458.         for (var filterType in settings.FilterSettings) {
  1459.                 if (settings.FilterSettings.hasOwnProperty(filterType)) {
  1460.                         var testText = inThreadPost ? settings.FilterSettings[filterType].threadPostFunction(input) : settings.FilterSettings[filterType].responseObjFunction(input);
  1461.                         var shortcut = settings.FilterSettings[filterType].value;
  1462.                         for (var line in shortcut) {
  1463.                                 if (shortcut.hasOwnProperty(line) && !shortcut[line].comment && shortcut[line].regex !== undefined) {
  1464.                                         if (shortcut[line].boards === undefined || boardPatt.test(shortcut[line].boards)) {
  1465.                                                 var regex = new RegExp(shortcut[line].regex.pattern, shortcut[line].regex.flag);
  1466.                                                 if (regex.test(testText)) {
  1467.                                                         switch (shortcut[line].mode) {
  1468.                                                                 case "fade":
  1469.                                                                         if (output < 1) {
  1470.                                                                                 output = 1;
  1471.                                                                         }
  1472.                                                                         break;
  1473.                                                                 case "hide":
  1474.                                                                         if (output < 2) {
  1475.                                                                                 output = 2;
  1476.                                                                         }
  1477.                                                                         break;
  1478.                                                                 case "remove":
  1479.                                                                         output = 3;
  1480.                                                                         break;
  1481.                                                                 case "purge":
  1482.                                                                         return 4;
  1483.                                                                 default:
  1484.                                                                         if (output < 1) {
  1485.                                                                                 output = 1;
  1486.                                                                         }
  1487.                                                         }
  1488.                                                 }
  1489.                                         }
  1490.                                 }
  1491.                         }
  1492.                 }
  1493.         }
  1494.         return output;
  1495. }
  1496.  
  1497. var embedImages = function(posts){
  1498.         posts.each(function(index, currentArticle){
  1499.                 var $currentArticle = $(currentArticle);
  1500.                 if (!$currentArticle.data('imgEmbed')) {
  1501.                         $currentArticle.data('imgEmbed', true);
  1502.                         var imgNum = settings.UserSettings.embedImages.suboptions.imgNumMaster.value - $currentArticle.find('.thread_image_box').length;
  1503.                         var isOP = $currentArticle.hasClass('thread');
  1504.                         (isOP ? $currentArticle.children('.text').find('a') : $currentArticle.find('.text a')).each(function(index, currentLink){
  1505.                                 if (imgNum === 0) {
  1506.                                         return false;
  1507.                                 }
  1508.                                 var mediaType = 'notMedia';
  1509.                                 var mediaLink = currentLink.href;
  1510.                                 if (pattImageFiletypes.test(mediaLink)) {
  1511.                                         mediaType = 'image';
  1512.                                 } else if (pattVideoFiletypes.test(mediaLink)) {
  1513.                                         mediaType = 'video';
  1514.                                 } else if (pattYoutubeLink.test(mediaLink)) {
  1515.                                         mediaType = 'youtube';
  1516.                                 }
  1517.                                 if (mediaType == 'image' || mediaType == 'video') {
  1518.                                         imgNum--;
  1519.                                         var filename = '<div class="post_file embedded_post_file"><a href="' + mediaLink + '" class="post_file_filename" rel="tooltip" title="' + mediaLink + '">' + mediaLink.match(/[^\/]*/g)[mediaLink.match(/[^\/]*/g).length - 2] + '</a></div>';
  1520.                                         var spoiler = '';
  1521.                                         var $elem = $('<div class="thread_image_box">' + filename + '</div>').insertBefore((isOP ? $currentArticle.children('header') : $currentArticle.find('header')));
  1522.                                         if ($(this).parents('.spoiler').length) {
  1523.                                                 spoiler = "spoilerImage ";
  1524.                                                 $elem.append('<div class="spoilerText">Spoiler</div>');
  1525.                                         }
  1526.                                         if (mediaType === 'image') {
  1527.                                                 $elem.append('<a href="' + mediaLink + '" target="_blank" rel="noreferrer" class="thread_image_link"><img src="' + mediaLink + '" class="lazyload post_image ' + spoiler + 'smallImage"></a>');
  1528.                                                 removeLink(currentLink);
  1529.                                                 var $image = $elem.find('img');
  1530.                                                 if (!$image.attr('loadEventSet')) {
  1531.                                                         $image.attr('loadEventSet', true);
  1532.                                                         $image.on('load', function(e){
  1533.                                                                 $image.unbind('load');
  1534.                                                                 $(e.target).closest('.thread_image_box').find(".spoilerText").css({"top": (e.target.height / 2) - 6.5}); // Center spoiler text
  1535.                                                                 $(e.target).closest('.thread_image_box').append('<br><span class="post_file_metadata">' + e.target.naturalWidth + 'x' + e.target.naturalHeight + '</span>'); // Add file dimensions
  1536.                                                         });
  1537.                                                 }
  1538.                                         } else if (mediaType === 'video') {
  1539.                                                 if (settings.UserSettings.embedImages.suboptions.embedVideos.value) {
  1540.                                                         mediaLink = mediaLink.replace(/\.gifv$/g, ".webm"); // Only tested to work with Imgur
  1541.                                                         $elem.append('<video width="' + imageWidth + '" style="float:left" name="media" loop muted ' + autoplayVid + ' class="' + spoiler + '"><source src="' + mediaLink + '" type="video/webm"></video>');
  1542.                                                         removeLink(currentLink);
  1543.                                                         $elem.find('video')[0].onloadedmetadata = function(e){
  1544.                                                                 $(e.target).closest('.thread_image_box').find(".spoilerText").css({"top": (e.target.clientHeight / 2) - 6.5}); // Center spoiler text
  1545.                                                                 $(e.target).closest('.thread_image_box').append('<br><span class="post_file_metadata">' + e.target.videoWidth + 'x' + e.target.videoHeight + '</span>'); // Add file dimensions
  1546.                                                         };
  1547.                                                 }
  1548.                                         }
  1549.                                         addHover($elem);
  1550.                                 } else if (mediaType === 'youtube') {
  1551.                                         if (settings.UserSettings.embedImages.suboptions.titleYoutubeLinks.value) {
  1552.                                                 var vidID = /[A-z0-9_-]{11}/.exec(mediaLink);
  1553.                                                 if (vidID) {
  1554.                                                         $.ajax({
  1555.                                                                 method: 'GET',
  1556.                                                                 url: ',
  1557.                                                                 data: {
  1558.                                                                         'part': 'snippet',
  1559.                                                                         'id': vidID[0],
  1560.                                                                         'fields': 'items(id,snippet(title))',
  1561.                                                                         'key': 'AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE'
  1562.                                                                 }
  1563.                                                         }).done(function(response){
  1564.                                                                 if (response.items.length) {
  1565.                                                                         currentLink.innerHTML = '<i>(YouTube)</i> - ' + response.items[0].snippet.title;
  1566.                                                                 }
  1567.                                                         });
  1568.                                                 }
  1569.                                         }
  1570.                                 } else if (settings.UserSettings.embedGalleries.value && pattImgGal.exec(currentLink.href) !== null) {
  1571.                                         var imgurLinkFragments = currentLink.href.split('\/');
  1572.                                         if (imgurLinkFragments[3] == "") {
  1573.                                                 // Do nothing with dodgy link
  1574.                                         } else if (imgurLinkFragments[3] == "r") {
  1575.                                                 // TODO: add in an actual way of handling this
  1576.                                         } else if (imgurLinkFragments[3] == "a") {
  1577.                                                 imgurLinkFragments[4] = imgurLinkFragments[4].replace(/#[0-9]+/, ''); // Remove the trailing image number
  1578.                                                 if (settings.UserSettings.embedGalleries.suboptions.showDetails.value) {
  1579.                                                         $currentArticle.find(".post_wrapper").prepend('<blockquote class="imgur-embed-pub" lang="en" data-id="a/' + imgurLinkFragments[4] + '"><a href=" + imgurLinkFragments[4] + '"></a></blockquote><script async src="" charset="utf-8"></script>');
  1580.                                                 } else {
  1581.                                                         $currentArticle.find(".post_wrapper").prepend('<blockquote class="imgur-embed-pub" lang="en" data-id="a/' + imgurLinkFragments[4] + '" data-context="false"><a href=" + imgurLinkFragments[4] + '"></a></blockquote><script async src="" charset="utf-8"></script>');
  1582.                                                 }
  1583.                                                 removeLink(currentLink);
  1584.                                         } else if (imgurLinkFragments[3] !== "gallery" && !isOP) { // I can't be bothered making this work properly for OPs, so let's just ignore it and pretend it doesn't exist, yes?
  1585.                                                 var link = pattImgGal.exec(currentLink.href);
  1586.                                                 var individualImages = link[0].match(/[A-z0-9]{7}/g);
  1587.                                                 var allDisplayed = true; // Track whether all of the linked images get inserted so that if not the link can be kept
  1588.                                                 $.each(individualImages, function(i, imgID){
  1589.                                                         if (imgNum) {
  1590.                                                                 imgNum--;
  1591.                                                                 var filename = '<div class="post_file embedded_post_file"><a href=" + imgID + '.jpg" class="post_file_filename" rel="tooltip" title=" + imgID + '.jpg">' + imgID + '.jpg</a></div>';
  1592.                                                                 var imgHTML = '<div class="thread_image_box">' + filename + '<a href=" + imgID + '.jpg" target="_blank" rel="noreferrer" class="thread_image_link"><img src=" + imgID + '.jpg" class="lazyload post_image smallImage"></a></div>';
  1593.                                                                 if (i) {
  1594.                                                                         $($currentArticle.find(".thread_image_box")[i - 1]).after(imgHTML);
  1595.                                                                 } else {
  1596.                                                                         $currentArticle.find(".post_wrapper").prepend(imgHTML);
  1597.                                                                 }
  1598.                                                         } else {
  1599.                                                                 allDisplayed = false;
  1600.                                                                 return false;
  1601.                                                         }
  1602.                                                 });
  1603.                                                 $currentArticle.find(".post_wrapper").prepend('<style>.thread_image_box{min-height:' + imageHeight + 'px;}.post_wrapper header{min-width:400px;}</style>');
  1604.                                                 $currentArticle.find('.thread_image_box img').each(function(i, image){
  1605.                                                         var $image = $(image);
  1606.                                                         if (!$image.attr('loadEventSet')) {
  1607.                                                                 $image.attr('loadEventSet', true);
  1608.                                                                 $image.on("load", function(e){
  1609.                                                                         $image.unbind('load');
  1610.                                                                         $(e.target).closest('.thread_image_box').find(".spoilerText").css({"top": (e.target.height / 2) - 6.5}); // Center spoiler text
  1611.                                                                         $(e.target).closest('.thread_image_box').append('<br><span class="post_file_metadata">' + e.target.naturalWidth + 'x' + e.target.naturalHeight + '</span>'); // Add file dimensions
  1612.                                                                 });
  1613.                                                         }
  1614.                                                 });
  1615.                                                 if (allDisplayed) {
  1616.                                                         removeLink(currentLink);
  1617.                                                 }
  1618.                                                 addHover($currentArticle);
  1619.                                         }
  1620.                                 } else {
  1621.                                         if (!(/&gt;&gt;/).test(mediaLink)) {
  1622.                                                 ;
  1623.                                         }
  1624.                                 }
  1625.                         });
  1626.                 }
  1627.         });
  1628. };
  1629.  
  1630. function removeLink(currentLink){
  1631.         if ($(currentLink)[0].nextSibling !== null) {
  1632.                 if ($(currentLink)[0].nextSibling.nodeName == "BR") {
  1633.                         if ($(currentLink)[0].previousSibling === null || $(currentLink)[0].previousSibling.nodeName !== "#text" || $(currentLink)[0].previousSibling.nodeValue == " ") {
  1634.                                 $(currentLink).next().remove(); // Remove linebreaks
  1635.                         }
  1636.                 }
  1637.         }
  1638.         $(currentLink).remove();
  1639. }
  1640.  
  1641. function pauseGifs(posts){
  1642.         posts.each(function(i, img){
  1643.                 if ((/\.gif/).test(img.src)) {
  1644.                         var $img = $(img);
  1645.                         $img.on('load', function(){
  1646.                                 $img.after('<canvas class="smallImage" width="' + img.naturalWidth + '" height="' + img.naturalHeight + '"></canvas>');
  1647.                                 $img.attr('gif', true).toggle();
  1648.                                 $img.addClass("bigImage").removeClass("smallImage");
  1649.                                 var canvas = $img.next('canvas');
  1650.                                 canvas[0].getContext("2d").drawImage(img, 0, 0);
  1651.                                 addHover($img.parent());
  1652.                         });
  1653.                 }
  1654.         });
  1655. }
  1656.  
  1657. function addHover($elements){
  1658.         if (settings.UserSettings.inlineImages.value) {
  1659.                 if (settings.UserSettings.inlineImages.suboptions.imageHover.value) {
  1660.                         var $image = $elements.find('img');
  1661.                         var $canvas = $elements.find('canvas');
  1662.                         if ($image.length) {
  1663.                                 imageHover($image);
  1664.                         }
  1665.                         if ($canvas.length) {
  1666.                                 canvasHover($canvas);
  1667.                         }
  1668.                 }
  1669.                 if (settings.UserSettings.inlineImages.suboptions.videoHover.value) {
  1670.                         var $video = $elements.find('video');
  1671.                         if ($video.length) {
  1672.                                 videoHover($video);
  1673.                         }
  1674.                 }
  1675.         }
  1676. }
  1677.  
  1678. function imageHover($image){
  1679.         $image.on('mouseenter', function(e){
  1680.                 if ( !== 'mascot' && !$(e.target).hasClass('bigImage') && !$(e.target).data('dontHover')) {
  1681.                         $(e.target).clone().removeClass('smallImage spoilerImage').addClass('hoverImage').appendTo('#hoverUI');
  1682.                 }
  1683.         });
  1684.         $image.on('mousemove mouseenter', function(e){
  1685.                 var etarget = e.target;
  1686.                 var $etarget = $(etarget);
  1687.                 if (!$etarget.hasClass('bigImage') && !$etarget.data('dontHover')) {
  1688.                         var headerBarHeight = document.getElementById('headerFixed').offsetHeight - 1; // -1 due to slight offscreen to hide border-top
  1689.                         var headerBarWidth = document.getElementById('headerFixed').offsetWidth - 1; // -1 due to slight offscreen to hide border-right
  1690.                         var windowWidth = $('body').innerWidth(); // Define internal dimensions
  1691.                         var windowHeight = window.innerHeight - 21; // -21 so link destination doesn't overlay image
  1692.                         var visibleHeight = windowHeight - headerBarHeight;
  1693.                         var visibleWidth = windowWidth - e.clientX - 50;
  1694.                         var canFitFullHeight = windowHeight * etarget.naturalWidth / etarget.naturalHeight < visibleWidth - headerBarWidth + 1;
  1695.                         var $hoverUI = $('#hoverUI');
  1696.                         var $img = $hoverUI.children('img');
  1697.                         $img.css({
  1698.                                 'max-height': canFitFullHeight ? windowHeight : visibleHeight,
  1699.                                 'max-width': visibleWidth,
  1700.                                 'top': canFitFullHeight ? (windowHeight - $img[0].height) * (e.clientY / windowHeight) : (visibleHeight - $img[0].height) * (e.clientY / visibleHeight) + headerBarHeight,
  1701.                                 'left': e.clientX + 50
  1702.                         });
  1703.                 }
  1704.         });
  1705.         $image.on('mouseout', function(){
  1706.                 $('#hoverUI').html('');
  1707.         });
  1708. }
  1709.  
  1710. function canvasHover($canvas){
  1711.         $canvas.on('mouseenter', function(e){
  1712.                 if ( !== 'myCanvas') {
  1713.                         $(e.target.previousSibling).clone().show().removeClass('spoilerImage').addClass('hoverImage').appendTo('#hoverUI');
  1714.                 }
  1715.         });
  1716.         $canvas.on('mousemove mouseenter', function(e){
  1717.                 var etarget = e.target;
  1718.                 var $etarget = $(etarget);
  1719.                 if (!$etarget.hasClass('bigImage') && !$etarget.data('dontHover')) {
  1720.                         var headerBarHeight = document.getElementById('headerFixed').offsetHeight - 1; // -1 due to slight offscreen to hide border-top
  1721.                         var headerBarWidth = document.getElementById('headerFixed').offsetWidth - 1; // -1 due to slight offscreen to hide border-right
  1722.                         var windowWidth = $('body').innerWidth(); // Define internal dimensions
  1723.                         var windowHeight = window.innerHeight - 21; // -21 so link destination doesn't overlay image
  1724.                         var visibleHeight = windowHeight - headerBarHeight;
  1725.                         var visibleWidth = windowWidth - e.clientX - 50;
  1726.                         var canFitFullHeight = windowHeight * etarget.naturalWidth / etarget.naturalHeight < visibleWidth - headerBarWidth + 1;
  1727.                         var $hoverUI = $('#hoverUI');
  1728.                         var $img = $hoverUI.children('img');
  1729.                         $img.css({
  1730.                                 'max-height': canFitFullHeight ? windowHeight : visibleHeight,
  1731.                                 'max-width': visibleWidth,
  1732.                                 'top': canFitFullHeight ? (windowHeight - $img[0].height) * (e.clientY / windowHeight) : (visibleHeight - $img[0].height) * (e.clientY / visibleHeight) + headerBarHeight,
  1733.                                 'left': e.clientX + 50
  1734.                         });
  1735.                 }
  1736.         });
  1737.         $canvas.on('mouseout', function(){
  1738.                 $('#hoverUI').html('');
  1739.         });
  1740. }
  1741.  
  1742. function videoHover($video){
  1743.         $video.on('mouseenter', function(e){
  1744.                 if ( !== 'mascot' && !$(e.target).hasClass('fullVideo')) {
  1745.                         $(e.target).clone().removeClass('spoilerImage').addClass('fullVideo hoverImage').appendTo('#hoverUI');
  1746.                         var $hoverUI = $('#hoverUI');
  1747.                         var $vid = $hoverUI.children('video');
  1748.                         $vid.removeAttr('width');
  1749.                         $vid.on('canplaythrough', function(){
  1750.                                 if ($vid.length) { // Check if video still exists. This is to prevent the problem where mousing out too soon still triggers the canplay event
  1751.                                         $vid[0].muted = false;
  1752.                                         $vid[0].play();
  1753.                                         $video.on('mousemove', function(e){
  1754.                                                 var headerBarHeight = document.getElementById('headerFixed').offsetHeight - 1; // -1 due to slight offscreen to hide border-top
  1755.                                                 var headerBarWidth = document.getElementById('headerFixed').offsetWidth - 1; // -1 due to slight offscreen to hide border-right
  1756.                                                 var windowWidth = $('body').innerWidth(); // Define internal dimensions
  1757.                                                 var windowHeight = window.innerHeight;
  1758.                                                 var visibleHeight = windowHeight - headerBarHeight;
  1759.                                                 var visibleWidth = windowWidth - e.clientX - 50;
  1760.                                                 var canFitFullHeight = windowHeight * e.target.videoWidth / e.target.videoHeight < visibleWidth - headerBarWidth + 1;
  1761.                                                 $vid.css({
  1762.                                                         'max-height': canFitFullHeight ? windowHeight : visibleHeight,
  1763.                                                         'max-width': visibleWidth,
  1764.                                                         'top': canFitFullHeight ? (windowHeight - $vid[0].clientHeight) * (e.clientY / windowHeight) : (visibleHeight - $vid[0].clientHeight) * (e.clientY / visibleHeight) + headerBarHeight,
  1765.                                                         'left': e.clientX + 50
  1766.                                                 });
  1767.                                         });
  1768.                                 }
  1769.                         });
  1770.                 }
  1771.         });
  1772.         $video.on('mouseout', function(){
  1773.                 $('#hoverUI').html('');
  1774.         });
  1775. }
  1776.  
  1777. function relativeTimestamps(posts){
  1778.         posts.find('time').each(function(index, timeElement){
  1779.                 if (!$(timeElement).data('relativeTime')) {
  1780.                         $(timeElement).data('relativeTime', true);
  1781.                         changeTimestamp(timeElement, Date.parse($(timeElement).attr('datetime')));
  1782.                 }
  1783.         });
  1784. }
  1785.  
  1786. function convertMS(ms){
  1787.         var d, h, m, s;
  1788.         s = Math.floor(ms / 1000);
  1789.         m = Math.floor(s / 60);
  1790.         s = s % 60;
  1791.         h = Math.floor(m / 60);
  1792.         m = m % 60;
  1793.         d = Math.floor(h / 24);
  1794.         h = h % 24;
  1795.         y = Math.floor(d / 365.25);
  1796.         d = Math.floor(d % 365.25);
  1797.         return {y: y, d: d, h: h, m: m, s: s};
  1798. }
  1799.  
  1800. function changeTimestamp(timeElement, postTimestamp){
  1801.         var currentTimestamp = Date.now();
  1802.         var diffMS = currentTimestamp - postTimestamp;
  1803.         if (diffMS < 0) {
  1804.                 diffMS = 0;
  1805.         } // Handle the issue where mismatched local and server time could end up with negative difference
  1806.         var diff = convertMS(diffMS);
  1807.         var years = diff.y === 1 ? 'year' : 'years';
  1808.         var days = diff.d === 1 ? 'day' : 'days';
  1809.         var hours = diff.h === 1 ? 'hour' : 'hours';
  1810.         var minutes = diff.m === 1 ? 'minute' : 'minutes';
  1811.         var seconds = diff.s === 1 ? 'second' : 'seconds';
  1812.         if (diff.y) {
  1813.                 $(timeElement).html(diff.y + ' ' + years + ' and ' + diff.d + ' ' + days + ' ago');
  1814.                 setTimeout(function(){
  1815.                         changeTimestamp(timeElement, postTimestamp);
  1816.                 }, 365.25 * 24 * 60 * 60 * 1000);
  1817.         } else if (diff.d) {
  1818.                 if (diff.d >= 2) {
  1819.                         $(timeElement).html(diff.d + ' ' + days + ' ago');
  1820.                         setTimeout(function(){
  1821.                                 changeTimestamp(timeElement, postTimestamp);
  1822.                         }, 24 * 60 * 60 * 1000);
  1823.                 } else {
  1824.                         $(timeElement).html(diff.d + ' ' + days + ' and ' + diff.h + ' ' + hours + ' ago');
  1825.                         setTimeout(function(){
  1826.                                 changeTimestamp(timeElement, postTimestamp);
  1827.                         }, 60 * 60 * 1000);
  1828.                 }
  1829.         } else if (diff.h) {
  1830.                 if (diff.h >= 2) {
  1831.                         $(timeElement).html(diff.h + ' ' + hours + ' ago');
  1832.                         setTimeout(function(){
  1833.                                 changeTimestamp(timeElement, postTimestamp);
  1834.                         }, (60 - diff.m) * 60 * 1000);
  1835.                 } else {
  1836.                         $(timeElement).html(diff.h + ' ' + hours + ' and ' + diff.m + ' ' + minutes + ' ago');
  1837.                         setTimeout(function(){
  1838.                                 changeTimestamp(timeElement, postTimestamp);
  1839.                         }, 10 * 60 * 1000);
  1840.                 }
  1841.         } else if (diff.m) {
  1842.                 if (diff.m >= 10) {
  1843.                         $(timeElement).html(diff.m + ' ' + minutes + ' ago');
  1844.                         setTimeout(function(){
  1845.                                 changeTimestamp(timeElement, postTimestamp);
  1846.                         }, 5 * 60 * 1000);
  1847.                 } else {
  1848.                         $(timeElement).html(diff.m + ' ' + minutes + ' and ' + diff.s + ' ' + seconds + ' ago');
  1849.                         setTimeout(function(){
  1850.                                 changeTimestamp(timeElement, postTimestamp);
  1851.                         }, 60 * 1000);
  1852.                 }
  1853.         } else {
  1854.                 if (diff.s >= 20) {
  1855.                         $(timeElement).html(diff.s + ' ' + seconds + ' ago');
  1856.                         setTimeout(function(){
  1857.                                 changeTimestamp(timeElement, postTimestamp);
  1858.                         }, 20 * 1000);
  1859.                 } else {
  1860.                         $(timeElement).html(diff.s + ' ' + seconds + ' ago');
  1861.                         setTimeout(function(){
  1862.                                 changeTimestamp(timeElement, postTimestamp);
  1863.                         }, 9 * 1000);
  1864.                 }
  1865.         }
  1866. }
  1867.  
  1868. var lastSeenPost = threadID;
  1869. var unseenPosts = [];
  1870. function seenPosts(){
  1871.         var $backlinkContainer = $('article.backlink_container');
  1872.         $backlinkContainer.attr('id', "0"); // Prevent error when it's undefined
  1873.         var parsedLastSeenPost = [parseInt(lastSeenPost.split('_')[0]), parseInt(lastSeenPost.split('_')[1])];
  1874.         $('article').each(function(index, currentArticle){ // Add unseen posts to array
  1875.                 var currentID = [parseInt(.split('_')[0]), parseInt(.split('_')[1])];
  1876.                 if (currentID[0] > parsedLastSeenPost[0]) {
  1877.                         unseenPosts.push();
  1878.                 } else if (currentID[0] == parsedLastSeenPost[0]) {
  1879.                         if (isNaN(currentID[1])) {
  1880.                                 // Do nothing
  1881.                         } else if (isNaN(parsedLastSeenPost[1]) || currentID[1] > parsedLastSeenPost[1]) {
  1882.                                 unseenPosts.push();
  1883.                         } else {
  1884.                                 ;
  1885.                         }
  1886.                 }
  1887.         });
  1888.         $backlinkContainer.removeAttr('id'); // Remove id again
  1889.         $('#' + unseenPosts[0]).addClass("unseenPost");
  1890. }
  1891.  
  1892. var unseenReplies = [];
  1893. function newPosts(){
  1894.         if (settings.UserSettings.favicon.value) {
  1895.                 if (unseenPosts.length) {
  1896.                         var predictedLastSeenPostIndex = -1;
  1897.                         if (windowFocus) {
  1898.                                 var viewportBottom = window.scrollY + window.innerHeight;
  1899.                                 var unseenPostOffset = document.getElementById(unseenPosts[0]).offsetTop + document.getElementById(unseenPosts[0]).offsetHeight;
  1900.                                 if (unseenPostOffset < viewportBottom) {
  1901.                                         var meanPostHeight = (document.getElementById(unseenPosts[unseenPosts.length - 1]).offsetTop - document.getElementById(unseenPosts[0]).offsetTop) / unseenPosts.length;
  1902.                                         var testedSeeds = []; // Keep track of which indices have been checked to prevent endless loops
  1903.                                         var lastSeed = 0;
  1904.                                         predictedLastSeenPostIndex = 0;
  1905.                                         var predictedLSP0offset = document.getElementById(unseenPosts[predictedLastSeenPostIndex]).offsetTop + document.getElementById(unseenPosts[predictedLastSeenPostIndex]).offsetHeight;
  1906.                                         var predictedLSP1offset;
  1907.                                         if (unseenPosts[predictedLastSeenPostIndex + 1] !== undefined) {
  1908.                                                 predictedLSP1offset = document.getElementById(unseenPosts[predictedLastSeenPostIndex + 1]).offsetTop + document.getElementById(unseenPosts[predictedLastSeenPostIndex + 1]).offsetHeight;
  1909.                                         }
  1910.                                         while (!(predictedLSP0offset <= viewportBottom && (unseenPosts[predictedLastSeenPostIndex + 1] === undefined || predictedLSP1offset > viewportBottom))) {
  1911.                                                 testedSeeds[predictedLastSeenPostIndex] = true;
  1912.                                                 predictedLastSeenPostIndex += Math.floor((viewportBottom - document.getElementById(unseenPosts[predictedLastSeenPostIndex]).offsetTop) / meanPostHeight);
  1913.                                                 if (predictedLastSeenPostIndex >= unseenPosts.length) {
  1914.                                                         predictedLastSeenPostIndex = unseenPosts.length - 1; // Keep it from exceeding the array bounds
  1915.                                                 } else if (predictedLastSeenPostIndex < 0) {
  1916.                                                         predictedLastSeenPostIndex = 0; // Keep it from being negative
  1917.                                                 }
  1918.                                                 if (testedSeeds[predictedLastSeenPostIndex]) { // Prevent it from getting caught in infinite loops by making sure it never uses the same predictedLSPi twice
  1919.                                                         for (lastSeed; lastSeed < unseenPosts.length; lastSeed++) {
  1920.                                                                 if (!testedSeeds[lastSeed]) {
  1921.                                                                         predictedLastSeenPostIndex = lastSeed;
  1922.                                                                         break;
  1923.                                                                 }
  1924.                                                         }
  1925.                                                 }
  1926.                                                 predictedLSP0offset = document.getElementById(unseenPosts[predictedLastSeenPostIndex]).offsetTop + document.getElementById(unseenPosts[predictedLastSeenPostIndex]).offsetHeight;
  1927.                                                 if (unseenPosts[predictedLastSeenPostIndex + 1] !== undefined) {
  1928.                                                         predictedLSP1offset = document.getElementById(unseenPosts[predictedLastSeenPostIndex + 1]).offsetTop + document.getElementById(unseenPosts[predictedLastSeenPostIndex + 1]).offsetHeight;
  1929.                                                 }
  1930.                                         }
  1931.                                 }
  1932.                         }
  1933.                         for (var i = predictedLastSeenPostIndex + 1, len = unseenPosts.length; i < len; i++) {
  1934.                                 if (yourPostsLookup[board][unseenPosts[i]]) {
  1935.                                         predictedLastSeenPostIndex = i; // Consider any posts following the last seen post that are yours as seen
  1936.                                 } else {
  1937.                                         break;
  1938.                                 }
  1939.                         }
  1940.                         if (predictedLastSeenPostIndex >= 0) {
  1941.                                 lastSeenPost = unseenPosts[predictedLastSeenPostIndex]; // Update last seen post
  1942.                                 saveLastSeenPosts(); // Save new last seen post
  1943.                                 unseenPosts = unseenPosts.slice(predictedLastSeenPostIndex + 1); // Only keep posts after the lastSeenPost
  1944.  
  1945.                                 var parsedLastSeenPost = [parseInt(lastSeenPost.split('_')[0]), parseInt(lastSeenPost.split('_')[1])];
  1946.                                 var unseenRepliesTemp = []; // Avoid trying to remove entries from an array that is being iterated over
  1947.                                 $.each(unseenReplies, function(i, unseenID){ // Work from a copy of the array so that removing elements from it doesn't ruin everything
  1948.                                         var currentID = [parseInt(unseenID.split('_')[0]), parseInt(unseenID.split('_')[1])];
  1949.                                         if (currentID[0] < parsedLastSeenPost[0]) {
  1950.                                                 return true;
  1951.                                         } else if (currentID[0] === parsedLastSeenPost[0]) {
  1952.                                                 if (isNaN(currentID[1]) || (!isNaN(parsedLastSeenPost[1]) && currentID[1] <= parsedLastSeenPost[1])) {
  1953.                                                         return true;
  1954.                                                 }
  1955.                                         }
  1956.                                         unseenRepliesTemp.push(unseenID); // Keep unseenIDs that are greater than the lastSeenPostID
  1957.                                 });
  1958.                                 unseenReplies = unseenRepliesTemp;
  1959.                         }
  1960.                 }
  1961.                 if (!windowFocus) {
  1962.                         $('.unseenPost').removeClass('unseenPost'); // Remove the previous unseen post line
  1963.                         $('#' + unseenPosts[0]).addClass('unseenPost'); // Add the unseen class to the first of the unseen posts
  1964.                 }
  1965.                 newPostCount = unseenPosts.length;
  1966.                 var $favicon = $('#favicon');
  1967.                 var faviconHref = $favicon.attr('href');
  1968.                 if (unseenReplies.length) {
  1969.                         faviconState = 'alert';
  1970.                         if (faviconHref !== faviconAlert) {
  1971.                                 $favicon.attr('href', faviconAlert);
  1972.                         }
  1973.                 } else if (newPostCount > 0) {
  1974.                         faviconState = 'lit';
  1975.                         if (faviconHref !== faviconLit) {
  1976.                                 $favicon.attr('href', faviconLit);
  1977.                         }
  1978.                 } else {
  1979.                         faviconState = 'unlit';
  1980.                         if (faviconHref !== faviconUnlit) {
  1981.                                 $favicon.attr('href', faviconUnlit);
  1982.                         }
  1983.                 }
  1984.         } else { // Original newpost counter code
  1985.                 $('article').each(function(index, currentArticle){
  1986.                         if (!$(currentArticle).data('seen')) {
  1987.                                 $(currentArticle).data('seen', true);
  1988.                                 newPostCount += 1;
  1989.                         }
  1990.                 });
  1991.                 if (windowFocus === true) {
  1992.                         newPostCount = 0;
  1993.                 }
  1994.         }
  1995.         document.title = "(" + newPostCount + ") " + DocumentTitle;
  1996. }
  1997.  
  1998. function postCounter(){
  1999.         if (!('other,statistics')) {
  2000.                 var postCount = notLoadedPostCount + $('.post_wrapper, div.thread').length;
  2001.                 var hiddenPostCount = $('.stub').length - $('.pull-left').children('.btn-toggle-post').filter(':visible').length; // Count total minus those that aren't visible to take account for hiding a whole thread on a board
  2002.                 var imageCount = $('.thread_image_box').length;
  2003.                 var limits = settings.UserSettings.postCounter.suboptions.limits.value;
  2004.                 var countHidden = settings.UserSettings.postCounter.suboptions.countHidden.value;
  2005.                 if (countHidden && settings.UserSettings.postCounter.suboptions.countHidden.suboptions.hideNullHiddenCounter.value) {
  2006.                         countHidden = hiddenPostCount;
  2007.                 }
  2008.                 var locationHeader = settings.UserSettings.postCounter.suboptions.location.value.value === "Header bar";
  2009.                 var $rules_box = $(".rules_box");
  2010.                 var $threadStats = $('.threadStats');
  2011.                 if (locationHeader) {
  2012.                         $rules_box.html(rulesBox);
  2013.                 } else {
  2014.                         $threadStats.html('');
  2015.                 }
  2016.                 (locationHeader ? $threadStats : $rules_box).html(
  2017.                         '<' + (locationHeader ? 'span' : 'h6') + '>Posts' +
  2018.                         (countHidden ? '(Hidden)' : '') + ': ' + postCount +
  2019.                         (countHidden ? '(' + hiddenPostCount + ')' : '') +
  2020.                         (limits ? '/' + settings.UserSettings.postCounter.suboptions.limits.suboptions.posts.value : '') +
  2021.                         (locationHeader ? '' : '<br>') + ' Images: ' + imageCount +
  2022.                         (limits ? '/' + settings.UserSettings.postCounter.suboptions.limits.suboptions.images.value : '') +
  2023.                         '</' + (locationHeader ? 'span' : 'h6') + '>' +
  2024.                         (locationHeader ? '' : rulesBox)
  2025.                 );
  2026.         }
  2027. }
  2028.  
  2029. var pokemon = ["bulbasaur", "ivysaur", "venusaur", "charmander", "charmeleon", "charizard", "squirtle", "wartortle", "blastoise", "caterpie", "metapod", "butterfree", "weedle", "kakuna", "beedrill", "pidgey", "pidgeotto", "pidgeot", "rattata", "raticate", "spearow", "fearow", "ekans", "arbok", "pikachu", "raichu", "sandshrew", "sandslash", "nidoran?", "nidorina", "nidoqueen", "nidoran?", "nidorino", "nidoking", "clefairy", "clefable", "vulpix", "ninetales", "jigglypuff", "wigglytuff", "zubat", "golbat", "oddish", "gloom", "vileplume", "paras", "parasect", "venonat", "venomoth", "diglett", "dugtrio", "meowth", "persian", "psyduck", "golduck", "mankey", "primeape", "growlithe", "arcanine", "poliwag", "poliwhirl", "poliwrath", "abra", "kadabra", "alakazam", "machop", "machoke", "machamp", "bellsprout", "weepinbell", "victreebel", "tentacool", "tentacruel", "geodude", "graveler", "golem", "ponyta", "rapidash", "slowpoke", "slowbro", "magnemite", "magneton", "farfetch'd", "doduo", "dodrio", "seel", "dewgong", "grimer", "muk", "shellder", "cloyster", "gastly", "haunter", "gengar", "onix", "drowzee", "hypno", "krabby", "kingler", "voltorb", "electrode", "exeggcute", "exeggutor", "cubone", "marowak", "hitmonlee", "hitmonchan", "lickitung", "koffing", "weezing", "rhyhorn", "rhydon", "chansey", "tangela", "kangaskhan", "horsea", "seadra", "goldeen", "seaking", "staryu", "starmie", "mr. mime", "scyther", "jynx", "electabuzz", "magmar", "pinsir", "tauros", "magikarp", "gyarados", "lapras", "ditto", "eevee", "vaporeon", "jolteon", "flareon", "porygon", "omanyte", "omastar", "kabuto", "kabutops", "aerodactyl", "snorlax", "articuno", "zapdos", "moltres", "dratini", "dragonair", "dragonite", "mewtwo", "mew", "chikorita", "bayleef", "meganium", "cyndaquil", "quilava", "typhlosion", "totodile", "croconaw", "feraligatr", "sentret", "furret", "hoothoot", "noctowl", "ledyba", "ledian", "spinarak", "ariados", "crobat", "chinchou", "lanturn", "pichu", "cleffa", "igglybuff", "togepi", "togetic", "natu", "xatu", "mareep", "flaaffy", "ampharos", "bellossom", "marill", "azumarill", "sudowoodo", "politoed", "hoppip", "skiploom", "jumpluff", "aipom", "sunkern", "sunflora", "yanma", "wooper", "quagsire", "espeon", "umbreon", "murkrow", "slowking", "misdreavus", "unown", "wobbuffet", "girafarig", "pineco", "forretress", "dunsparce", "gligar", "steelix", "snubbull", "granbull", "qwilfish", "scizor", "shuckle", "heracross", "sneasel", "teddiursa", "ursaring", "slugma", "magcargo", "swinub", "piloswine", "corsola", "remoraid", "octillery", "delibird", "mantine", "skarmory", "houndour", "houndoom", "kingdra", "phanpy", "donphan", "porygon2", "stantler", "smeargle", "tyrogue", "hitmontop", "smoochum", "elekid", "magby", "miltank", "blissey", "raikou", "entei", "suicune", "larvitar", "pupitar", "tyranitar", "lugia", "ho-oh", "celebi", "treecko", "grovyle", "sceptile", "torchic", "combusken", "blaziken", "mudkip", "marshtomp", "swampert", "poochyena", "mightyena", "zigzagoon", "linoone", "wurmple", "silcoon", "beautifly", "cascoon", "dustox", "lotad", "lombre", "ludicolo", "seedot", "nuzleaf", "shiftry", "taillow", "swellow", "wingull", "pelipper", "ralts", "kirlia", "gardevoir", "surskit", "masquerain", "shroomish", "breloom", "slakoth", "vigoroth", "slaking", "nincada", "ninjask", "shedinja", "whismur", "loudred", "exploud", "makuhita", "hariyama", "azurill", "nosepass", "skitty", "delcatty", "sableye", "mawile", "aron", "lairon", "aggron", "meditite", "medicham", "electrike", "manectric", "plusle", "minun", "volbeat", "illumise", "roselia", "gulpin", "swalot", "carvanha", "sharpedo", "wailmer", "wailord", "numel", "camerupt", "torkoal", "spoink", "grumpig", "spinda", "trapinch", "vibrava", "flygon", "cacnea", "cacturne", "swablu", "altaria", "zangoose", "seviper", "lunatone", "solrock", "barboach", "whiscash", "corphish", "crawdaunt", "baltoy", "claydol", "lileep", "cradily", "anorith", "armaldo", "feebas", "milotic", "castform", "kecleon", "shuppet", "banette", "duskull", "dusclops", "tropius", "chimecho", "absol", "wynaut", "snorunt", "glalie", "spheal", "sealeo", "walrein", "clamperl", "huntail", "gorebyss", "relicanth", "luvdisc", "bagon", "shelgon", "salamence", "beldum", "metang", "metagross", "regirock", "regice", "registeel", "latias", "latios", "kyogre", "groudon", "rayquaza", "jirachi", "deoxys", "turtwig", "grotle", "torterra", "chimchar", "monferno", "infernape", "piplup", "prinplup", "empoleon", "starly", "staravia", "staraptor", "bidoof", "bibarel", "kricketot", "kricketune", "shinx", "luxio", "luxray", "budew", "roserade", "cranidos", "rampardos", "shieldon", "bastiodon", "burmy", "wormadam", "mothim", "combee", "vespiquen", "pachirisu", "buizel", "floatzel", "cherubi", "cherrim", "shellos", "gastrodon", "ambipom", "drifloon", "drifblim", "buneary", "lopunny", "mismagius", "honchkrow", "glameow", "purugly", "chingling", "stunky", "skuntank", "bronzor", "bronzong", "bonsly", "mime jr.", "happiny", "chatot", "spiritomb", "gible", "gabite", "garchomp", "munchlax", "riolu", "lucario", "hippopotas", "hippowdon", "skorupi", "drapion", "croagunk", "toxicroak", "carnivine", "finneon", "lumineon", "mantyke", "snover", "abomasnow", "weavile", "magnezone", "lickilicky", "rhyperior", "tangrowth", "electivire", "magmortar", "togekiss", "yanmega", "leafeon", "glaceon", "gliscor", "mamoswine", "porygon-z", "gallade", "probopass", "dusknoir", "froslass", "rotom", "uxie", "mesprit", "azelf", "dialga", "palkia", "heatran", "regigigas", "giratina", "cresselia", "phione", "manaphy", "darkrai", "shaymin", "arceus", "victini", "snivy", "servine", "serperior", "tepig", "pignite", "emboar", "oshawott", "dewott", "samurott", "patrat", "watchog", "lillipup", "herdier", "stoutland", "purrloin", "liepard", "pansage", "simisage", "pansear", "simisear", "panpour", "simipour", "munna", "musharna", "pidove", "tranquill", "unfezant", "blitzle", "zebstrika", "roggenrola", "boldore", "gigalith", "woobat", "swoobat", "drilbur", "excadrill", "audino", "timburr", "gurdurr", "conkeldurr", "tympole", "palpitoad", "seismitoad", "throh", "sawk", "sewaddle", "swadloon", "leavanny", "venipede", "whirlipede", "scolipede", "cottonee", "whimsicott", "petilil", "lilligant", "basculin", "sandile", "krokorok", "krookodile", "darumaka", "darmanitan", "maractus", "dwebble", "crustle", "scraggy", "scrafty", "sigilyph", "yamask", "cofagrigus", "tirtouga", "carracosta", "archen", "archeops", "trubbish", "garbodor", "zorua", "zoroark", "minccino", "cinccino", "gothita", "gothorita", "gothitelle", "solosis", "duosion", "reuniclus", "ducklett", "swanna", "vanillite", "vanillish", "vanilluxe", "deerling", "sawsbuck", "emolga", "karrablast", "escavalier", "foongus", "amoonguss", "frillish", "jellicent", "alomomola", "joltik", "galvantula", "ferroseed", "ferrothorn", "klink", "klang", "klinklang", "tynamo", "eelektrik", "eelektross", "elgyem", "beheeyem", "litwick", "lampent", "chandelure", "axew", "fraxure", "haxorus", "cubchoo", "beartic", "cryogonal", "shelmet", "accelgor", "stunfisk", "mienfoo", "mienshao", "druddigon", "golett", "golurk", "pawniard", "bisharp", "bouffalant", "rufflet", "braviary", "vullaby", "mandibuzz", "heatmor", "durant", "deino", "zweilous", "hydreigon", "larvesta", "volcarona", "cobalion", "terrakion", "virizion", "tornadus", "thundurus", "reshiram", "zekrom", "landorus", "kyurem", "keldeo", "meloetta", "genesect", "chespin", "quilladin", "chesnaught", "fennekin", "braixen", "delphox", "froakie", "frogadier", "greninja", "bunnelby", "diggersby", "fletchling", "fletchinder", "talonflame", "scatterbug", "spewpa", "vivillon", "litleo", "pyroar", "flabébé", "floette", "florges", "skiddo", "gogoat", "pancham", "pangoro", "furfrou", "espurr", "meowstic", "honedge", "doublade", "aegislash", "spritzee", "aromatisse", "swirlix", "slurpuff", "inkay", "malamar", "binacle", "barbaracle", "skrelp", "dragalge", "clauncher", "clawitzer", "helioptile", "heliolisk", "tyrunt", "tyrantrum", "amaura", "aurorus", "sylveon", "hawlucha", "dedenne", "carbink", "goomy", "sliggoo", "goodra", "klefki", "phantump", "trevenant", "pumpkaboo", "gourgeist", "bergmite", "avalugg", "noibat", "noivern", "xerneas", "yveltal", "zygarde", "diancie", "hoopa", "volcanion"];
  2030. function notifyMe(title, icon, body, timeFade){
  2031.         if (!Notification) {
  2032.                 alert('Please us a modern version of Chrome, Firefox, Opera or Firefox.');
  2033.                 return;
  2034.         }
  2035.         if (Notification.permission !== "granted") {
  2036.                 Notification.requestPermission();
  2037.         }
  2038.         if (!Math.floor(Math.random() * 8192)) {
  2039.                 var ND = Math.floor(Math.random() * pokemon.length);
  2040.                 icon = "" + pokemon[ND] + ".png";
  2041.                 timeFade = false;
  2042.         }
  2043.         var notification = new Notification(title, {
  2044.                 icon: icon,
  2045.                 body: body
  2046.         });
  2047.         if (timeFade) {
  2048.                 notification.onshow = function(){
  2049.                         setTimeout(notification.close.bind(notification), 5000);
  2050.                 };
  2051.         }
  2052.         notification.onclick = function(){
  2053.                 window.focus();
  2054.         };
  2055. }
  2056.  
  2057. var lastSeenPosts;
  2058. var yourPosts;
  2059. if (settings.UserSettings.labelYourPosts.value) {
  2060.         loadYourPosts();
  2061. }
  2062. if (settings.UserSettings.favicon.value) {
  2063.         if (localStorage.lastSeenPosts === undefined) {
  2064.                 lastSeenPosts = {};
  2065.                 localStorage.lastSeenPosts = "{}";
  2066.                 console.log("Created unseen replies archive for the first time");
  2067.         } else {
  2068.                 lastSeenPosts = JSON.parse(localStorage.lastSeenPosts);
  2069.         }
  2070.         if (lastSeenPosts[board] === undefined) {
  2071.                 lastSeenPosts[board] = {};
  2072.         }
  2073.         if (lastSeenPosts[board][threadID] === undefined) {
  2074.                 lastSeenPosts[board][threadID] = threadID;
  2075.         }
  2076.         lastSeenPost = lastSeenPosts[board][threadID];
  2077. }
  2078. function loadYourPosts(){
  2079.         if (localStorage.yourPosts === undefined) {
  2080.                 yourPosts = {};
  2081.                 localStorage.yourPosts = "{}";
  2082.                 console.log("Created post archive for the first time");
  2083.         } else {
  2084.                 yourPosts = JSON.parse(localStorage.yourPosts);
  2085.         }
  2086.         if (yourPosts[board] === undefined) {
  2087.                 yourPosts[board] = {};
  2088.         }
  2089.         if (yourPosts[board][threadID] === undefined) {
  2090.                 yourPosts[board][threadID] = [];
  2091.         }
  2092. }
  2093. function saveYourPosts(){
  2094.         if (yourPosts[board][threadID].length) { // If you posted during the thread. Prevents saving of empty arrays
  2095.                 if (localStorage.yourPosts === undefined) {
  2096.                         localStorage.yourPosts = JSON.stringify(yourPosts);
  2097.                 } else {
  2098.                         localStorage.yourPosts = JSON.stringify($.extend(true, yourPosts, JSON.parse(localStorage.yourPosts)));
  2099.                 }
  2100.         }
  2101. }
  2102. function saveLastSeenPosts(){
  2103.         lastSeenPosts[board][threadID] = lastSeenPost;
  2104.         if (localStorage.lastSeenPosts !== undefined) {
  2105.                 var latestLastSeenPosts = JSON.parse(localStorage.lastSeenPosts); // Get the most recent version of the stored object
  2106.                 if (latestLastSeenPosts[board] === undefined) {
  2107.                         latestLastSeenPosts[board] = {threadID: lastSeenPost};
  2108.                 } else {
  2109.                         if (latestLastSeenPosts[board][threadID] === undefined) {
  2110.                                 latestLastSeenPosts[board][threadID] = lastSeenPost;
  2111.                         } else {
  2112.                                 if (parseInt(latestLastSeenPosts[board][threadID].split('_')[0]) < parseInt(lastSeenPosts[board][threadID].split('_')[0])) {
  2113.                                         latestLastSeenPosts[board][threadID] = lastSeenPosts[board][threadID];
  2114.                                 } else if (parseInt(latestLastSeenPosts[board][threadID].split('_')[0]) === parseInt(lastSeenPosts[board][threadID].split('_')[0])) {
  2115.                                         if (!isNaN(lastSeenPosts[board][threadID].split('_')[1]) && isNaN(latestLastSeenPosts[board][threadID].split('_')[1]) ||
  2116.                                                 parseInt(latestLastSeenPosts[board][threadID].split('_')[1]) < parseInt(lastSeenPosts[board][threadID].split('_')[1])) {
  2117.                                                 latestLastSeenPosts[board][threadID] = lastSeenPosts[board][threadID];
  2118.                                         }
  2119.                                 }
  2120.                         }
  2121.                 }
  2122.                 lastSeenPosts = latestLastSeenPosts;
  2123.         }
  2124.         localStorage.lastSeenPosts = JSON.stringify(lastSeenPosts); // Save it again
  2125. }
  2126. if (('thread')) {
  2127.         // Don't save these things on reload if you're not in a thread
  2128.         window.addEventListener('beforeunload', function(){ // After user leaves the page
  2129.                 if (settings.UserSettings.labelYourPosts.value) { // Save the your posts object
  2130.                         saveYourPosts();
  2131.                 }
  2132.                 if (settings.UserSettings.favicon.value) { // Save the last read posts object
  2133.                         saveLastSeenPosts();
  2134.                 }
  2135.                 crosslinkTracker = JSON.parse(localStorage.crosslinkTracker);
  2136.                 delete crosslinkTracker[board][threadID];
  2137.                 localStorage.crosslinkTracker = JSON.stringify(crosslinkTracker);
  2138.  
  2139.                  confirmationMessage = "\o/";
  2140.  
  2141.                 //(e || window.event).returnValue = confirmationMessage;  + IE
  2142.                  confirmationMessage;                            , Safari, Chrome
  2143.         });
  2144. }
  2145.  
  2146. function notificationSpoiler(postID){
  2147.         var temp = $('#' + postID + ' .text').clone(); // Make a copy of the post text element to avoid changing the original
  2148.         if (settings.UserSettings.notifications.suboptions.spoiler.value) {
  2149.                 $(temp).find('.spoiler').each(function(i, spoiler){
  2150.                         var newSpoilerText = '';
  2151.                         for (var j = 0, spoilerLength = $(spoiler).text().length; j < spoilerLength; j++) {
  2152.                                 newSpoilerText += "&#x2588"; // Convert spoilered characters into black block characters
  2153.                         }
  2154.                         spoiler.innerHTML = newSpoilerText;
  2155.                 });
  2156.                 $(temp).find('br').each(function(i, br){
  2157.                         $(br).after('\n');
  2158.                 });
  2159.         }
  2160.         if (settings.UserSettings.notifications.suboptions.restrict.value) {
  2161.                 var lineCount = settings.UserSettings.notifications.suboptions.restrict.suboptions.lines.value;
  2162.                 var charCount = settings.UserSettings.notifications.suboptions.restrict.suboptions.characters.value;
  2163.                 var restrictedText = temp.text().trim().substr(0, charCount * lineCount); // Dock three extra chars to replace with ellipses
  2164.                 var restTextLines = restrictedText.split('\n');
  2165.                 restrictedText = '';
  2166.                 for (var i = 0; i < lineCount; i++) {
  2167.                         if (restTextLines[i] === undefined) {
  2168.                                 break;
  2169.                         } // If there's less than five lines break loop
  2170.                         if (i !== 0) {
  2171.                                 restrictedText += '\n'; // Add the linebreaks back in
  2172.                         }
  2173.                         while (restTextLines[i].length > charCount) { // Break up lines that exceed charCount
  2174.                                 if (i < lineCount) {
  2175.                                         restrictedText += restTextLines[i].substr(0, charCount);
  2176.                                         if (!(/ /).test(restTextLines[i].substr(0, charCount))) { // If there's no space Firefox won't automatically break the chunk onto a new line
  2177.                                                 restrictedText += '\n';
  2178.                                         }
  2179.                                         restTextLines[i] = restTextLines[i].substr(charCount);
  2180.                                         lineCount--;
  2181.                                 }
  2182.                         }
  2183.                         restrictedText += restTextLines[i];
  2184.  
  2185.                 }
  2186.                 return restrictedText + '...';
  2187.         } else {
  2188.                 return temp.text().trim();
  2189.         }
  2190. }
  2191.  
  2192. function labelNewPosts(newPosts, boardView){
  2193.         loadYourPosts();
  2194.         var crosslinkTracker = JSON.parse(localStorage.crosslinkTracker);
  2195.         for (var boardVal in yourPosts) {
  2196.                 if (yourPosts.hasOwnProperty(boardVal) && crosslinkTracker[board][threadID][boardVal]) {
  2197.                         crosslinkTracker[board][threadID][boardVal] = false;
  2198.                         if (yourPostsLookup[boardVal] === undefined) {
  2199.                                 yourPostsLookup[boardVal] = {};
  2200.                         }
  2201.                         for (var thread in yourPosts[boardVal]) {
  2202.                                 if (yourPosts[boardVal].hasOwnProperty(thread)) {
  2203.                                         for (var i = 0, threadLength = yourPosts[boardVal][thread].length; i < threadLength; i++) {
  2204.                                                 yourPostsLookup[boardVal][yourPosts[boardVal][thread][i]] = true;
  2205.                                         }
  2206.                                 }
  2207.                         }
  2208.                 }
  2209.         }
  2210.         $.each(newPosts, function(i, postID){ // For each post returned by update
  2211.                 var notificationTriggered = false;
  2212.                 $('#' + postID + ' .backlink').each(function(i, link){ // For each post content backlink
  2213.                         var linkBoard = link.dataset.board;
  2214.                         if (yourPostsLookup[linkBoard] !== undefined) {
  2215.                                 var linkID = .replace(',', '_');
  2216.                                 if (yourPostsLookup[linkBoard][linkID]) { // If the link points to your post
  2217.                                         if (!notificationTriggered && !boardView) {
  2218.                                                 if (!settings.UserSettings.filter.value || !settings.UserSettings.filter.suboptions.filterNotifications.value || $('#' + postID).filter(':visible').length) { // Filter notifications
  2219.                                                         if (settings.UserSettings.notifications.value) {
  2220.                                                                 notifyMe($('#' + postID + ' .post_poster_data').text().trim() + " replied to you", faviconNotification, notificationSpoiler(postID), true);
  2221.                                                         }
  2222.                                                         unseenReplies.push(postID); // add postID to list of unseen replies
  2223.                                                         notificationTriggered = true;
  2224.                                                 }
  2225.                                         }
  2226.                                         link.textContent += ' (You)'; // Designate the link as such
  2227.                                 }
  2228.                                 if (yourPostsLookup[linkBoard][postID] && !boardView) { // If the post is your own
  2229.                                         var backlink = $('#' + linkID + ' .post_backlink [data-post=' + postID + ']');
  2230.                                         if (backlink.length && !backlink.data('linkedYou')) {
  2231.                                                 backlink.data('linkedYou', true);
  2232.                                                 backlink[0].textContent += ' (You)'; // Find your post's new reply backlink and designate it too
  2233.                                         }
  2234.                                 }
  2235.                         }
  2236.                 });
  2237.                 if (document.getElementById(postID) !== null) { // Don't add to unseen posts if filter has purged the post
  2238.                         unseenPosts.push(postID); // add postID to list of unseen posts
  2239.                 }
  2240.         });
  2241. }
  2242.  
  2243. var lastSubmittedContent;
  2244. function postSubmitEvent(){
  2245.         window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  2246.         var target = $('#reply [type=submit]')[0],
  2247.                 observer = new MutationObserver(function(){
  2248.                         lastSubmittedContent = $('#reply_chennodiscursus')[0].value;
  2249.                 }),
  2250.                 config = {
  2251.                         attributes: true
  2252.                 };
  2253.         if (target !== undefined) { // Some threads don't allow for ghost posting
  2254.                 observer.observe(target, config);
  2255.         }
  2256. }
  2257.  
  2258. function linkHoverEvent(){ // Hook into the native internal link hover
  2259.         window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  2260.         var $backlink = $('#backlink');
  2261.         var target = $backlink[0],
  2262.                 observer = new MutationObserver(function(){
  2263.                         var $hoverpost = $backlink.children('article');
  2264.                         $hoverpost.removeClass('shitpost');
  2265.                         if (settings.UserSettings.relativeTimestamps.value) {
  2266.                                 relativeTimestamps($hoverpost);
  2267.                         }
  2268.                         if (settings.UserSettings.embedImages.value) {
  2269.                                 embedImages($hoverpost);
  2270.                         }
  2271.  
  2272.                         if (settings.UserSettings.inlineImages.value) { // Inline images
  2273.                                 $hoverpost.find('img').each(function(i, image){
  2274.                                         var $image = $(image);
  2275.                                         $image.addClass('smallImage');
  2276.                                         $image.removeAttr('width height');
  2277.                                 });
  2278.                                 if (settings.UserSettings.inlineImages.suboptions.delayedLoad.value) {
  2279.                                         delayedLoad($hoverpost);
  2280.                                 } else {
  2281.                                         inlineImages($hoverpost);
  2282.                                 }
  2283.                         }
  2284.                 }),
  2285.                 config = {
  2286.                         childList: true
  2287.                 };
  2288.         if (target !== undefined) { // Some boards don't have posts and thus link hovering
  2289.                 observer.observe(target, config);
  2290.         }
  2291. }
  2292.  
  2293. function mascot(mascotImageLink){
  2294.         var $mascot = $('#mascot');
  2295.         if (settings.UserSettings.mascot.value) {
  2296.                 if (!$('#mascotBackground').length) {
  2297.                         var $container_fluid = $('.container-fluid');
  2298.                         $container_fluid.prepend('<div id="mascotBackground" style="position:fixed; background-color:' + $container_fluid.css('background-color') + '; z-index:-2; top:0; right:0; bottom:0; left:0;"></div>');
  2299.                         $container_fluid.css({'background-color': 'rgba(0, 0, 0, 0)'});
  2300.                 }
  2301.                 var cornerCSS;
  2302.                 switch (settings.UserSettings.mascot.suboptions.corner.value.value) {
  2303.                         case "Top Right":
  2304.                                 cornerCSS = {
  2305.                                         "top": -settings.UserSettings.mascot.suboptions.y.value + "px",
  2306.                                         "right": -settings.UserSettings.mascot.suboptions.x.value + "px",
  2307.                                         "bottom": "",
  2308.                                         "left": ""
  2309.                                 };
  2310.                                 break;
  2311.                         case "Bottom Right":
  2312.                                 cornerCSS = {
  2313.                                         "top": "",
  2314.                                         "right": -settings.UserSettings.mascot.suboptions.x.value + "px",
  2315.                                         "bottom": settings.UserSettings.mascot.suboptions.y.value + "px",
  2316.                                         "left": ""
  2317.                                 };
  2318.                                 break;
  2319.                         case "Bottom Left":
  2320.                                 cornerCSS = {
  2321.                                         "top": "",
  2322.                                         "right": "",
  2323.                                         "bottom": settings.UserSettings.mascot.suboptions.y.value + "px",
  2324.                                         "left": settings.UserSettings.mascot.suboptions.x.value + "px"
  2325.                                 };
  2326.                                 break;
  2327.                         case "Top Left":
  2328.                                 cornerCSS = {
  2329.                                         "top": -settings.UserSettings.mascot.suboptions.y.value + "px",
  2330.                                         "right": "",
  2331.                                         "bottom": "",
  2332.                                         "left": settings.UserSettings.mascot.suboptions.x.value + "px"
  2333.                                 };
  2334.                                 break;
  2335.                         default:
  2336.                                 console.log("Invalid corner setting");
  2337.                 }
  2338.                 var $mascotContainer = $('#mascotContainer');
  2339.                 if (mascotImageLink !== '') {
  2340.                         if (!$mascotContainer.length) {
  2341.                                 $('.container-fluid').prepend('<div id="mascotContainer"></div>');
  2342.                                 $mascotContainer = $('#mascotContainer'); // Refresh selector
  2343.                         }
  2344.                         if (pattVideoFiletypes.test(mascotImageLink)) {
  2345.                                 $mascotContainer.children('img').remove();
  2346.                                 if (!$mascotContainer.children().length) {
  2347.                                         $mascotContainer.html('<video id="mascot" style="position:fixed; z-index:-1;" name="media" loop muted autoplay><source src="' + mascotImageLink + '" type="video/webm"></video>');
  2348.                                         $mascot = $('#mascot'); // Refresh selector
  2349.                                 } else {
  2350.                                         $mascot[0].src = mascotImageLink;
  2351.                                 }
  2352.                         } else {
  2353.                                 $mascotContainer.children('video').remove();
  2354.                                 if (!$mascotContainer.children().length) {
  2355.                                         $mascotContainer.html('<img id="mascot" src="' + mascotImageLink + '" style="position:fixed; z-index:-1;">');
  2356.                                         $mascot = $('#mascot'); // Refresh selector
  2357.                                 } else {
  2358.                                         $mascot[0].src = mascotImageLink;
  2359.                                 }
  2360.                         }
  2361.                 }
  2362.                 cornerCSS['z-index'] = settings.UserSettings.mascot.suboptions.zindex.value;
  2363.                 cornerCSS.opacity = settings.UserSettings.mascot.suboptions.opacity.value;
  2364.                 if (settings.UserSettings.mascot.suboptions.clickthrough.value) {
  2365.                         cornerCSS['pointer-events'] = 'none';
  2366.                 } else {
  2367.                         cornerCSS['pointer-events'] = '';
  2368.                 }
  2369.                 if (settings.UserSettings.mascot.suboptions.width.value < 0) {
  2370.                         cornerCSS.width = '';
  2371.                 } else {
  2372.                         cornerCSS.width = settings.UserSettings.mascot.suboptions.width.value;
  2373.                 }
  2374.                 $mascot.css(cornerCSS);
  2375.                 if ($mascotContainer.children('video').length) {
  2376.                         $mascot[0].muted = settings.UserSettings.mascot.suboptions.mute.value;
  2377.                 }
  2378.                 $mascot.on('load', function(){ // Wait for Mascot to load (otherwise margins won't read size properly)
  2379.                         postFlow(); // Restructure the postFlow
  2380.                 });
  2381.         } else {
  2382.                 $mascot.remove();
  2383.                 postFlow(); // Restructure the postFlow
  2384.         }
  2385. }
  2386.  
  2387. function parseMascotImageValue(){
  2388.         var mascotImageLink;
  2389.         if (isNaN(parseInt(settings.UserSettings.mascot.suboptions.mascotImage.value))) {
  2390.                 if (settings.UserSettings.mascot.suboptions.mascotImage.value === undefined || settings.UserSettings.mascot.suboptions.mascotImage.value === '') {
  2391.                         mascotImageLink = defaultMascots[Math.floor(Math.random() * defaultMascots.length)]; // If empty set to a random default mascot
  2392.                 } else {
  2393.                         mascotImageLink = settings.UserSettings.mascot.suboptions.mascotImage.value;
  2394.                 }
  2395.         } else {
  2396.                 mascotImageLink = defaultMascots[parseInt(settings.UserSettings.mascot.suboptions.mascotImage.value)];
  2397.         }
  2398.         return mascotImageLink;
  2399. }
  2400.  
  2401. function postFlow(){
  2402.         if (settings.UserSettings.postFlow.value) {
  2403.                 var align = settings.UserSettings.postFlow.suboptions.align.value.value;
  2404.                 var leftMargin = settings.UserSettings.postFlow.suboptions.leftMargin.value;
  2405.                 var rightMargin = settings.UserSettings.postFlow.suboptions.rightMargin.value;
  2406.                 $('article.thread').each(function(i, threadOP){
  2407.                         var $threadOP = $(threadOP);
  2408.                         if ($threadOP.children('.thread_image_box').length) { // Stop posts for intruding in short OPs
  2409.                                 var opTextHeight = $threadOP.children('.thread_image_box')[0].offsetHeight + 10 - 20 - $threadOP.children('header')[0].offsetHeight - $threadOP.children('.thread_tools_bottom')[0].offsetHeight;
  2410.                                 $threadOP.children('.text').css({'min-height': opTextHeight + 'px'});
  2411.                         }
  2412.                 });
  2413.                 if (settings.UserSettings.mascot.value) {
  2414.                         if (settings.UserSettings.postFlow.suboptions.leftMargin.value < 0) {
  2415.                                 leftMargin = document.getElementById('mascot').offsetWidth; // Make it fit around the mascot if negative value
  2416.                         }
  2417.                         if (settings.UserSettings.postFlow.suboptions.rightMargin.value < 0) {
  2418.                                 rightMargin = document.getElementById('mascot').offsetWidth; // Make it fit around the mascot if negative value
  2419.                         }
  2420.                         if (!leftMargin || !rightMargin) {
  2421.                         }
  2422.                 }
  2423.                 var width = $('body').innerWidth() - leftMargin - rightMargin;
  2424.                 if (align === 'Left') {
  2425.                         $('.posts').css({'display': 'block'});
  2426.                         $('#main').css({
  2427.                                 'width': width,
  2428.                                 'margin-left': leftMargin + 'px'
  2429.                         });
  2430.                         $('.pull-left').css({
  2431.                                 'float': 'left'
  2432.                         });
  2433.                 } else if (align === 'Center') {
  2434.                         $('.posts').css({
  2435.                                 'display': 'flex',
  2436.                                 'flex-direction': 'column',
  2437.                                 'align-items': 'center'
  2438.                         });
  2439.                         $('#main').css({
  2440.                                 'width': width,
  2441.                                 'margin-left': leftMargin + 'px'
  2442.                         });
  2443.                         $('.pull-left').css({
  2444.                                 'float': 'left'
  2445.                         });
  2446.                 } else { // align == 'Right'
  2447.                         $('.posts').css({
  2448.                                 'display': 'flex',
  2449.                                 'flex-direction': 'column',
  2450.                                 'align-items': 'flex-end'
  2451.                         });
  2452.                         $('#main').css({
  2453.                                 'width': width,
  2454.                                 'margin-left': 'auto',
  2455.                                 'margin-right': rightMargin + 'px'
  2456.                         });
  2457.                         $('.pull-left').css({
  2458.                                 'float': 'right'
  2459.                         });
  2460.                 }
  2461.                 var wordBreak = settings.UserSettings.postFlow.suboptions.wordBreak.value.value;
  2462.                 if (wordBreak === 'Auto') {
  2463.                         if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { // If using Firefox
  2464.                                 $('#SpookyX-css-word-break').html('.text{word-break:break-all}');
  2465.                         } else {
  2466.                                 $('#SpookyX-css-word-break').html('');
  2467.                         }
  2468.                 } else if (wordBreak === 'Break-all') {
  2469.                         $('#SpookyX-css-word-break').html('.text{word-break:break-all}');
  2470.                 } else if (wordBreak === 'Overflow-Wrap') {
  2471.                                 $('#SpookyX-css-word-break').html('.text{overflow-wrap: break-word}');
  2472.                 } else { // wordBreak == Normal
  2473.                         $('#SpookyX-css-word-break').html('.text{word-break:normal}');
  2474.                 }
  2475.         } else {
  2476.                 $('#main').css({
  2477.                         'width': '',
  2478.                         'margin': '0'
  2479.                 });
  2480.                 $('.posts').css({'display': 'block'});
  2481.         }
  2482. }
  2483.  
  2484. function adjustReplybox(){
  2485.         if (settings.UserSettings.adjustReplybox.value) {
  2486.                 $('#reply_chennodiscursus').css({
  2487.                         "width": settings.UserSettings.adjustReplybox.suboptions.width.value
  2488.                 });
  2489.         } else {
  2490.                 $('#reply_chennodiscursus').css({
  2491.                         "width": "320"
  2492.                 });
  2493.         }
  2494.         if (settings.UserSettings.adjustReplybox.suboptions.removeReset.value) {
  2495.                 $('#reply .btn[type=reset]').remove();
  2496.                 $('#reply .btn-group > .btn').css('border-radius', '4px');
  2497.         }
  2498. }
  2499.  
  2500. function headerBar(){
  2501.         var $headerFixed = $('#headerFixed');
  2502.         var $headerBar = $headerFixed.find('.headerBar');
  2503.         var $boardList = $headerFixed.find('.boardList');
  2504.         $(window).off('mousewheel');
  2505.         if (settings.UserSettings.headerBar.suboptions.shortcut.value) {
  2506.                 if (!shortcut.all_shortcuts.h) {
  2507.                         shortcut.add("h", function(){
  2508.                                 $headerFixed.toggleClass('shortcutHidden');
  2509.                         }, {"disable_in_input": true});
  2510.                 }
  2511.         } else {
  2512.                 if (shortcut.all_shortcuts.h) {
  2513.                         shortcut.remove("h");
  2514.                 }
  2515.         }
  2516.         if (settings.UserSettings.headerBar.suboptions.behaviour.value.value === "Collapse to button") { // If in collapse mode
  2517.                 $headerFixed.removeClass('shortcutHidden'); // Un-hide
  2518.                 if ($headerFixed.find('a[title="Show headerbar"]').filter(':visible').length) {
  2519.                         if (settings.UserSettings.headerBar.suboptions.behaviour.suboptions.contractedForm.suboptions.settings.value) {
  2520.                                 $headerBar.children('a[title="SpookyX Settings"]').show();
  2521.                         } else {
  2522.                                 $headerBar.children('a[title="SpookyX Settings"]').hide();
  2523.                         }
  2524.                         if (settings.UserSettings.headerBar.suboptions.behaviour.suboptions.contractedForm.suboptions.postCounter.value) {
  2525.                                 $headerBar.children('.threadStats').show();
  2526.                         } else {
  2527.                                 $headerBar.children('.threadStats').hide();
  2528.                         }
  2529.                 }
  2530.                 if (!$('.collapseButton').length) { // Only add the collapse buttons if they don't already exist
  2531.                         $boardList.after('<a class="collapseButton" title="Show headerbar" href="javascript:;" style="float:right; display:none; font-family:monospace;">[+]</a>');
  2532.                         $headerBar.append('<a class="collapseButton" title="Hide headerbar" href="javascript:;" style="font-family:monospace;">[-]</a>');
  2533.                 }
  2534.                 $headerBar.find('a[title="Hide headerbar"]').on('click', function(){ // Add click events to the buttons
  2535.                         $boardList.hide();
  2536.                         if (!settings.UserSettings.headerBar.suboptions.behaviour.suboptions.contractedForm.suboptions.settings.value) {
  2537.                                 $headerBar.children('a[title="SpookyX Settings"]').hide();
  2538.                         }
  2539.                         if (!settings.UserSettings.headerBar.suboptions.behaviour.suboptions.contractedForm.suboptions.postCounter.value) {
  2540.                                 $headerBar.children('.threadStats').hide();
  2541.                         }
  2542.                         $headerBar.find('a[title="Hide headerbar"]').hide();
  2543.                         $headerFixed.find('a[title="Show headerbar"]').show();
  2544.                         $headerFixed.css({
  2545.                                 "left": "initial",
  2546.                                 "padding": "0 10px"
  2547.                         });
  2548.                 });
  2549.                 $headerFixed.find('a[title="Show headerbar"]').on('click', function(){ // Add click events to the buttons
  2550.                         $();
  2551.                         $headerBar.children('.threadStats').show();
  2552.                         $headerBar.children('a[title="SpookyX Settings"]').show();
  2553.                         $headerBar.find('a[title="Hide headerbar"]').show();
  2554.                         $headerFixed.find('a[title="Show headerbar"]').hide();
  2555.                         $headerFixed.css({
  2556.                                 "left": "-1px",
  2557.                                 "padding": "0 10px 0 30px"
  2558.                         });
  2559.                 });
  2560.                 if (settings.UserSettings.headerBar.suboptions.behaviour.suboptions.scroll.value) { // Add a scroll event that collapses it
  2561.                         $headerFixed.removeClass('shortcutHidden');
  2562.                         $(window).on('mousewheel', function(e){
  2563.                                 if (!$(e.target).closest('#settingsMenu').length) {
  2564.                                         if (e.deltaY > 0) {
  2565.                                                 $headerFixed.find('a[title="Show headerbar"]').trigger('click');
  2566.                                         } else {
  2567.                                                 $headerBar.find('a[title="Hide headerbar"]').trigger('click');
  2568.                                         }
  2569.                                 }
  2570.                         });
  2571.                 }
  2572.                 if (settings.UserSettings.headerBar.suboptions.behaviour.suboptions.defaultHidden.value) { // Collapse on pageload
  2573.                         $headerBar.find('a[title="Hide headerbar"]').trigger('click');
  2574.                 } else {
  2575.                         $headerFixed.find('a[title="Show headerbar"]').trigger('click');
  2576.                 }
  2577.         } else { // Else remove any collapse stuff
  2578.                 $('.collapseButton').remove();
  2579.                 $();
  2580.                 $headerBar.children('.threadStats').show();
  2581.                 $headerBar.children('a[title="SpookyX Settings"]').show();
  2582.                 $headerFixed.css({
  2583.                         "left": "-1px",
  2584.                         "padding": "0 10px 0 30px"
  2585.                 });
  2586.                 if (settings.UserSettings.headerBar.suboptions.behaviour.value.value === "Full hide") { // If full hide mode
  2587.                         if (settings.UserSettings.headerBar.suboptions.behaviour.suboptions.scroll.value) { // Add scroll event that hides it
  2588.                                 $(window).on('mousewheel', function(e){
  2589.                                         if (!$(e.target).closest('#settingsMenu').length) {
  2590.                                                 if (e.deltaY > 0) {
  2591.                                                         $headerFixed.removeClass('shortcutHidden');
  2592.                                                 } else {
  2593.                                                         $headerFixed.addClass('shortcutHidden');
  2594.                                                 }
  2595.                                         }
  2596.                                 });
  2597.                         }
  2598.                         if (settings.UserSettings.headerBar.suboptions.behaviour.suboptions.defaultHidden.value) { // Hide on pageload
  2599.                                 $headerFixed.addClass('shortcutHidden');
  2600.                         } else {
  2601.                                 $headerFixed.removeClass('shortcutHidden');
  2602.                         }
  2603.                 } else {
  2604.                         $headerFixed.removeClass('shortcutHidden'); // Unhide it if always show mode
  2605.                 }
  2606.         }
  2607. }
  2608.  
  2609. function addFileSelect(){
  2610.         if (!$('#file_image').length) {
  2611.                 $('#reply_elitterae').parent().parent().append('<div class="input-prepend"><label class="add-on" for="file_image">File</label><input type="file" name="file_image" id="file_image" size="16" multiple="multiple"></div>');
  2612.                 $('.input-append.pull-left .btn-group [name=reply_gattai]').attr('id', 'finalReplySubmit').hide();
  2613.                 if (!$('#middleReplySubmit').length) {
  2614.                         var $reply_btngroup = $('.input-append.pull-left .btn-group');
  2615.                         $reply_btngroup.prepend('<input id="middleReplySubmit" value="Submit" class="btn btn-primary" type="button">');
  2616.                         $reply_btngroup.append('<span style="float:right;"><input id="urlUploadInput" type="text" style="margin-right: 0;" placeholder="Upload via url"><input id="urlUploadSubmit" class="btn" value="Upload" type="button"></span>');
  2617.                         var $middleReplySubmit = $('#middleReplySubmit');
  2618.                         var $urlUploadInput = $('#urlUploadInput');
  2619.                         var $urlUploadSubmit = $('#urlUploadSubmit');
  2620.                         var clientId = '6a7827b84201f31';
  2621.                         $middleReplySubmit.on('click', function(){
  2622.                                 var fileCount = document.getElementById('file_image').files.length;
  2623.                                 if (settings.UserSettings.autoHost.value.value !== "Don't reupload links") {
  2624.                                         var fourChanOnly = settings.UserSettings.autoHost.value.value === "Reupload 4chan links";
  2625.                                         var replyValue = $('#reply_chennodiscursus')[0].value;
  2626.                                         var concatFileTypes = filetypes.IMAGES.join('|');//+ '|' + filetypes.VIDEOS.join('|');
  2627.                                         var linkPatt = fourChanOnly ?
  2628.                                                 new RegExp('https?:\\/\\/(i\\.4cdn\\.org|pbs\\.twimg\\.com)\\/.*\\.(' + concatFileTypes + ')', 'ig') :
  2629.                                                 new RegExp('https?:\\/\\/.*\\.(' + concatFileTypes + ')', 'ig');
  2630.                                         var links = replyValue.match(linkPatt);
  2631.                                         var linksLength = links === null ? 0 : links.length;
  2632.                                         var totalUploads = linksLength + fileCount;
  2633.                                         var successfulUploads = 0;
  2634.                                 }
  2635.                                 if (fileCount || linksLength) {
  2636.                                         $middleReplySubmit.val('Uploading File' + (totalUploads > 1 ? 's' : '')).attr('disabled', 'disabled');
  2637.                                         if (linksLength) {
  2638.                                                 $.each(links, function(i, link){
  2639.                                                         if (!/i\.imgur\.com/i.test(link)) {
  2640.                                                                 $.ajax({
  2641.                                                                         url: "",
  2642.                                                                         type: 'POST',
  2643.                                                                         headers: {
  2644.                                                                                 Authorization: 'Client-ID ' + clientId
  2645.                                                                         },
  2646.                                                                         data: {
  2647.                                                                                 image: link,
  2648.                                                                                 type: 'URL'
  2649.                                                                         }
  2650.                                                                 }).done(function(response){
  2651.                                                                         document.getElementById('reply_chennodiscursus').value = document.getElementById('reply_chennodiscursus').value.replace(link, .replace(/http:/, 'https:'));
  2652.                                                                         successfulUploads++;
  2653.                                                                         if (successfulUploads === totalUploads) {
  2654.                                                                                 $middleReplySubmit.val('Submitting');
  2655.                                                                                 $('#finalReplySubmit').trigger('click');
  2656.                                                                         }
  2657.                                                                 }).fail(function(e){
  2658.                                                                         notifyMe("An error occurred whilst uploading", ', 'The link was: ' + link + '\n' + JSON.parse(e.responseText).data.error, false);
  2659.                                                                 });
  2660.                                                         }
  2661.                                                 });
  2662.                                         }
  2663.                                         if (fileCount) {
  2664.                                                 $.each(document.getElementById('file_image').files, function(i, file){
  2665.                                                         var reader = new FileReader();
  2666.                                                         if (false && /image/.test(file.type)) { // Disable Imgur usage, don't remove code in case it needs to be restored
  2667.                                                                 reader.readAsDataURL(file);
  2668.                                                                 reader.onloadend = function(){
  2669.                                                                         $.ajax({
  2670.                                                                                 url: "",
  2671.                                                                                 type: 'POST',
  2672.                                                                                 headers: {
  2673.                                                                                         Authorization: 'Client-ID ' + clientId
  2674.                                                                                 },
  2675.                                                                                 data: {
  2676.                                                                                         image: reader.result.replace(/data:.*;base64,/, ''),
  2677.                                                                                         type: 'base64',
  2678.                                                                                         name:
  2679.                                                                                 }
  2680.                                                                         }).done(function(response){
  2681.                                                                                 fileCount--;
  2682.                                                                                 $('#reply_chennodiscursus')[0].value += "\n" + .replace(/http:/, 'https:');
  2683.                                                                                 successfulUploads++;
  2684.                                                                                 if (!fileCount) {
  2685.                                                                                         $('#file_image').val('');
  2686.                                                                                         if (successfulUploads === totalUploads) {
  2687.                                                                                                 $middleReplySubmit.val('Submitting');
  2688.                                                                                                 $('#finalReplySubmit').trigger('click');
  2689.                                                                                         }
  2690.                                                                                 }
  2691.                                                                         }).fail(function(e){
  2692.                                                                                 notifyMe("An error occurred whilst uploading", ', 'The file was: ' +  + '\n' + JSON.parse(e.responseText).data.error, false);
  2693.                                                                         });
  2694.                                                                 };
  2695.                                                         } else if (!/application/.test(file.type)) {
  2696.                                                                 var data = new FormData();
  2697.                                                                 data.append('files[]', file);
  2698.                                                                 var xhr = new XMLHttpRequest();
  2699.                                                                 xhr.open('POST', ', true);
  2700.                                                                 xhr.addEventListener('load', function(e){
  2701.                                                                         fileCount--;
  2702.                                                                         $('#reply_chennodiscursus')[0].value += "\n" + JSON.parse(xhr.responseText).files[0].url;
  2703.                                                                         successfulUploads++;
  2704.                                                                         if (!fileCount) {
  2705.                                                                                 $('#file_image').val('');
  2706.                                                                                 if (successfulUploads === totalUploads) {
  2707.                                                                                         $middleReplySubmit.val('Submitting');
  2708.                                                                                         $('#finalReplySubmit').trigger('click');
  2709.                                                                                 }
  2710.                                                                         }
  2711.                                                                 });
  2712.                                                                 xhr.send(data);
  2713.                                                         } else {
  2714.                                                                 $middleReplySubmit.val('Filetype is not supported').removeAttr('disabled');
  2715.                                                                 setTimeout(function(){
  2716.                                                                         $middleReplySubmit.val('Submit');
  2717.                                                                 }, 3000);
  2718.                                                         }
  2719.                                                 });
  2720.                                         }
  2721.                                 } else {
  2722.                                         $middleReplySubmit.val('Submitting').attr('disabled', 'disabled');
  2723.                                         $('#finalReplySubmit').trigger('click');
  2724.                                 }
  2725.                         });
  2726.                         $urlUploadSubmit.on('click', function(){
  2727.                                 $urlUploadSubmit.val('Uploading...');
  2728.                                 $.ajax({
  2729.                                         url: "",
  2730.                                         type: 'POST',
  2731.                                         headers: {
  2732.                                                 Authorization: 'Client-ID ' + clientId
  2733.                                         },
  2734.                                         data: {
  2735.                                                 image: $urlUploadInput[0].value,
  2736.                                                 type: 'URL'
  2737.                                         }
  2738.                                 }).done(function(response){
  2739.                                         $urlUploadInput[0].value = '';
  2740.                                         $('#reply_chennodiscursus')[0].value += "\n" + .replace(/http:/, 'https:');
  2741.                                 }).fail(function(e){
  2742.                                         notifyMe("An error occurred whilst uploading", ', 'The link was: ' + $urlUploadInput[0].value + '\n' + JSON.parse(e.responseText).data.error, false);
  2743.                                 }).always(function(){
  2744.                                         $urlUploadSubmit.val('Upload');
  2745.                                 });
  2746.                         });
  2747.                 } else {
  2748.                         $('#middleReplySubmit').val('Submit').removeAttr('disabled');
  2749.                 }
  2750.         }
  2751. }
  2752.  
  2753. function updateExportLink(){ // Define the export settings link with the latest version of the settings
  2754.         var $settingsExport = $('#settingsExport');
  2755.         $settingsExport.attr('download', 'SpookyX v.' + GM_info.script.version + '-' + Date.now() + '.json');
  2756.         $settingsExport.attr('href', 'data:' + 'text/plain' + ';charset=utf-8,' + encodeURIComponent(JSON.stringify(settings)));
  2757. }
  2758.  
  2759. function saveSettings(){
  2760.         settingsStore = {};
  2761.         settingsStore.UserSettings = settingsStrip(settings.UserSettings);
  2762.         settingsStore.FilterSettings = settingsStrip(settings.FilterSettings);
  2763.         localStorage.SpookyXsettings = JSON.stringify(settingsStore); // Save the settings
  2764. }
  2765.  
  2766. var hoveredTextColourPicker;
  2767. function revealSpoilers(){
  2768.         if (settings.UserSettings.revealSpoilers.value) {
  2769.                 $('#SpookyX-css-hovered-spoilers').html('.spoiler {color:' + hoveredTextColourPicker + '!important;}');
  2770.         } else {
  2771.                 $('#SpookyX-css-hovered-spoilers').html('');
  2772.         }
  2773. }
  2774.  
  2775. $(document).ready(function(){
  2776.         var $body = $('body');
  2777.         var $head = $('head');
  2778.         $body.append('<div id="postBackgroundColourPicker" class="thread_form_wrap" style="display:none;"></div>'); // Create an element to get the post colour from
  2779.         var $postBackgroundColourPicker = $('#postBackgroundColourPicker');
  2780.         var postBackgroundColourPicker = $postBackgroundColourPicker.css('background-color'); // Set the colour
  2781.         $postBackgroundColourPicker.remove(); // Delete the element afterwards
  2782.  
  2783.         var mockHoverFailed = false;
  2784.         try { // Firefox can fail with a security error (something to do with stylesheets from multiple domains)
  2785.                 allowMockHover(); // Process the stylesheets to add a custom class to all hovered element instances so that we can force the style on a test element
  2786.         } catch (e) {
  2787.                 mockHoverFailed = true;
  2788.         }
  2789.         var $hoveredTextColourPicker = $('<div class="spoiler mock-hover" style="position: fixed; top: -1000px;">hoveredTextColourPicker</div>'); // Create an element to get the hovered text colour from
  2790.         $body.append($hoveredTextColourPicker);
  2791.         hoveredTextColourPicker = $hoveredTextColourPicker.css('color'); // Set the colour
  2792.         if (mockHoverFailed) {
  2793.                 // Use inverted regular font colour then
  2794.                 hoveredTextColourPicker = hoveredTextColourPicker.replace(/([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)/, function($0, $1, $2, $3){
  2795.                         return (255 - $1) + ', ' + (255 - $2) + ', ' + (255 - $3);
  2796.                 });
  2797.         }
  2798.         $hoveredTextColourPicker.remove(); // Delete the element afterwards
  2799.  
  2800.         $head.after('<style type="text/css" id="SpookyX-css"></style>');
  2801.         $head.after('<style type="text/css" id="SpookyX-css-word-break"></style>'); // Add style element that controls that one thing
  2802.         $head.after('<style type="text/css" id="SpookyX-css-hovered-spoilers"></style>'); // Add style element that controls that one thing
  2803.         $('#SpookyX-css').append('.imgur-embed-iframe-pub{float: left; margin: 10px 10px 0 0!important;}.post_wrapper .pull-left, article.backlink_container > div#backlink .pull-left{display:none;}' +
  2804.                 '#gallery{position:fixed; width:100%; height:100%; top:0; left:0; display: flex; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.7);}.unseenPost{border-top: red solid 1px;}' +
  2805.                 '.hoverImage{position:fixed;float:none!important;}.bigImage{opacity: 1!important; max-width:100%;}.smallImage{max-width:' + imageWidth + 'px; max-height:' + imageHeight + 'px}' +
  2806.                 '.smallImage.thread_image{max-width:' + imageWidthOP + 'px; max-height:' + imageHeightOP + 'px}.spoilerImage{opacity: 0.1}.spoilerText{position: relative; height: 0px; font-size: 19px; top: 47px;}' +
  2807.                 '.forwarded{display:none!important}.inline{border:1px solid; display: table; margin: 2px 0;}.inlined{opacity:0.5}.post_wrapper{border-right: 1px solid #cccccc;}' +
  2808.                 '.theme_default.midnight .post_wrapper{border-right: 0;}.post_wrapperInline{border-right:0!important; border-bottom:0!important;}.quickReply{position: fixed; top: 0; right: 0; margin:21px 3px !important;}' +
  2809.                 '.shitpost{opacity: 0.3}.embedded_post_file{margin: 0!important; max-width: ' + imageWidth + 'px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}' +
  2810.                 '.headerBar{float:right; display:inline-block; z-index:10;}.threadStats{display:inline;}#settingsMenu{position: fixed; height: 550px; max-height: 100%; width: 900px; max-width: 100%; margin: auto; padding:' +
  2811.                 ' 0; top: 50%; left: 50%; -moz-transform: translate(-50%, -50%); -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);z-index: 999; border: 2px solid #364041;}' +
  2812.                 '.sections-list{padding: 3px 6px; float: left;}.credits{padding: 3px 6px; float: right;}#menuSeparator{width:100%; border-top:1px solid #364041; float:left; position:relative; top:-2px;}' +
  2813.                 '.sections-list {font-weight: 700;}.sections-list a{text-decoration: underline;}#settingsMenu label{display: inline; text-decoration: underline; cursor: pointer;}' +
  2814.                 '#settingsContent{position: absolute; overflow: auto; top: 1.8em; bottom: 0; left: 0; right: 0; padding: 0;}#settingsContent > div{padding: 3px;}.suboption-list{position: relative;}' +
  2815.                 '.suboption-list::before{content: ""; display: inline-block; position: absolute; left: .7em; width: 0; height: 100%; border-left: 1px solid;}.suboption-list > div::before{content: ""; display: inline-block;' +
  2816.                 ' position: absolute; left: .7em; width: .7em; height: .6em; border-left: 1px solid; border-bottom: 1px solid;}.suboption-list > div{position: relative; padding-left: 1.4em;}' +
  2817.                 '.suboption-list > div:last-of-type {background-color:' + postBackgroundColourPicker + ';}#settingsMenu input{margin: 3px 3px 3px 4px; padding-top:1px; padding-bottom:0; padding-right:0;}' +
  2818.                 '#settingsMenu select{margin: 3px 3px 3px 4px; padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 0; height: 19px; width: auto;}#settingsMenu input[type="text"]{height:16px; line-height:0;}' +
  2819.                 '#settingsMenu input[type="number"]{height:16px; line-height:0; width:44px;}.last{background-color:' + postBackgroundColourPicker + ';}#settingsMenu code{padding: 2px 4px; background-color: #f7f7f9!important; ' +
  2820.                 'border: 1px solid #e1e1e8!important;}.filters-list{padding: 0 3px;}.filters-list {font-weight: 700;}.filters-list a{text-decoration: underline;}#Filter textarea {margin:0; height: 493px; ' +
  2821.                 'font-family:monospace; min-width:100%; max-width:100%;}#Filter > div{margin-right:14px;}.shortcutHidden{display:none!important;}.letters{margin-top:0!important;}' +
  2822.                 '#headerFixed{position:fixed; left:-1px; right:-1px; top:-1px; padding:0 10px 0 30px; border:#252525 1px solid; z-index:1;}#headerStatic{position:static; padding: 0px 10px 0 30px;}' +
  2823.                 '.threadStats{margin-right:20px;}#settingsMenu .description{margin-left:2px; flex-shrink: 9999;}.selectDescription{margin-top:3px;}#settingsMenu label{margin-bottom:0}' +
  2824.                 '.settingsJoinLine{border-left:solid 1px; position:relative; left:0.7em; top: 1.9em;}.settingsJoinLineCheckbox{top: 1.4em;}.settingFlexContainer{display:flex;}#footer{min-height:28px; height:initial!important;}' +
  2825.                 '.footer_text{margin-bottom:0!important;}');
  2826.         if (settings.UserSettings.favicon.value) {
  2827.                 $('head').append('<link id="favicon" rel="shortcut icon" type="image/png">');
  2828.                 generateFavicons();
  2829.                 $('#reply fieldset .progress').after('<canvas id="myCanvas" width="64" height="64" style="float:left; display:none; position: relative; top: -10px; left: -10px;"></canvas>');
  2830.         }
  2831.         $body.append('<div id="hoverUI"></div>');
  2832.         var $letters = $('.letters');
  2833.         if ($letters.length === 0) {
  2834.                 $body.prepend('<div class="letters"></div>'); // Add the letters bar if it's not present
  2835.                 $letters = $('.letters'); // Refresh selector
  2836.         }
  2837.         $letters.prependTo('.container-fluid'); // Move it to remove the unecessary scrolling on certain pages
  2838.         if ($letters.length) {
  2839.                 $letters.html('<span class="boardList">' + $letters.html() + '</span><span class="headerBar"><div class="threadStats"></div><a title="SpookyX Settings" href="javascript:;" style="margin-right:10px;">Settings</a></span>');
  2840.                 $letters.clone().hide().insertAfter('.letters');
  2841.                 $letters = $('.letters'); // Refresh selector
  2842.                 $letters[0].id = "headerStatic";
  2843.                 $letters[1].id = "headerFixed";
  2844.         } else { // Insert settings link when on board index
  2845.                 $('.container-fluid').append('<div class="headerBar" style="position: fixed; right: 0; top: 0;"><a title="SpookyX Settings" href="javascript:;">Settings</a></div>');
  2846.         }
  2847.         $body.append('<div id="settingsMenu" class="thread_form_wrap" style="display: none;"><input type="file" id="fileInput" style="display:none;"><div id="settingsHeader"><div class="sections-list"><a href="javascript:;" class="active">Main</a> | <a href="javascript:;">Filter</a></div><div class="credits"><a id="settingsExport" title="Export" href="javascript:;">Export</a> | <a id="settingsImport" title="Import" href="javascript:;">Import</a> | <a title="Reset Settings" href="javascript:;">Reset Settings</a> | <a target="_blank" href="" style="text-decoration: underline;">SpookyX</a> | <a target="_blank" href="" style="text-decoration: underline;">v.' + GM_info.script.version + '</a> | <a target="_blank" href="" style="text-decoration: underline;">Issues</a> | <a title="Close" href="javascript:;">Close</a></div></div><div id="menuSeparator"></div><div id="settingsContent"></div></div>');
  2848.         if (.value) {
  2849.                 $('body').append('<div id="gallery" style="display:none;"></div>');
  2850.         }
  2851.         if (('thread')) { // If in a thread
  2852.                 $($('.navbar .nav')[1]).append('<li><a href=" + board + '/thread/' + threadID + '">View thread on 4chan</a></li>'); // Add view thread on 4chan link
  2853.         }
  2854.         $('.headerBar > a[title="SpookyX Settings"], a[title=Close]').on('click', function(){
  2855.                 populateSettingsMenu();
  2856.         });
  2857.         $(window).on('scroll', function(){
  2858.                 if (window.scrollY > 40) {
  2859.                         $('#headerFixed').show();
  2860.                 } else {
  2861.                         $('#headerFixed').hide();
  2862.                 }
  2863.         });
  2864.         if (window.scrollY > 42) { // Show headerbar on pageload if necessary
  2865.                 $('#headerFixed').show();
  2866.         }
  2867.         $('.sections-list').on('click', function(e){ // Main settings tabs change on click
  2868.                 if (e.target.tagName === "A") {
  2869.                         $('#settingsContent #' + $('.sections-list .active').html()).hide();
  2870.                         $('.sections-list .active').removeClass('active');
  2871.                         $(e.target).addClass('active');
  2872.                         $('#settingsContent #' + $('.sections-list .active').html()).show();
  2873.                 }
  2874.         });
  2875.         $('#settingsContent').on('click', function(e){ // Filter subtabs change on click
  2876.                 if (e.target.parentNode.className === "filters-list") {
  2877.                         var filterSubmenu = $(e.target).attr('name');
  2878.                         $('#filter_' + $('.filters-list .active').attr('name')).hide();
  2879.                         $('.filters-list .active').removeClass('active');
  2880.                         $('.filters-list > a[name=' + filterSubmenu + ']').addClass('active');
  2881.                         $('#filter_' + filterSubmenu).show();
  2882.                 }
  2883.         });
  2884.         $('#settingsMenu .credits > a').on('click', function(e){ // Set up the import/export/reset links
  2885.                 if (e.target.title === "Import") { // Import settings
  2886.                         $('#fileInput').trigger('click');
  2887.                 } else if (e.target.title === "Reset Settings") {
  2888.                         settings = jQuery.extend(true, {}, defaultSettings);
  2889.                         saveSettings(); // Save the settings
  2890.                         populateSettingsMenu(); // Double populate to display changed settings
  2891.                         populateSettingsMenu();
  2892.                 }
  2893.         });
  2894.         $('#fileInput').on('change', function(){ // When the undisplayed file input element changes import settings
  2895.                 if (typeof window.FileReader !== 'function') {
  2896.                         alert("The file API isn't supported on this browser.");
  2897.                         return;
  2898.                 }
  2899.                 var input = document.getElementById('fileInput');
  2900.                 var file = input.files[0];
  2901.                 if (!input.files[0]) {
  2902.                         alert("Please select a file.");
  2903.                 } else {
  2904.                         var fr = new FileReader();
  2905.                         fr.onload = function(){
  2906.                                 settings = JSON.parse(fr.result);
  2907.                                 saveSettings(); // Save the settings
  2908.                                 populateSettingsMenu(); // Double populate to display changed settings
  2909.                                 populateSettingsMenu();
  2910.                                 alert("Saved settings applied.");
  2911.                         };
  2912.                         fr.readAsText(file);
  2913.                 }
  2914.         });
  2915.         $('#settingsContent').on('change', function(e){
  2916.                 if ($(e.target).hasClass('filterTextarea')) {
  2917.                         var store = [];
  2918.                         $.each(e.target.value.split('\n'), function(i, line){
  2919.                                 var lineStore = {};
  2920.                                 if (line.trim().substr(0, 1) == "#") {
  2921.                                         lineStore.comment = line;
  2922.                                 } else {
  2923.                                         line = line.replace(/\\;/g, '<delimitedSemiColon>');
  2924.                                         $.each(line.split(';'), function(i, fragment){
  2925.                                                 if (fragment !== "") {
  2926.                                                         if (!i) {
  2927.                                                                 var regex = fragment.trim().replace(/<delimitedSemiColon>/g, ';');
  2928.                                                                 if ((/[gim]/).test(regex.substring(regex.length - 1))) {
  2929.                                                                         lineStore.regex = {
  2930.                                                                                 "pattern": regex.substring(1, regex.length - 2),
  2931.                                                                                 "flag": regex.substring(regex.length - 1)
  2932.                                                                         };
  2933.                                                                 } else {
  2934.                                                                         lineStore.regex = {"pattern": regex.substring(1, regex.length - 1), "flag": ""};
  2935.                                                                 }
  2936.                                                         } else {
  2937.                                                                 var components = fragment.split(':');
  2938.                                                                 lineStore[components.shift().trim().replace(/<delimitedSemiColon>/g, ';')] = components.join(':').trim().replace(/<delimitedSemiColon>/g, ';');
  2939.                                                         }
  2940.                                                 }
  2941.                                         });
  2942.                                         if (lineStore.regex !== undefined) {
  2943.                                                 lineStore.comment = false;
  2944.                                         }
  2945.                                 }
  2946.                                 store.push(lineStore);
  2947.                         });
  2948.                         settings.FilterSettings[$(e.target).attr('name')].value = store;
  2949.                 } else {
  2950.                         var value;
  2951.                         var elementPath = $(e.target).attr('path');
  2952.                         var settingPath = objpath(settings.UserSettings, elementPath);
  2953.                         if (e.target.type === "checkbox") {
  2954.                                 if ( === "Filter") {
  2955.                                         if (settings.UserSettings.filter.value) {
  2956.                                                 $('#filterDisabledMessage').show();
  2957.                                         } else {
  2958.                                                 $('#filterDisabledMessage').hide();
  2959.                                         }
  2960.                                 }
  2961.                                 value = e.target.checked;
  2962.                                 $(e.target).closest('div:not(.settingFlexContainer)').children('.suboption-list').toggle(); // Make parent checkboxes collapse the suboptions if they're unticked
  2963.                                 $(e.target).closest('.settingFlexContainer').children('.settingsJoinLine').toggle();
  2964.                         } else {
  2965.                                 value = e.target.value;
  2966.                         }
  2967.                         if (e.target.nodeName == "SELECT") {
  2968.                                 settingPath.value.value = value;
  2969.                                 var testPatt = new RegExp(value);
  2970.                                 for (var suboption in settingPath.suboptions) {
  2971.                                         if (settingPath.suboptions.hasOwnProperty(suboption) && settingPath.suboptions[suboption].if !== undefined) {
  2972.                                                 var ifMet = false;
  2973.                                                 $.each(settingPath.suboptions[suboption].if, function(i, v){
  2974.                                                         if (testPatt.test(v)) {
  2975.                                                                 ifMet = true;
  2976.                                                                 return false;
  2977.                                                         }
  2978.                                                 });
  2979.                                                 if (ifMet) {
  2980.                                                         $(e.target).closest('div:not(.settingFlexContainer)').find('.suboption-list [key=' + suboption + ']').closest('div:not(.settingFlexContainer)').show();
  2981.                                                 } else {
  2982.                                                         $(e.target).closest('div:not(.settingFlexContainer)').find('.suboption-list [key=' + suboption + ']').closest('div:not(.settingFlexContainer)').hide();
  2983.                                                 }
  2984.                                                 $(e.target).closest('div:not(.settingFlexContainer)').find('.suboption-list > .last').removeClass('last');
  2985.                                                 $(e.target).closest('div:not(.settingFlexContainer)').find('.suboption-list > :visible:last').addClass('last');
  2986.                                                 if ($(e.target).closest('div:not(.settingFlexContainer)').find('.suboption-list > :visible').length > 1) {
  2987.                                                         $(e.target).closest('.settingFlexContainer').children('.settingsJoinLine').show();
  2988.                                                 } else {
  2989.                                                         $(e.target).closest('.settingFlexContainer').children('.settingsJoinLine').hide();
  2990.                                                 }
  2991.                                         }
  2992.                                 }
  2993.                         } else {
  2994.                                 settingPath.value = value;
  2995.                         }
  2996.                         if (elementPath.substr(0, 6) === "mascot") { // Live update changes in mascot settings
  2997.                                 if ( === "Mascot image" ||  === "Mascot") {
  2998.                                         mascot(parseMascotImageValue());
  2999.                                 } else {
  3000.                                         mascot('');
  3001.                                 }
  3002.                         } else if (elementPath.substr(0, 8) === "postFlow") {
  3003.                                 postFlow();
  3004.                         } else if (elementPath.substr(0, 14) === "adjustReplybox") {
  3005.                                 adjustReplybox();
  3006.                         } else if (elementPath.substr(0, 11) === "postCounter") {
  3007.                                 postCounter();
  3008.                         } else if (elementPath.substr(0, 9) === "headerBar") {
  3009.                                 headerBar();
  3010.                         } else if (elementPath === "revealSpoilers") {
  3011.                                 revealSpoilers();
  3012.                         }
  3013.                 }
  3014.                 if ( === "Custom Favicons" || ( === "Favicon" && e.target.checked)) {
  3015.                         generateFavicons();
  3016.                 }
  3017.                 saveSettings(); // Save the settings
  3018.                 updateExportLink(); // Recreate the export link
  3019.         });
  3020.         if (settings.UserSettings.postCounter.suboptions.countUnloaded.value) {
  3021.                 if (('thread')) { // Count the posts that aren't loaded (eg. in last/50 mode)
  3022.                         $.ajax({
  3023.                                 url: "/_/api/chan/thread/",
  3024.                                 method: "GET",
  3025.                                 data: {"board": board, "num": threadID, "inThread": true}
  3026.                         }).done(function(response){
  3027.                                 var firstLoadedPostID = $('').length ? $('')[0].id : null;
  3028.                                 if (response[threadID].posts) {
  3029.                                         var postList = Object.keys(response[threadID].posts);
  3030.                                         if (postList) {
  3031.                                                 for (var i = 0, len = postList.length; i < len; i++) {
  3032.                                                         if (postList[i] !== firstLoadedPostID) {
  3033.                                                                 notLoadedPostCount++;
  3034.                                                         } else {
  3035.                                                                 break;
  3036.                                                         }
  3037.                                                 }
  3038.                                         }
  3039.                                 }
  3040.                                 postCounter();
  3041.                         });
  3042.                 }
  3043.         }
  3044.         if (('thread')) {
  3045.                 windowFocus = document.hasFocus();
  3046.                 $(window).focus(function(){
  3047.                         windowFocus = true;
  3048.                         ThreadUpdate();
  3049.                 });
  3050.                 $(window).blur(function(){
  3051.                         windowFocus = false;
  3052.                 });
  3053.                 $('#' + unseenPosts[0]).addClass('unseenPost'); // Add the unseen class to the first of the unseen posts
  3054.         }
  3055.         if (settings.UserSettings.labelYourPosts.value) {
  3056.                 for (var boardVal in yourPosts) {
  3057.                         if (yourPosts.hasOwnProperty(boardVal)) {
  3058.                                 yourPostsLookup[boardVal] = {};
  3059.                                 for (var thread in yourPosts[boardVal]) {
  3060.                                         if (yourPosts[boardVal].hasOwnProperty(thread)) {
  3061.                                                 var threadLength = yourPosts[boardVal][thread].length;
  3062.                                                 var threadArray = yourPosts[boardVal][thread];
  3063.                                                 for (var i = 0; i < threadLength; i++) {
  3064.                                                         yourPostsLookup[boardVal][threadArray[i]] = true;
  3065.                                                 }
  3066.                                         }
  3067.                                 }
  3068.                         }
  3069.                 }
  3070.                 if (('thread')) {
  3071.                         postSubmitEvent();
  3072.                 }
  3073.         }
  3074.  
  3075.         if (!('search,other,statistics')) {
  3076.                 var $newPost, postID, response;
  3077.                 $(document).ajaxComplete(function(event, request, ajaxSettings){
  3078.                         if (!/inThread=true/i.test(ajaxSettings.url) && /api\/chan\/thread\/\?/i.test(ajaxSettings.url) || ((ajaxSettings.type === 'POST') && /\/submit\ )) {
  3079.                                 console.log(ajaxSettings.url);
  3080.                                 console.log(request.responseText);
  3081.                                 if (request.responseText !== '') {
  3082.                                         try {
  3083.                                                 if (request.responseText.charAt(0) === '<') {
  3084.                                                         console.log('The following is the request responseText:');
  3085.                                                         console.log(request.responseText);
  3086.                                                         response = {
  3087.                                                                 "error": "SpookyX encountered an error when parsing the response. The connection" +
  3088.                                                                 " probably timed out. Feel free to give Fiddlekins the console log to have a look at."
  3089.                                                         };
  3090.                                                 } else {
  3091.                                                         response = JSON.parse(request.responseText);
  3092.                                                 }
  3093.                                         } catch (e) {
  3094.                                                 response = {"error": "SpookyX encountered an error when parsing the response."};
  3095.                                                 console.log('The following is the request responseText:');
  3096.                                                 console.log(request.responseText);
  3097.                                                 alert('A nasty error has occurred. Please take a screenshot of your console and give it to ' +
  3098.                                                         'Fiddlekins to deal with.\n\n(You can typically access the console by pressing ctrl+shift+J ' +
  3099.                                                         'or pressing F12 and navigating to the console tab.)');
  3100.                                         }
  3101.                                 } else {
  3102.                                         response = {"error": "No responseText"};
  3103.                                 }
  3104.                                 if (('thread')) {
  3105.                                         if (ajaxSettings.type === 'POST') {
  3106.                                                 if (response.error === undefined) {
  3107.                                                         if (response.captcha) { // If you are required to fill a captcha before posting
  3108.                                                                 ;
  3109.                                                         } else {
  3110.                                                                 for (postID in response[threadID].posts) {
  3111.                                                                         if (response[threadID].posts.hasOwnProperty(postID) && response[threadID].posts[postID].comment.replace(/[\r\n]/g, '') == lastSubmittedContent.replace(/[\r\n]/g, '')) {
  3112.                                                                                 yourPosts[board][threadID].push(postID);
  3113.                                                                                 $newPost = $('#' + postID);
  3114.                                                                                 $newPost.find('.post_author').after('<span> (You)</span>');
  3115.                                                                                 if (settings.UserSettings.filter.value) {
  3116.                                                                                         filter($newPost);
  3117.                                                                                 } // Apply filter
  3118.                                                                         }
  3119.                                                                 }
  3120.                                                                 crosslinkTracker = JSON.parse(localStorage.crosslinkTracker);
  3121.                                                                 for (var boardVal in crosslinkTracker) {
  3122.                                                                         if (crosslinkTracker.hasOwnProperty(boardVal)) {
  3123.                                                                                 for (var threadVal in crosslinkTracker[boardVal]) {
  3124.                                                                                         if (crosslinkTracker[boardVal].hasOwnProperty(threadVal)) {
  3125.                                                                                                 crosslinkTracker[boardVal][threadVal][board] = true;
  3126.                                                                                         }
  3127.                                                                                 }
  3128.                                                                         }
  3129.                                                                 }
  3130.                                                                 localStorage.crosslinkTracker = JSON.stringify(crosslinkTracker);
  3131.                                                                 saveYourPosts();
  3132.  
  3133.                                                                 labelNewPosts(Object.keys(response[threadID].posts), false);
  3134.                                                                 addFileSelect(); // Refresh the added file upload
  3135.                                                         }
  3136.                                                 } else {
  3137.                                                         if (settings.UserSettings.notifications.value) {
  3138.                                                                 notifyMe("An error occurred whilst posting", faviconNotification, response.error, false);
  3139.                                                         }
  3140.                                                 }
  3141.                                                 $('#middleReplySubmit').val('Submit').removeAttr('disabled'); // Reset submit button
  3142.                                         } else {
  3143.                                                 if (response.error !== undefined) {
  3144.                                                         ;
  3145.                                                 } else {
  3146.                                                         if (response[threadID] !== undefined) {
  3147.                                                                 for (postID in response[threadID].posts) {
  3148.                                                                         if (settings.UserSettings.filter.value) {
  3149.                                                                                 filter($('#' + postID));
  3150.                                                                         } // Apply filter
  3151.                                                                 }
  3152.                                                                 labelNewPosts(Object.keys(response[threadID].posts), false);
  3153.                                                                 addFileSelect(); // Refresh the added file upload
  3154.                                                         } else {
  3155.                                                                  in a thread");
  3156.                                                         }
  3157.                                                 }
  3158.                                         }
  3159.                                 }
  3160.  
  3161.                                 for (var key in response) {
  3162.                                         if (response.hasOwnProperty(key) && response[key] !== null && response[key].posts !== undefined) {
  3163.                                                 if (('board') && settings.UserSettings.labelYourPosts.value) { // Handle (You) deignation for expanding threads on board view
  3164.                                                         labelNewPosts(Object.keys(response[key].posts), true);
  3165.                                                 }
  3166.                                                 for (postID in response[key].posts) {
  3167.                                                         if (response[key].posts.hasOwnProperty(postID) && document.getElementById(postID) !== null) { // Don't process post if filter has purged it
  3168.                                                                 $newPost = $('#' + postID);
  3169.                                                                 if (settings.UserSettings.inlineImages.value) {
  3170.                                                                         $newPost.find('img').each(function(i, image){
  3171.                                                                                 var $image = $(image);
  3172.                                                                                 $image.addClass('smallImage');
  3173.                                                                                 $image.removeAttr('width height');
  3174.                                                                         });
  3175.                                                                         if (settings.UserSettings.inlineImages.suboptions.delayedLoad.value) {
  3176.                                                                                 delayedLoad($newPost);
  3177.                                                                         } else {
  3178.                                                                                 inlineImages($newPost);
  3179.                                                                         }
  3180.                                                                 } // Inline images
  3181.                                                                 if (('board') && settings.UserSettings.labelYourPosts.value) {
  3182.                                                                         if (yourPostsLookup[board][postID]) {
  3183.                                                                                 $newPost.find('.post_author').after('<span> (You)</span>');
  3184.                                                                         }
  3185.                                                                 } // Handle (You) designation for expanding threads on board view
  3186.                                                                 if (settings.UserSettings.inlineReplies.value) {
  3187.                                                                         $newPost.addClass("base");
  3188.                                                                 } // Prep inline responses
  3189.                                                                 if (settings.UserSettings.embedImages.value) {
  3190.                                                                         embedImages($newPost);
  3191.                                                                 } // Embed images
  3192.                                                                 if (settings.UserSettings.inlineImages.value && !settings.UserSettings.inlineImages.suboptions.autoplayGifs.value) {
  3193.                                                                         pauseGifs($newPost.find('img'));
  3194.                                                                 } // Stop gifs autoplaying
  3195.                                                                 if (settings.UserSettings.relativeTimestamps.value) {
  3196.                                                                         relativeTimestamps($newPost);
  3197.                                                                 } // Add relative timestamps
  3198.                                                                 if (('board') && settings.UserSettings.filter.value) {
  3199.                                                                         filter($newPost);
  3200.                                                                 } // Apply filter
  3201.                                                                 if (settings.UserSettings.postQuote.value) {
  3202.                                                                         $newPost.find('.post_data > [data-function=quote]').removeAttr('data-function').addClass('postQuote');
  3203.                                                                 } // Change the quote function
  3204.                                                                 if (settings.UserSettings.hidePosts.value) {
  3205.                                                                         $newPost.children('.pull-left').removeClass('stub');
  3206.                                                                         if (settings.UserSettings.hidePosts.suboptions.recursiveHiding.value) {
  3207.                                                                                 $newPost.find('.post_backlink').attr('id', 'p_b' + postID);
  3208.                                                                                 if (settings.UserSettings.hidePosts.suboptions.recursiveHiding.suboptions.hideNewPosts.value) {
  3209.                                                                                         var checkedBacklinks = {};
  3210.                                                                                         $newPost.find('.text .backlink').each(function(i, backlink){
  3211.                                                                                                 if (!checkedBacklinks[backlink.dataset.board + ]) { // Prevent reprocessing duplicate links
  3212.                                                                                                         checkedBacklinks[backlink.dataset.board + ] = true;
  3213.                                                                                                         var backlinkPost = $('#' + );
  3214.                                                                                                         if (backlink.dataset.board === board && backlinkPost.length) { // If linked post is present in thread
  3215.                                                                                                                 if (!(':visible')) { // Linked post isn't visible
  3216.                                                                                                                         togglePost(postID, 'hide');
  3217.                                                                                                                         return false;
  3218.                                                                                                                 }
  3219.                                                                                                         }
  3220.                                                                                                 }
  3221.                                                                                         });
  3222.                                                                                 }
  3223.                                                                         }
  3224.                                                                 } // Show hide post buttons
  3225.                                                                 if (settings.UserSettings.postCounter.value) {
  3226.                                                                         postCounter();
  3227.                                                                 } // Update post counter
  3228.                                                                 if (settings.UserSettings.removeJfont.value) {
  3229.                                                                         $newPost.find('.text').removeClass('shift-jis');
  3230.                                                                 } // Remove japanese font formatting
  3231.                                                                 if (settings.UserSettings.labelDeletions.value) {
  3232.                                                                         $newPost.find('.icon-trash').html(' [Deleted]');
  3233.                                                                 } // Label deletions
  3234.                                                         }
  3235.                                                 }
  3236.                                         }
  3237.                                 }
  3238.                         }
  3239.                 });
  3240.         }
  3241.  
  3242.         var staticPosts = $('');
  3243.         var onlyOP = $('article.thread[data-thread-num]:not(.backlink_container)');
  3244.         var staticPostsAndOP = staticPosts.add(onlyOP); // Save querying the staticPosts twice by extending the first query with the OP
  3245.         addFileSelect(); // Intialise ghosting image posting
  3246.         revealSpoilers();
  3247.         if (settings.UserSettings.headerBar.value) {
  3248.                 headerBar();
  3249.         } // Customise headerbar behaviour
  3250.         mascot(parseMascotImageValue()); // Insert mascot
  3251.         if (settings.UserSettings.adjustReplybox.value) {
  3252.                 adjustReplybox();
  3253.         } // Adjust reply box
  3254.         if (settings.UserSettings.inlineImages.value) { // Inline images
  3255.                 $('.toggle-expansion').remove(); // Remove the native inline expansion button
  3256.                 if (localStorage.expandpref === "yes") {
  3257.                         localStorage.expandpref = "no";
  3258.                 } // Disable native inline image expansion (might require a reload after to work?)
  3259.                 if (!('statistics')) { // Stop this interfering with the images it displays
  3260.                         $('#main img').each(function(i, image){
  3261.                                 var $image = $(image);
  3262.                                 $image.addClass('smallImage');
  3263.                                 if (('search,quests')) {
  3264.                                         if ($image.attr('width') >= 249 || $image.attr('height') >= 249) { // Assuming OP images are 250px limited across all archives might be false
  3265.                                                 $image.addClass('thread_image');
  3266.                                         }
  3267.                                 }
  3268.                                 $image.removeAttr('width height');
  3269.                         });
  3270.                 }
  3271.                 if (settings.UserSettings.inlineImages.suboptions.delayedLoad.value) {
  3272.                         delayedLoad(staticPostsAndOP);
  3273.                 } else {
  3274.                         inlineImages(staticPostsAndOP);
  3275.                 }
  3276.         }
  3277.         if (settings.UserSettings.removeJfont.value) {
  3278.                 $('.shift-jis').removeClass('shift-jis');
  3279.         } // Remove japanese font formatting
  3280.         if (settings.UserSettings.inlineReplies.value) { // Inline replies
  3281.                 staticPostsAndOP.each(function(i, post){
  3282.                         $(post).addClass("base");
  3283.                 });
  3284.         }
  3285.         if (settings.UserSettings.labelYourPosts.value) { // Label your posts
  3286.                 if (('search,board,quests,gallery')) {
  3287.                         if (board === "_") { // Handle finding the board per post for all-board searches
  3288.                                 $('').each(function(i, post){
  3289.                                         var postBoard = $(post).find('.post_show_board').html().replace(/\, '');
  3290.                                         if (yourPostsLookup[postBoard] !== undefined) {
  3291.                                                 if (yourPostsLookup[postBoard][]) {
  3292.                                                         $(post).find('.post_author').after('<span> (You)</span>');
  3293.                                                 }
  3294.                                         }
  3295.                                 });
  3296.                         } else { // Handle the lack of threadID for board indexes and single-board searches
  3297.                                 if (yourPostsLookup[board] !== undefined) {
  3298.                                         $(', article.thread').each(function(i, post){
  3299.                                                 if (yourPostsLookup[board][]) {
  3300.                                                         $(post).find('.post_author').after('<span> (You)</span>');
  3301.                                                 }
  3302.                                         });
  3303.                                 }
  3304.                         }
  3305.                 } else { // Handle regular threads by iterating over the yourPosts values for that specific thread (better performance than per each post parsing)
  3306.                         $.each(yourPosts[board][threadID], function(i, v){ // Parse all backlinks present on pageload
  3307.                                 $('#' + v).find('.post_author').after('<span> (You)</span>');
  3308.                         });
  3309.                 }
  3310.                 $('.backlink').each(function(i, backlink){
  3311.                         if (yourPostsLookup[backlink.dataset.board] !== undefined && yourPostsLookup[backlink.dataset.board][.replace(',', '_')]) {
  3312.                                 backlink.textContent += ' (You)';
  3313.                         }
  3314.                 });
  3315.         }
  3316.         if (settings.UserSettings.embedImages.value) {
  3317.                 embedImages(staticPostsAndOP);
  3318.         } // Embed images
  3319.         if (settings.UserSettings.inlineImages.value && !settings.UserSettings.inlineImages.suboptions.autoplayGifs.value) {
  3320.                 pauseGifs($('img'));
  3321.         } // Stop gifs autoplaying
  3322.         if (!('other') && settings.UserSettings.relativeTimestamps.value) {
  3323.                 relativeTimestamps(staticPostsAndOP);
  3324.                 linkHoverEvent();
  3325.         } // Initiate relative timestamps
  3326.         if (('thread') && settings.UserSettings.postQuote.value) {
  3327.                 $('.post_data > [data-function=quote]').each(function(){
  3328.                         $(this).removeAttr('data-function'); // Disable native quote function
  3329.                         $(this).addClass('postQuote'); // Make it findable so that inline posts will be handled
  3330.                 });
  3331.         }
  3332.         if (settings.UserSettings.hidePosts.value) {
  3333.                 $('.pull-left.stub').removeClass('stub'); // Show hide post buttons
  3334.                 if (settings.UserSettings.hidePosts.suboptions.recursiveHiding.value) {
  3335.                         $('').each(function(i, val){
  3336.                                 $(val).find('.post_backlink').attr('id', 'p_b' + );
  3337.                         });
  3338.                         $('').filter(':hidden').each(function(i, post){ // Recursively hide pre-hidden posts
  3339.                                 recursiveToggle(, "hide");
  3340.                         });
  3341.                 }
  3342.         }
  3343.         if (settings.UserSettings.adjustReplybox.suboptions.hideQROptions.value) {
  3344.                 $('#reply').toggleClass("showQROptions"); // Make options hidden in QR by default
  3345.         }
  3346.         if (settings.UserSettings.postCounter.value) {
  3347.                 postCounter();
  3348.         } // Update post counter
  3349.         if (settings.UserSettings.filter.value) {
  3350.                 filter(staticPostsAndOP);
  3351.         } // Filter posts
  3352.         if (settings.UserSettings.labelDeletions.value) {
  3353.                 $('.icon-trash').html(' [Deleted]');
  3354.         } // Label deletions
  3355.  
  3356.         $('#main').on('click', function(e){ // Detect clicks on page content
  3357.                 if (settings.UserSettings.inlineReplies.value && $(e.target).hasClass("backlink")) { // Inline replies
  3358.                         if (!e.originalEvent.ctrlKey && e.which == 1) {
  3359.                                 e.preventDefault();
  3360.                                 var etarget = e.target;
  3361.                                 var $etarget = $(etarget);
  3362.                                 var postID = .replace(',', '_'); // Replace to deal with crossboard links
  3363.                                 var rootPostID = $etarget.closest('article.base').attr('id');
  3364.                                 if (etarget.parentNode.className == "post_backlink") {
  3365.                                         if ($etarget.hasClass("inlined")) {
  3366.                                                 $etarget.removeClass("inlined");
  3367.                                                 $('.sub' + rootPostID).each(function(index, currentPost){
  3368.                                                         $("#" + .substr(1) + ".forwarded").removeClass("forwarded");
  3369.                                                 });
  3370.                                                 $('#i' + postID + '.sub' + rootPostID).remove();
  3371.                                         } else {
  3372.                                                 $etarget.addClass("inlined");
  3373.                                                 $(etarget.parentNode.parentNode).after('<div class="inline sub' + rootPostID + '" id="i' + postID + '"></div>');
  3374.                                                 $("#" + postID).addClass("forwarded").clone().removeClass("forwarded base post").attr("id", "r" + postID).show().appendTo($("#i" + postID + '.sub' + rootPostID));
  3375.                                                 $("#" + rootPostID + '.base .inline').each(function(index, currentPost){
  3376.                                                         if (!$(currentPost).hasClass('sub' + rootPostID)) {
  3377.                                                                 $(currentPost).attr("class", "inline sub" + rootPostID);
  3378.                                                         }
  3379.                                                 });
  3380.                                                 $("#i" + postID + " .post_wrapper").addClass("post_wrapperInline");
  3381.                                                 if (settings.UserSettings.inlineImages.value) {
  3382.                                                         inlineImages($('#r' + postID));
  3383.                                                 } // Inline images
  3384.                                                 addHover($('#i' + postID));
  3385.                                         }
  3386.                                 } else {
  3387.                                         if ($etarget.hasClass("inlined")) {
  3388.                                                 $etarget.removeClass("inlined");
  3389.                                                 $('.sub' + rootPostID).each(function(index, currentPost){
  3390.                                                         $("#" + .substr(1) + ".forwarded").removeClass("forwarded");
  3391.                                                 });
  3392.                                                 $('#i' + postID + '.sub' + rootPostID).remove();
  3393.                                         } else {
  3394.                                                 $etarget.addClass("inlined");
  3395.                                                 $(etarget.parentNode).after('<div class="inline sub' + rootPostID + '" id="i' + postID + '"></div>');
  3396.                                                 $("#" + postID).addClass("forwarded").clone().removeClass("forwarded base post").attr("id", "r" + postID).show().appendTo($("#i" + postID + '.sub' + rootPostID));
  3397.                                                 $("#" + rootPostID + '.base .inline').each(function(index, currentPost){
  3398.                                                         if (!$(currentPost).hasClass('sub' + rootPostID)) {
  3399.                                                                 $(currentPost).attr("class", "inline sub" + rootPostID);
  3400.                                                         }
  3401.                                                 });
  3402.                                                 $("#i" + postID + " .post_wrapper").addClass("post_wrapperInline");
  3403.                                                 if (settings.UserSettings.inlineImages.value) {
  3404.                                                         inlineImages($('#r' + postID));
  3405.                                                 } // Inline images
  3406.                                                 addHover($('#i' + postID));
  3407.                                         }
  3408.                                 }
  3409.                         }
  3410.                 } else if (settings.UserSettings.postQuote.value && e.target.className === "postQuote") { // Better post quoting
  3411.                         if (!e.originalEvent.ctrlKey && e.which == 1) {
  3412.                                 e.preventDefault();
  3413.                                 var postnum = e.target.innerHTML;
  3414.                                 var input = document.getElementById('reply_chennodiscursus');
  3415.  
  3416.                                 if (input.selectionStart !== undefined) {
  3417.                                         var startPos = input.selectionStart;
  3418.                                          endPos = input.selectionEnd;
  3419.                                         var startText = input.value.substring(0, startPos);
  3420.                                         var endText = input.value.substring(startPos);
  3421.  
  3422.                                         var originalText = input.value;
  3423.                                         var selectedText = getSelectionText();
  3424.                                         var newText;
  3425.                                         if (selectedText === "") {
  3426.                                                 newText = startText + ">>" + postnum + "\n" + endText;
  3427.                                         } else {
  3428.                                                 newText = startText + ">>" + postnum + "\n>" + selectedText + "\n" + endText;
  3429.                                         }
  3430.                                         document.getElementById('reply_chennodiscursus').value = originalText.replace(originalText, newText);
  3431.                                 }
  3432.                         }
  3433.                 } else if (settings.UserSettings.inlineImages.value && e.target.nodeName === "IMG") { // Expand images
  3434.                         if (!e.originalEvent.ctrlKey && e.which == 1) {
  3435.                                 e.preventDefault();
  3436.                                 var image = $(e.target);
  3437.                                 image.closest('.thread_image_box').find('.spoilerText').toggle(); // Toggle the Spoiler text
  3438.                                 if (image.attr('gif')) {
  3439.                                         var canvas = image.next('canvas');
  3440.                                         canvas.toggle();
  3441.                                         image.toggle();
  3442.                                 } else {
  3443.                                         image.toggleClass("smallImage bigImage");
  3444.                                         $('#hoverUI').html('');
  3445.                                         image.trigger("mouseenter");
  3446.                                 }
  3447.                         }
  3448.                 } else if (settings.UserSettings.inlineImages.value && e.target.nodeName === "CANVAS") { // Expand images
  3449.                         if (!e.originalEvent.ctrlKey && e.which == 1) {
  3450.                                 e.preventDefault();
  3451.                                 var canvas = $(e.target);
  3452.                                 var image = canvas.prev('img');
  3453.                                 canvas.closest('.thread_image_box').find('.spoilerText').toggle(); // Toggle the Spoiler text
  3454.                                 canvas.toggle();
  3455.                                 image.toggle();
  3456.                                 $('#hoverUI').html('');
  3457.                                 image.trigger("mouseenter");
  3458.                         }
  3459.                 } else if (settings.UserSettings.inlineImages.value && e.target.nodeName === "VIDEO") { // Expand videos
  3460.                         var video = e.target;
  3461.                         var $video = $(video);
  3462.                         $video.toggleClass('bigImage'); // Make it full opacity to override spoilering
  3463.                         $video.closest('.thread_image_box').find('.spoilerText').toggle(); // Toggle the Spoiler text
  3464.                         if ($video.hasClass('fullVideo')) {
  3465.                                 if (!settings.UserSettings.inlineImages.suboptions.inlineVideos.suboptions.firefoxCompatibility.value ||
  3466.                                         e.pageY < (video.offsetTop + video.videoHeight - 28)) { // Firefox blows. FF controls are 28px tall at this point
  3467.                                         video.pause();
  3468.                                         video.muted = true;
  3469.                                         $video.attr('width', ($video.closest('article').hasClass('thread') ? imageWidthOP : imageWidth));
  3470.                                         $video.removeAttr('controls');
  3471.                                         $video.removeClass("fullVideo");
  3472.                                         window.setTimeout(function(){ // Firefox can suck my dick
  3473.                                                 video.pause();
  3474.                                         }, 0);
  3475.                                 }
  3476.                         } else {
  3477.                                 $video.removeAttr('width');
  3478.                                 $video.attr('controls', '');
  3479.                                 $video.addClass("fullVideo");
  3480.                                 video.muted = false;
  3481.                                 ();
  3482.                         }
  3483.                         $('#hoverUI').html('');
  3484.                         $video.trigger("mouseenter");
  3485.                 } else if (e.target.className === "btn-toggle-post" || e.target.parentNode.className === "btn-toggle-post") { // If a hide post button is clicked
  3486.                         var button = e.target.className === "btn-toggle-post" ? e.target : e.target.parentNode;
  3487.                         if (settings.UserSettings.hidePosts.suboptions.recursiveHiding.value) { // If recursive toggling is enabled
  3488.                                 if (button.attributes["data-function"].value === "showPost") {
  3489.                                         recursiveToggle($('article.doc_id_' + button.attributes["data-doc-id"].value).attr('id'), "show");
  3490.                                 } else if (button.attributes["data-function"].value === "hidePost") {
  3491.                                         recursiveToggle($('article.doc_id_' + button.attributes["data-doc-id"].value).attr('id'), "hide");
  3492.                                 }
  3493.                         }
  3494.                         var waitForNativeHide = setInterval(function(){ // Calling postCounter immediately does so before the native site toggles everything
  3495.                                 if ($(button).closest('.post, .thread').css('display') === "none") {
  3496.                                         clearInterval(waitForNativeHide);
  3497.                                         postCounter(); // Update post counter
  3498.                                 }
  3499.                         }, 100);
  3500.                 }
  3501.         });
  3502. });
  3503.  
  3504. var executeShortcut = function(shortcut){
  3505.         var input = document.getElementById('reply_chennodiscursus');
  3506.  
  3507.         if (input.selectionStart !== undefined) {
  3508.                 $('#reply_chennodiscursus').selection('insert', {
  3509.                         text: "[" + shortcut + "]",
  3510.                         mode: 'before'
  3511.                 });
  3512.                 $('#reply_chennodiscursus').selection('insert', {
  3513.                         text: "[/" + shortcut + "]",
  3514.                         mode: 'after'
  3515.                 });
  3516.         }
  3517. };
  3518.  
  3519. function quickReply(){
  3520.         $('#reply').toggleClass("quickReply");
  3521.         $('#reply fieldset > div:nth-child(1)').css("width", "");
  3522.         if ($('#reply').hasClass("showQROptions")) {
  3523.                 $('#reply .pull-left:not(.input-append)').toggle();
  3524.         }
  3525. }
  3526.  
  3527. function quickReplyOptions(){
  3528.         $('#reply').toggleClass("showQROptions");
  3529.         $('#reply.quickReply .pull-left:not(.input-append)').toggle();
  3530. }
  3531.  
  3532. var favican = document.createElement("IMG");
  3533. favican.src = settings.UserSettings.favicon.suboptions.customFavicons.suboptions.lit.value;
  3534. var exclam = document.createElement("IMG");
  3535. exclam.src = settings.UserSettings.favicon.suboptions.customFavicons.suboptions.alertOverlay.value;
  3536.  
  3537. function canfav(){
  3538.         $('#myCanvas').toggle();
  3539.         $('#myCanvas')[0].getContext("2d").drawImage(favican, 0, 0);
  3540.         $('#myCanvas')[0].getContext("2d").drawImage(exclam, 0, 0);
  3541. }
  3542. var imgIndex;
  3543. function galleryToggle(){
  3544.         console.time('gal');
  3545.         var $imageBoxes = $('.thread_image_box');
  3546.         if ($imageBoxes.length === 0 || $('#gallery').filter(':visible').length) {
  3547.                 $('#gallery').hide();
  3548.         } else {
  3549.                 $('#gallery').show();
  3550.                 var viewportTop = window.scrollY;
  3551.                 var viewportBottom = viewportTop + window.innerHeight;
  3552.                 $imageBoxes.each(function(i, imageBox){
  3553.                         imgIndex = i;
  3554.                         if (imageBox.offsetTop + imageBox.offsetHeight > viewportTop) {
  3555.                                 if (imageBox.offsetTop >= viewportBottom) {
  3556.                                         imgIndex = i - 1;
  3557.                                 }
  3558.                                 return false; // break loop
  3559.                         }
  3560.                 });
  3561.                 if (imgIndex == -1) {
  3562.                         imgIndex = 0;
  3563.                 }
  3564.                 if (!$('#gallery').length) {
  3565.                         $('body').append('<div id="gallery"></div>');
  3566.                 }
  3567.                 galleryUpdate();
  3568.         }
  3569.         console.timeEnd('gal');
  3570. }
  3571.  
  3572. function galleryChange(direction){
  3573.         if ($('#gallery').filter(':visible').length) {
  3574.                 if (direction == "left") {
  3575.                         if (imgIndex === 0) {
  3576.                                 imgIndex = $('.thread_image_box').length - 1;
  3577.                         } else {
  3578.                                 imgIndex--;
  3579.                         }
  3580.                 } else if (direction == "right") {
  3581.                         if (imgIndex == $('.thread_image_box').length - 1) {
  3582.                                 imgIndex = 0;
  3583.                         } else {
  3584.                                 imgIndex++;
  3585.                         }
  3586.                 } else {
  3587.                         console.log("Something went wrong boss.");
  3588.                 }
  3589.                 galleryUpdate();
  3590.         }
  3591. }
  3592.  
  3593. function galleryUpdate(){
  3594.         if ($('#gallery').length) {
  3595.                 var imgList = $('.thread_image_box');
  3596.                 if ($(imgList[imgIndex]).find('img').length) {
  3597.                         $('#gallery').html('<img style="max-width:90%; max-height:90%;" src="' + $(imgList[imgIndex]).find('img')[0].src + '">');
  3598.                 } else if ($(imgList[imgIndex]).find('video').length) {
  3599.                         $('#gallery').html('<video style="float:left; max-width:90%; max-height:90%;" name="media" loop muted controls ' + autoplayVid + '><source src="' + $(imgList[imgIndex]).find('video')[0].currentSrc + '" type="video/webm"></video>');
  3600.                 } else {
  3601.                         console.log("Oh boy something gone wrong again!");
  3602.                         console.log($(imgList[imgIndex]));
  3603.                 }
  3604.                 $(document).scrollTop($(imgList[imgIndex]).find('img, video').offset().top - 26);
  3605.         }
  3606. }
  3607.  
  3608. function populateSettingsMenu(){
  3609.         if ($('#settingsMenu').is(":visible")) {
  3610.                 $('#settingsMenu').hide();
  3611.         } else {
  3612.                 if (localStorage.SpookyXsettings !== undefined) {
  3613.                         $.extend(true, settings, JSON.parse(localStorage.SpookyXsettings));
  3614.                 }
  3615.                 var settingsHTML = '<div id="Main">' + generateSubOptionHTML(settings.UserSettings, '') + '</div>';
  3616.                 settingsHTML += '<div id="Filter">' + generateFilterHTML() + '</div>';
  3617.                 $('#settingsContent').html(settingsHTML);
  3618.                 if (!settings.UserSettings.filter.value) {
  3619.                         $('#filterDisabledMessage').show(); // Show filter disabled message if the filter is disabled
  3620.                 }
  3621.                 $('#settingsMenu').show(); // Show the menu
  3622.                 $('#settingsContent .suboption-list > :visible:last').addClass('last');
  3623.                 $('#settingsContent > div').hide(); //  Hide all tabs
  3624.                 $('#settingsContent #' + $('.sections-list .active').html()).show(); // Show active tab
  3625.                 $('#settingsContent select, #settingsContent input').each(function(i, el){
  3626.                         if (el.type !== "checkbox") { // Add the top margins for non-checkboxes to align description with name
  3627.                                 $(el).parent().next().addClass('selectDescription');
  3628.                         }
  3629.                         if (el.nodeName === "SELECT") { // Hide the settings join line for select options that start with one or less visible suboptions
  3630.                                 var visibleSubopsCount = 0;
  3631.                                 $(el).closest('div:not(.settingFlexContainer)').children('.suboption-list').children().each(function(i, subop){
  3632.                                         if ($(subop).css('display') !== "none") {
  3633.                                                 visibleSubopsCount++;
  3634.                                         }
  3635.                                 });
  3636.                                 if (visibleSubopsCount <= 1) {
  3637.                                         $(el).closest('.settingFlexContainer').children('.settingsJoinLine').hide();
  3638.                                 }
  3639.                         }
  3640.                 });
  3641.                 updateExportLink(); // Create the export link
  3642.         }
  3643. }
  3644.  
  3645. function generateFilterHTML(){
  3646.         var settingsHTML = '';
  3647.         var type;
  3648.         settingsHTML += '<div class="filters-list"><a href="javascript:;" class="active" name="guide">Guide</a>';
  3649.         for (type in settings.FilterSettings) {
  3650.                 if (settings.FilterSettings.hasOwnProperty(type)) {
  3651.                         settingsHTML += ' | <a href="javascript:;" name="' + type + '">' + settings.FilterSettings[type].name + '</a>';
  3652.                 }
  3653.         }
  3654.         settingsHTML += '</div>';
  3655.         settingsHTML += '<div id="filter_guide" style="padding:4px;"><div id="filterDisabledMessage" style="color: #d14; background-color: #f7f7f9; text-align: center; font-size: 15px; display:none;">The Filter is currently disabled. Turn it on via the setting in the Main tab</div><p>Use <a href="" style="text-decoration: underline;">regular expressions</a>, one per line. <br>Lines starting with a <code>#</code> will be ignored. <br>For example, <code>/weeaboo/i</code> will filter posts containing the string <code>weeaboo</code>, case-insensitive. <br><br>You can use these settings with each regular expression, separate them with semicolons: </p><ul> <li>Per boards, separate them with commas. It is global if not specified. <br>For example: <code>boards:a,tg;</code></li><li> Set the way the filter will handle the post with <code>mode</code><br>For example: <code>mode:hide;</code><br>Valid options are: <ul> <li><code>purge</code>: Remove the post from the page entirely, the site will need to reload the post for hoverlinks and such to work.</li><li><code>remove</code>: Remove the post from view but leave it in the page.</li><li><code>hide</code>: Collapse the post, leave a button to restore it.</li><li><code>fade</code>: Simply halve the opacity of the post. This is the default if the mode isn\'t specified.</li></ul> </li></ul></div>';
  3656.         for (type in settings.FilterSettings) {
  3657.                 if (settings.FilterSettings.hasOwnProperty(type)) {
  3658.                         settingsHTML += '<div id="filter_' + type + '" style="display: none;"><textarea name="' + type + '" spellcheck="false" class="filterTextarea">';
  3659.                         $.each(settings.FilterSettings[type].value, function(i, line){
  3660.                                 if (i) {
  3661.                                         settingsHTML += '\n';
  3662.                                 }
  3663.                                 if (!line.comment) {
  3664.                                         if (line.regex !== undefined && line.regex.pattern !== undefined) {
  3665.                                                 settingsHTML += "/" + line.regex.pattern + "/" + line.regex.flag + ';';
  3666.                                                 for (var prop in line) {
  3667.                                                         if (line.hasOwnProperty(prop) && prop !== "comment" && prop !== "regex") {
  3668.                                                                 settingsHTML += prop + ':' + line[prop] + ';';
  3669.                                                         }
  3670.                                                 }
  3671.                                         }
  3672.                                 } else {
  3673.                                         settingsHTML += line.comment;
  3674.                                 }
  3675.                         });
  3676.                         settingsHTML += '</textarea></div>';
  3677.                 }
  3678.         }
  3679.         return settingsHTML;
  3680. }
  3681.  
  3682. function generateSubOptionHTML(input, path){
  3683.         var settingsHTML = '';
  3684.         $.each(input, function(key, value){
  3685.                 if ( !== undefined) {
  3686.                         var checked = '';
  3687.                         var subOpsHidden = '';
  3688.                         if (value.value) {
  3689.                                 checked = ' checked';
  3690.                         } else {
  3691.                                 subOpsHidden = ' style="display: none;"';
  3692.                         }
  3693.                         if (value.if !== undefined) {
  3694.                                 var parentPath = objpath(settings.UserSettings, path.substring(0, path.length - '.suboptions.'.length));
  3695.                                 var pattTest = new RegExp(parentPath.value.value);
  3696.                                 var ifMet = false;
  3697.                                 $.each(value.if, function(i, v){
  3698.                                         if (pattTest.test(v)) {
  3699.                                                 ifMet = true;
  3700.                                                 return false;
  3701.                                         }
  3702.                                 });
  3703.                                 if (ifMet) {
  3704.                                         settingsHTML += '<div>';
  3705.                                 } else {
  3706.                                         settingsHTML += '<div style="display:none;">';
  3707.                                 }
  3708.                         } else {
  3709.                                 settingsHTML += '<div>';
  3710.                         }
  3711.                         settingsHTML += '<div class="settingFlexContainer">';
  3712.                         if (value.suboptions !== undefined && Object.keys(value.suboptions).length > 1) {
  3713.                                 settingsHTML += '<div class="settingsJoinLine';
  3714.                                 if (value.type === "checkbox") {
  3715.                                         settingsHTML += ' settingsJoinLineCheckbox';
  3716.                                 }
  3717.                                 settingsHTML += '"' + subOpsHidden + '></div>';
  3718.                         }
  3719.                         settingsHTML += '<label>';
  3720.                         switch (value.type) {
  3721.                                 case "checkbox":
  3722.                                         settingsHTML += '<input type="checkbox" name="' +  + '" key="' + key + '"' + checked + ' path="' + path + key + '">';
  3723.                                         break;
  3724.                                 case "text":
  3725.                                         settingsHTML += '<input type="text" name="' +  + '" key="' + key + '" value="' + value.value + '" path="' + path + key + '">';
  3726.                                         break;
  3727.                                 case "number":
  3728.                                         settingsHTML += '<input type="number" name="' +  + '" key="' + key + '" value="' + value.value + '" path="' + path + key + '">';
  3729.                                         break;
  3730.                                 case "select":
  3731.                                         settingsHTML += '<select name="' +  + '" key="' + key + '" path="' + path + key + '">';
  3732.                                         $.each(value.value.options, function(i, v){
  3733.                                                 settingsHTML += '<option';
  3734.                                                 if (v == value.value.value) {
  3735.                                                         settingsHTML += ' selected';
  3736.                                                 }
  3737.                                                 settingsHTML += '>' + v + '</option>';
  3738.                                         });
  3739.                                         settingsHTML += '</select>';
  3740.                                         break;
  3741.                         }
  3742.                         settingsHTML +=  + ': </label><span class="description">' + value.description + '</span></div>';
  3743.                         if (value.suboptions !== undefined) {
  3744.                                 settingsHTML += '<div class="suboption-list"' + subOpsHidden + '>' + generateSubOptionHTML(value.suboptions, path + key + '.suboptions.') + '</div>';
  3745.                         }
  3746.                         settingsHTML += '</div>';
  3747.                 }
  3748.         });
  3749.         return settingsHTML;
  3750. }
  3751.  
  3752. function settingsStrip(input){
  3753.         var obj = {};
  3754.         $.each(input, function(key, value){
  3755.                 obj[key] = {};
  3756.                 obj[key].value = value.value;
  3757.                 if (value.suboptions !== undefined) {
  3758.                         obj[key].suboptions = settingsStrip(value.suboptions);
  3759.                 }
  3760.         });
  3761.         return obj;
  3762. }
  3763.  
  3764. $(function(){
  3765.         shortcut.add("ctrl+s", function(){
  3766.                 executeShortcut("spoiler");
  3767.         });
  3768.         shortcut.add("ctrl+i", function(){
  3769.                 executeShortcut("i");
  3770.         });
  3771.         shortcut.add("ctrl+b", function(){
  3772.                 executeShortcut("b");
  3773.         });
  3774.         shortcut.add("ctrl+u", function(){
  3775.                 executeShortcut("u");
  3776.         });
  3777.         shortcut.add("q", function(){
  3778.                 quickReply();
  3779.         }, {"disable_in_input": true});
  3780.         shortcut.add("ctrl+q", function(){
  3781.                 quickReplyOptions();
  3782.         }, {"disable_in_input": false});
  3783.         shortcut.add("f", function(){
  3784.                 if (settings.UserSettings.favicon.value) {
  3785.                         canfav();
  3786.                 }
  3787.         }, {"disable_in_input": true});
  3788.         shortcut.add("g", function(){
  3789.                 if (.value) {
  3790.                         galleryToggle();
  3791.                 }
  3792.         }, {"disable_in_input": true});
  3793.         shortcut.add("left", function(){
  3794.                 if (.value) {
  3795.                         galleryChange("left");
  3796.                 }
  3797.         }, {"disable_in_input": true});
  3798.         shortcut.add("right", function(){
  3799.                 if (.value) {
  3800.                         galleryChange("right");
  3801.                 }
  3802.         }, {"disable_in_input": true});
  3803.         shortcut.add("o", function(){
  3804.                 populateSettingsMenu();
  3805.         }, {"disable_in_input": true});
  3806.         if (('thread')) {
  3807.                 seenPosts();
  3808.                 ThreadUpdate();
  3809.                 window.setInterval(function(){
  3810.                         ThreadUpdate();
  3811.                 }, 250);
  3812.         }
  3813. });
  3814.  
captcha