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) {