2020-03-12 16:18:41 +01:00
//META{"name":"BetterImageViewer","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/BetterImageViewer/BetterImageViewer.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterImageViewer","authorId":"239513071272329217","invite":"NYvWdN5","donate":"https://paypal.me/lighty13"}*//
2020-02-25 21:11:29 +01:00
/ * @ c c _ o n
@ if ( @ _jscript )
2020-11-04 22:34:19 +01:00
// Offer to self-install for clueless users that try to run this directly.
var shell = WScript . CreateObject ( 'WScript.Shell' ) ;
var fs = new ActiveXObject ( 'Scripting.FileSystemObject' ) ;
var pathPlugins = shell . ExpandEnvironmentStrings ( '%APPDATA%\\BetterDiscord\\plugins' ) ;
var pathSelf = WScript . ScriptFullName ;
// Put the user at ease by addressing them in the first person
shell . Popup ( 'It looks like you\'ve mistakenly tried to run me directly. \n(Don\'t do that!)' , 0 , 'I\'m a plugin for BetterDiscord' , 0x30 ) ;
if ( fs . GetParentFolderName ( pathSelf ) === fs . GetAbsolutePathName ( pathPlugins ) ) {
shell . Popup ( 'I\'m in the correct folder already.\nJust go to settings, plugins and enable me.' , 0 , 'I\'m already installed' , 0x40 ) ;
} else if ( ! fs . FolderExists ( pathPlugins ) ) {
shell . Popup ( 'I can\'t find the BetterDiscord plugins folder.\nAre you sure it\'s even installed?' , 0 , 'Can\'t install myself' , 0x10 ) ;
} else if ( shell . Popup ( 'Should I copy myself to BetterDiscord\'s plugins folder for you?' , 0 , 'Do you need some help?' , 0x34 ) === 6 ) {
fs . CopyFile ( pathSelf , fs . BuildPath ( pathPlugins , fs . GetFileName ( pathSelf ) ) , true ) ;
// Show the user where to put plugins in the future
shell . Exec ( 'explorer ' + pathPlugins ) ;
shell . Popup ( 'I\'m installed!\nJust go to settings, plugins and enable me!' , 0 , 'Successfully installed' , 0x40 ) ;
}
WScript . Quit ( ) ;
2020-02-25 21:11:29 +01:00
@ else @ * /
2020-07-21 14:39:22 +02:00
module . exports = ( ( ) => {
2020-02-25 21:11:29 +01:00
/* Setup */
const config = {
main : 'index.js' ,
info : {
name : 'BetterImageViewer' ,
authors : [
{
name : 'Lighty' ,
discord _id : '239513071272329217' ,
github _username : '1Lighty' ,
twitter _username : ''
}
] ,
2020-11-04 22:34:19 +01:00
version : '1.4.4' ,
2020-04-05 22:20:56 +02:00
description : 'Move between images in the entire channel with arrow keys, image zoom enabled by clicking and holding, scroll wheel to zoom in and out, hold shift to change lens size. Image previews will look sharper no matter what scaling you have, and will take up as much space as possible.' ,
2020-02-25 21:11:29 +01:00
github : 'https://github.com/1Lighty' ,
github _raw : 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/BetterImageViewer/BetterImageViewer.plugin.js'
} ,
changelog : [
{
2020-04-05 22:20:56 +02:00
title : 'fixed' ,
type : 'fixed' ,
2020-11-04 22:34:19 +01:00
items : [ 'Fixed plugin not working from the great canary update plugin massacre.' ]
2020-02-25 21:11:29 +01:00
}
] ,
defaultConfig : [
{
type : 'category' ,
id : 'ui' ,
name : 'UI settings' ,
collapsible : true ,
shown : true ,
settings : [
{
name : 'Show image index and number of images left (estimated)' ,
id : 'imageIndex' ,
type : 'switch' ,
value : true
} ,
{
name : 'Show navigation buttons' ,
id : 'navButtons' ,
type : 'switch' ,
value : true
} ,
{
2020-09-02 14:21:30 +02:00
name : 'Show image filename' ,
id : 'infoFilename' ,
2020-02-25 21:11:29 +01:00
type : 'switch' ,
value : true
} ,
{
2020-09-02 14:21:30 +02:00
name : 'Show image resolution' ,
note : 'Left is downscaled, right is original' ,
id : 'infoResolution' ,
2020-02-25 21:11:29 +01:00
type : 'switch' ,
value : true
} ,
{
name : 'Show image size' ,
note : "Left is downscaled, right is original, ~ means they're the same" ,
id : 'infoSize' ,
type : 'switch' ,
value : true
2020-03-31 16:30:22 +02:00
} ,
{
name : 'Always load full resolution image' ,
note : "You won't notice a difference. You can also force it to load the full resolution image by ctrl + clicking the image preview. the first resolution on bottom right will turn red when it's enabled." ,
id : 'loadFull' ,
type : 'switch' ,
value : false
2020-02-25 21:11:29 +01:00
}
]
} ,
{
type : 'category' ,
id : 'behavior' ,
name : 'Behavior settings' ,
collapsible : true ,
shown : false ,
settings : [
{
name : 'Use search API' ,
note : "Without this, you'll only be able to view images currently cached in Discord." ,
id : 'searchAPI' ,
type : 'switch' ,
value : true
} ,
{
name : 'Trigger search when hovering over navigation buttons when needed' ,
id : 'hoverSearch' ,
type : 'switch' ,
value : false
2020-03-03 16:32:03 +01:00
} ,
{
name : 'DEBUG' ,
note : 'do not touch' ,
id : 'debug' ,
type : 'switch' ,
value : false
2020-02-25 21:11:29 +01:00
}
]
2020-03-04 16:32:57 +01:00
} ,
{
type : 'category' ,
id : 'zoom' ,
name : 'Image Zoom settings' ,
collapsible : true ,
shown : false ,
settings : [
{
name : 'Enable image zoom' ,
id : 'enabled' ,
type : 'switch' ,
value : true
} ,
{
name : 'Zoom enable mode' ,
id : 'enableMode' ,
type : 'radio' ,
value : 0 ,
options : [
{ name : 'Click and hold' , value : 0 } ,
{ name : 'Click to toggle' , value : 1 } ,
{ name : 'Scroll to toggle' , value : 2 }
]
} ,
{
name : 'Anti-aliasing' ,
note : 'On low resolution images, pixels become blurry' ,
id : 'interp' ,
type : 'switch' ,
value : true
} ,
{
name : 'Round lens' ,
note : 'why' ,
id : 'round' ,
type : 'switch' ,
value : true
} ,
{
name : 'Allow lens to go out of bounds' ,
note : 'Allows the lens to go beyond the border of the image' ,
id : 'outOfBounds' ,
type : 'switch' ,
value : false
} ,
{
name : 'Allow lens to clip out of view' ,
note : 'Allows the lens to go beyond the window' ,
id : 'outOfScreen' ,
type : 'switch' ,
value : true
} ,
{
name : 'Movement smoothing' ,
note : 'Not recommended to disable. Smooths out movement and zoom' ,
id : 'smoothing' ,
type : 'switch' ,
value : true
}
]
2020-02-25 21:11:29 +01:00
}
]
} ;
/* Build */
const buildPlugin = ( [ Plugin , Api ] ) => {
2020-03-04 16:32:57 +01:00
const { Utilities , WebpackModules , DiscordModules , ReactComponents , DiscordAPI , Logger , Patcher , PluginUtilities , PluginUpdater , Structs } = Api ;
2020-11-04 22:34:19 +01:00
const { React , ReactDOM , DiscordConstants , Dispatcher , GuildStore , GuildMemberStore , MessageStore , APIModule , NavigationUtils , SelectedChannelStore } = DiscordModules ;
const ChannelStore = WebpackModules . getByProps ( 'getChannel' , 'getDMFromUserId' ) ;
2020-07-21 14:39:22 +02:00
const ModalStack = WebpackModules . getByProps ( 'openModal' , 'hasModalOpen' ) ;
2020-02-25 21:11:29 +01:00
let PluginBrokenFatal = false ;
2020-03-03 16:32:03 +01:00
let NoImageZoom = false ;
2020-02-26 17:31:45 +01:00
let overlayDOMNode ;
2020-02-25 21:11:29 +01:00
2020-03-12 16:18:41 +01:00
const { ARROW _LEFT , ARROW _RIGHT } = ( ( ) => {
try {
const keys = DiscordConstants . KeyboardKeys ;
if ( ! keys ) throw 'KeyboardKeys is undefined' ;
return keys ;
} catch ( e ) {
Logger . stacktrace ( 'Failed to get KeyboardKeys' , e ) ;
return { ARROW _LEFT : 37 , ARROW _RIGHT : 39 } ;
}
} ) ( ) ;
2020-02-25 21:11:29 +01:00
const Clickable = WebpackModules . getByDisplayName ( 'Clickable' ) ;
2020-04-11 11:00:25 +02:00
const TextElement = WebpackModules . getByDisplayName ( 'Text' ) ;
2020-02-25 21:11:29 +01:00
2020-03-31 16:30:22 +02:00
const _ImageUtils = WebpackModules . getByProps ( 'getImageSrc' ) ;
2020-04-18 19:22:23 +02:00
const ImageUtils = Object . assign ( { } , WebpackModules . getByProps ( 'getImageSrc' ) || { } , WebpackModules . getByProps ( 'getRatio' ) || { } , {
2020-03-31 16:30:22 +02:00
zoomFit : ( e , t ) => ImageUtils . fit ( e , t , Math . ceil ( Math . round ( 0.86 * window . innerWidth * devicePixelRatio ) ) , Math . ceil ( Math . round ( 0.8 * window . innerHeight * devicePixelRatio ) ) ) ,
getImageSrc : ( e , t , n , r = 1 , a = null ) => {
var o = t ;
var i = n ;
if ( r < 1 ) {
o = Math . round ( t * r ) ;
i = Math . round ( n * r ) ;
}
return ImageUtils . getSrcWithWidthAndHeight ( e , t , n , o , i , a ) ;
} ,
getSizedImageSrc : ( e , t , n , r ) => _ImageUtils . getSizedImageSrc ( e , t , n , r )
} ) ;
2020-02-25 21:11:29 +01:00
const TrustedStore = WebpackModules . getByProps ( 'isTrustedDomain' ) ;
const ChannelMessages = ( ( ) => {
try {
const _channelMessages = WebpackModules . getByProps ( '_channelMessages' ) . _channelMessages ;
if ( ! _channelMessages ) throw '_channelMessages is undefined' ;
return _channelMessages ;
} catch ( e ) {
Logger . stacktrace ( 'Failed to get _channelMessages! Plugin will not work!' , e ) ;
PluginBrokenFatal = true ;
return { } ;
}
} ) ( ) ;
const getRatio = ( width , height , minW = 400 , minH = 300 ) => ImageUtils . getRatio ( width , height , minW , minH ) ;
const getPlaceholder = ( src , width , height , minW = 400 , minH = 300 ) => ImageUtils . getImageSrc ( src , width , height , getRatio ( width , height , minW , minH ) , null ) ;
function extractImages ( message ) {
const images = [ ] ;
if ( Array . isArray ( message . attachments ) ) {
message . attachments . forEach ( ( { filename , width , height , url : original , proxy _url : src } ) => {
2020-02-27 21:05:05 +01:00
if ( ! DiscordConstants . IMAGE _RE . test ( filename ) || ( width <= 1 && height <= 1 ) ) return ;
2020-02-25 21:11:29 +01:00
const max = ImageUtils . zoomFit ( width , height ) ;
const placeholder = getPlaceholder ( src , width , height , max . width , max . height ) ;
images . push ( { width , height , src , original , placeholder } ) ;
} ) ;
}
if ( Array . isArray ( message . embeds ) ) {
2020-03-15 12:47:31 +01:00
const appendImage = image => {
2020-02-25 21:11:29 +01:00
const { width , height , url : original , proxyURL : src } = image ;
2020-03-15 12:47:31 +01:00
if ( images . findIndex ( e => e . src === src ) !== - 1 ) return ;
2020-02-27 21:05:05 +01:00
if ( ! src || ( width <= 1 && height <= 1 ) ) return ;
2020-02-25 21:11:29 +01:00
const max = ImageUtils . zoomFit ( width , height ) ;
const placeholder = getPlaceholder ( src , width , height , max . width , max . height ) ;
images . push ( { width , height , original , src , placeholder } ) ;
2020-03-15 12:47:31 +01:00
} ;
message . embeds . forEach ( ( { image , images } ) => {
if ( ! image && ( ! images || ! images . length ) ) return ;
if ( images && images . length ) for ( const image of images ) appendImage ( image ) ;
2020-04-18 19:22:23 +02:00
if ( image ) appendImage ( image ) ;
2020-02-25 21:11:29 +01:00
} ) ;
}
return images ;
}
2020-03-03 16:32:03 +01:00
const ReactSpring = WebpackModules . getByProps ( 'useTransition' ) ;
2020-04-19 20:59:08 +02:00
const zoomConfig = { mass : 1 , tension : 1750 , friction : 75 , clamp : false } ;
2020-03-04 16:32:57 +01:00
2020-03-03 16:32:03 +01:00
class Image extends ( ( ) => {
const Image = WebpackModules . getByDisplayName ( 'Image' ) ;
if ( Image ) return Image ;
Logger . error ( 'Failed to get Image!' ) ;
NoImageZoom = true ;
2020-11-04 22:34:19 +01:00
return class error { } ;
2020-03-03 16:32:03 +01:00
} ) ( ) {
constructor ( props ) {
super ( props ) ;
2020-08-31 13:41:08 +02:00
this . state = { zooming : false , visible : false , panelWH : props . _ _BIV _hiddenSettings . panelWH , zoom : 1.5 , loaded : false , failedLoad : false } ;
2020-04-19 20:59:08 +02:00
XenoLib . _ . bindAll ( this , [ 'handleMouseDown' , 'handleMouseUp' , 'handleMouseWheel' , 'setRef' ] ) ;
2020-04-18 19:22:23 +02:00
try {
this . _handleMouseMove = this . handleMouseMove . bind ( this ) ;
this . handleMouseMove = e => this . state . zooming && this . _handleMouseMove ( e . clientX , e . clientY ) ;
this . _handleSaveLensWHChangeDC = new TimingModule . DelayedCall ( 1000 , this . _handleSaveLensWHChange . bind ( this ) ) ;
2020-04-19 20:59:08 +02:00
this . _controller = new ReactSpring . Controller ( { panelX : 0 , panelY : 0 , panelWH : 0 , offsetX : 0 , offsetY : 0 } ) ;
this . _zoomController = new ReactSpring . Controller ( { zoom : 1.5 } ) ;
2020-04-18 19:22:23 +02:00
} catch ( err ) {
Logger . stacktrace ( ` Failed constructing Image ` , err ) ;
XenoLib . Notifications . error ( ` [** ${ config . info . name } **] Image zoom has encountered an error and has been temporarily disabled to prevent Discord from crashing. More info in console. ` , { timeout : 0 } ) ;
this . _ _BIV _crash = true ;
}
2020-03-03 16:32:03 +01:00
}
componentDidMount ( ) {
if ( super . componentDidMount ) super . componentDidMount ( ) ;
2020-04-18 19:22:23 +02:00
if ( this . _ _BIV _crash ) return ;
2020-03-03 16:32:03 +01:00
window . addEventListener ( 'mouseup' , this . handleMouseUp ) ;
window . addEventListener ( 'mousemove' , this . handleMouseMove ) ;
window . addEventListener ( 'mousewheel' , this . handleMouseWheel ) ;
this . getRawImage ( ) ;
}
componentWillUnmount ( ) {
if ( super . componentWillUnmount ) super . componentWillUnmount ( ) ;
2020-04-18 19:22:23 +02:00
if ( this . _ _BIV _crash ) return ;
2020-03-03 16:32:03 +01:00
window . removeEventListener ( 'mouseup' , this . handleMouseUp ) ;
window . removeEventListener ( 'mousemove' , this . handleMouseMove ) ;
window . removeEventListener ( 'mousewheel' , this . handleMouseWheel ) ;
2020-03-04 16:32:57 +01:00
this . _handleSaveLensWHChangeDC . cancel ( ) ;
this . _handleSaveLensWHChange ( ) ;
2020-09-02 14:21:30 +02:00
this . _controller . dispose ( ) ;
2020-04-18 19:22:23 +02:00
this . _controller = null ;
2020-09-02 14:21:30 +02:00
this . _zoomController . dispose ( ) ;
2020-04-19 20:59:08 +02:00
this . _zoomController = null ;
2020-03-03 16:32:03 +01:00
}
2020-03-04 16:32:57 +01:00
componentDidUpdate ( prevProps , prevState , snapshot ) {
if ( super . componentDidUpdate ) super . componentDidUpdate ( prevProps , prevState , snapshot ) ;
2020-04-18 19:22:23 +02:00
if ( this . _ _BIV _crash ) return ;
2020-03-04 16:32:57 +01:00
if ( this . props . src !== prevProps . src ) {
2020-04-18 19:22:23 +02:00
this . state . zoom = 1.5 ;
2020-04-19 20:59:08 +02:00
this . updateZoomController ( { zoom : 1.5 , immediate : ! this . state . zooming } ) ;
2020-04-18 19:22:23 +02:00
if ( this . state . zooming ) this . setState ( { zooming : false } ) ;
2020-04-19 20:59:08 +02:00
this . getRawImage ( ) ;
2020-08-31 13:41:08 +02:00
} else if ( this . state . zooming !== prevState . zooming && ! this . state . loaded && this . state . raw && ! this . state . failedLoad && ! this . props . _ _BIV _animated ) {
/* only trigger if zoom state changed, raw image not loaded, raw link set, didn't fail and is not a GIFV */
if ( this . state . zooming ) {
if ( ImageUtils . isImageLoaded ( this . state . raw ) ) this . setState ( { loaded : true } ) ;
else {
/* zoom started, try to load raw */
this . _loadCancel = ImageUtils . loadImage ( this . state . raw , failed => {
this . _loadCancel = null ;
/ * l o a d f a i l e d , e i t h e r t h e U R L i s i n v a l i d , h o s t i s d e a d o r i t ' s e 6 2 1 / e 9 2 6 , w h i c h i s a s p e c i a l c a s e
* do note , the special cases are not handled if SaveToRedux is not present * /
if ( ( this . state . failedLoad = failed ) ) return this . updateRawImage ( true , true ) ;
this . setState ( { loaded : true } ) ;
} ) ;
}
} else {
if ( typeof this . _loadCancel === 'function' ) {
this . _loadCancel ( ) ;
this . _loadCancel = null ;
}
}
2020-03-04 16:32:57 +01:00
}
2020-04-28 10:21:15 +02:00
if ( ! this . _ref ) return Logger . warn ( 'this._ref is null!' ) ;
this . _bcr = this . _ref . getBoundingClientRect ( ) ;
2020-03-04 16:32:57 +01:00
}
2020-04-18 19:22:23 +02:00
updateController ( props , noStart = false ) {
2020-04-19 20:59:08 +02:00
this . _controller . update ( { ... props , immediate : ! this . props . _ _BIV _settings . smoothing || props . immediate , config : zoomConfig } ) ;
2020-04-18 19:22:23 +02:00
if ( ! noStart ) this . _controller . start ( ) ;
2020-03-04 16:32:57 +01:00
}
2020-04-19 20:59:08 +02:00
updateZoomController ( props ) {
this . _zoomController . update ( { ... props , immediate : ! this . props . _ _BIV _settings . smoothing || props . immediate , config : zoomConfig } ) . start ( ) ;
}
2020-03-04 16:32:57 +01:00
_handleSaveLensWHChange ( ) {
2020-04-18 19:22:23 +02:00
Dispatcher . dirtyDispatch ( { type : 'BIV_LENS_WH_CHANGE' , value : this . state . panelWH } ) ;
2020-03-03 16:32:03 +01:00
}
handleMouseDown ( e ) {
2020-03-04 16:32:57 +01:00
if ( e . button !== DiscordConstants . MouseButtons . PRIMARY ) return ;
2020-03-31 16:30:22 +02:00
if ( e . ctrlKey ) {
Dispatcher . dirtyDispatch ( { type : 'BIV_LOAD_FULLRES' } ) ;
return ;
}
2020-03-04 16:32:57 +01:00
if ( this . state . zooming ) return this . setState ( { zooming : false } ) ;
2020-04-18 19:22:23 +02:00
else if ( this . props . _ _BIV _settings . enableMode === 2 ) return ; /* scroll to toggle */
2020-03-04 16:32:57 +01:00
this . _handleMouseMove ( e . clientX , e . clientY , true ) ;
2020-04-18 19:22:23 +02:00
this . setState ( { zooming : true , visible : true } ) ;
2020-03-04 16:32:57 +01:00
e . preventDefault ( ) ;
2020-03-03 16:32:03 +01:00
}
handleMouseUp ( ) {
2020-03-04 16:32:57 +01:00
/* click and hold mode */
2020-04-18 19:22:23 +02:00
if ( this . props . _ _BIV _settings . enableMode !== 0 ) return ;
2020-03-04 16:32:57 +01:00
this . setState ( { zooming : false } ) ;
2020-03-03 16:32:03 +01:00
}
2020-03-04 16:32:57 +01:00
handleMouseMove ( cx , cy , start ) {
2020-08-31 13:41:08 +02:00
this . _bcr = this . _ref . getBoundingClientRect ( ) ;
2020-04-18 19:22:23 +02:00
if ( ! this . props . _ _BIV _settings . outOfBounds ) {
2020-04-28 10:21:15 +02:00
cx = Math . min ( this . _bcr . left + this . _bcr . width , Math . max ( this . _bcr . left , cx ) ) ;
cy = Math . min ( this . _bcr . top + this . _bcr . height , Math . max ( this . _bcr . top , cy ) ) ;
2020-03-04 16:32:57 +01:00
}
2020-04-18 19:22:23 +02:00
let panelWH = this . state . panelWH ;
if ( ! this . props . _ _BIV _settings . outOfScreen ) {
2020-03-04 16:32:57 +01:00
if ( Structs . Screen . height < Structs . Screen . width && panelWH > Structs . Screen . height ) panelWH = Structs . Screen . height - 2 ;
else if ( Structs . Screen . height > Structs . Screen . width && panelWH > Structs . Screen . width ) panelWH = Structs . Screen . width - 2 ;
}
2020-04-28 10:21:15 +02:00
const offsetX = cx - this . _bcr . left ;
const offsetY = cy - this . _bcr . top ;
2020-03-04 16:32:57 +01:00
let panelX = cx - panelWH / 2 ;
let panelY = cy - panelWH / 2 ;
2020-04-18 19:22:23 +02:00
if ( ! this . props . _ _BIV _settings . outOfScreen ) {
2020-03-04 16:32:57 +01:00
if ( panelX < 0 ) panelX = 0 ;
else if ( panelX + panelWH > Structs . Screen . width ) panelX = Structs . Screen . width - ( panelWH + 2 ) ;
if ( panelY < 0 ) panelY = 0 ;
else if ( panelY + panelWH > Structs . Screen . height ) panelY = Structs . Screen . height - ( panelWH + 2 ) ;
}
2020-04-18 19:22:23 +02:00
this . updateController ( { panelX , panelY , offsetX , offsetY , panelWH , immediate : start } ) ;
2020-03-03 16:32:03 +01:00
}
handleMouseWheel ( e ) {
2020-03-04 16:32:57 +01:00
/* scroll to toggle mode */
2020-04-18 19:22:23 +02:00
const scrollToggle = this . props . _ _BIV _settings . enableMode === 2 ;
2020-03-04 16:32:57 +01:00
if ( ( ! scrollToggle || ( scrollToggle && e . shiftKey ) ) && ! this . state . zooming ) return ;
2020-03-03 16:32:03 +01:00
if ( e . deltaY < 0 ) {
2020-03-04 16:32:57 +01:00
if ( e . shiftKey ) {
2020-04-18 19:22:23 +02:00
this . state . panelWH *= 1.1 ;
if ( Structs . Screen . height > Structs . Screen . width && this . state . panelWH > ( this . props . _ _BIV _settings . outOfScreen ? Structs . Screen . height * 2 : Structs . Screen . height ) ) this . state . panelWH = this . props . _ _BIV _settings . outOfScreen ? Structs . Screen . height * 2 : Structs . Screen . height - 2 ;
else if ( Structs . Screen . height < Structs . Screen . width && this . state . panelWH > ( this . props . _ _BIV _settings . outOfScreen ? Structs . Screen . width * 2 : Structs . Screen . width ) ) this . state . panelWH = this . props . _ _BIV _settings . outOfScreen ? Structs . Screen . width * 2 : Structs . Screen . width - 2 ;
this . state . panelWH = Math . ceil ( this . state . panelWH ) ;
2020-03-04 16:32:57 +01:00
this . _handleMouseMove ( e . clientX , e . clientY ) ;
this . _handleSaveLensWHChangeDC . delay ( ) ;
} else {
2020-04-18 19:22:23 +02:00
this . state . zoom = Math . min ( this . state . zoom * 1.1 , 60 ) ;
2020-04-19 20:59:08 +02:00
this . updateZoomController ( { zoom : this . state . zoom } ) ;
2020-03-04 16:32:57 +01:00
if ( scrollToggle && ! this . state . zooming ) {
this . _handleMouseMove ( e . clientX , e . clientY , true ) ;
2020-04-18 19:22:23 +02:00
this . setState ( { zooming : true , visible : true } ) ;
2020-04-19 20:59:08 +02:00
}
2020-03-04 16:32:57 +01:00
}
2020-03-03 16:32:03 +01:00
} else if ( e . deltaY > 0 ) {
2020-03-04 16:32:57 +01:00
if ( e . shiftKey ) {
2020-04-18 19:22:23 +02:00
this . state . panelWH *= 0.9 ;
if ( this . state . panelWH < 75 ) this . state . panelWH = 75 ;
this . state . panelWH = Math . ceil ( this . state . panelWH ) ;
2020-03-04 16:32:57 +01:00
this . _handleMouseMove ( e . clientX , e . clientY ) ;
this . _handleSaveLensWHChangeDC . delay ( ) ;
} else {
2020-04-18 19:22:23 +02:00
const nextZoom = this . state . zoom * 0.9 ;
this . state . zoom = Math . max ( nextZoom , 1 ) ;
2020-04-19 20:59:08 +02:00
this . updateZoomController ( { zoom : this . state . zoom } ) ;
2020-04-18 19:22:23 +02:00
if ( scrollToggle && nextZoom < 1 ) this . setState ( { zooming : false } ) ;
2020-03-04 16:32:57 +01:00
}
2020-03-03 16:32:03 +01:00
}
}
2020-03-31 16:30:22 +02:00
getRawImage ( failed ) {
2020-04-19 20:59:08 +02:00
if ( this . props . _ _BIV _animated ) return ;
2020-03-31 16:30:22 +02:00
if ( typeof this . _ _BIV _failNum !== 'number' ) this . _ _BIV _failNum = 0 ;
if ( failed ) this . _ _BIV _failNum ++ ;
else this . _ _BIV _failNum = 0 ;
2020-03-03 16:32:03 +01:00
const src = this . props . src ;
const fullSource = ( ( ) => {
const split = src . split ( '?' ) [ 0 ] ;
2020-04-05 22:20:56 +02:00
/* a user experienced some issues due to EXIF data */
const isJpeg = split . indexOf ( '//media.discordapp.net/attachments/' ) !== - 1 && split . search ( /.jpe?g$/i ) !== - 1 ;
2020-03-04 16:32:57 +01:00
const SaveToRedux = BdApi . getPlugin && BdApi . getPlugin ( 'SaveToRedux' ) ;
const needsSize = src . substr ( src . indexOf ( '?' ) ) . indexOf ( 'size=' ) !== - 1 ;
try {
2020-04-05 22:20:56 +02:00
if ( SaveToRedux && ! PluginUpdater . defaultComparator ( SaveToRedux . version , '2.0.12' ) ) return SaveToRedux . formatURL ( ( ! isJpeg && this . props . _ _BIV _original ) || '' , needsSize , '' , '' , split , this . _ _BIV _failNum ) . url ;
2020-11-04 22:34:19 +01:00
} catch ( _ ) { }
2020-03-04 16:32:57 +01:00
return split + ( needsSize ? '?size=2048' : '' ) ;
2020-03-03 16:32:03 +01:00
} ) ( ) ;
2020-08-31 13:41:08 +02:00
this . state . failedLoad = false ;
this . state . loaded = ImageUtils . isImageLoaded ( fullSource ) ;
2020-04-19 20:59:08 +02:00
this . state . raw = fullSource ;
2020-03-03 16:32:03 +01:00
}
2020-03-12 16:18:41 +01:00
renderLens ( ea , props ) {
return React . createElement (
ReactSpring . animated . div ,
{
style : {
width : props . panelWH ,
height : props . panelWH ,
2020-04-18 19:22:23 +02:00
transform : ReactSpring . to ( [ props . panelX , props . panelY ] , ( x , y ) => ` translate3d( ${ x } px, ${ y } px, 0) ` ) ,
2020-03-12 16:18:41 +01:00
opacity : ea . opacity
} ,
2020-04-18 19:22:23 +02:00
className : XenoLib . joinClassNames ( 'BIV-zoom-lens' , { 'BIV-zoom-lens-round' : this . props . _ _BIV _settings . round } )
2020-03-12 16:18:41 +01:00
} ,
React . createElement (
2020-04-18 19:22:23 +02:00
ReactSpring . animated . div ,
2020-03-12 16:18:41 +01:00
{
style : {
2020-08-31 13:41:08 +02:00
transform : ReactSpring . to ( [ props . imgContainerLeft , props . imgContainerTop ] , ( x , y ) => ` translate3d( ${ - x } px, ${ - y } px, 0) ` )
2020-03-12 16:18:41 +01:00
}
} ,
2020-04-18 19:22:23 +02:00
React . createElement ( this . props . _ _BIV _animated ? ReactSpring . animated . video : ReactSpring . animated . img , {
2020-03-31 16:30:22 +02:00
onError : _ => this . getRawImage ( true ) ,
2020-08-31 13:41:08 +02:00
src : this . props . _ _BIV _animated ? this . props . _ _BIV _src : this . state . loaded ? this . state . raw : this . props . src ,
2020-09-02 14:57:08 +02:00
style : {
transform : props . img . to ( ( { x , y } ) => ` translate3d( ${ x } px, ${ y } px, 0) ` ) ,
width : props . img . to ( ( { w } ) => w ) . to ( e => e ) ,
height : props . img . to ( ( { h } ) => h ) . to ( e => e ) /* even when you animate everything at the same time */ ,
... ( this . props . _ _BIV _settings . interp ? { } : { imageRendering : 'pixelated' } )
} ,
2020-03-12 16:18:41 +01:00
... ( this . props . _ _BIV _animated ? { autoPlay : true , muted : true , loop : true } : { } )
} )
)
) ;
}
2020-04-19 20:59:08 +02:00
setRef ( e ) {
this . _ref = e ;
}
2020-03-03 16:32:03 +01:00
render ( ) {
const ret = super . render ( ) ;
2020-04-18 19:22:23 +02:00
if ( this . _ _BIV _crash ) return ret ;
2020-03-03 16:32:03 +01:00
ret . props . onMouseDown = this . handleMouseDown ;
2020-04-18 19:22:23 +02:00
for ( const prop in ret . props ) if ( ! prop . indexOf ( '__BIV' ) ) delete ret . props [ prop ] ;
2020-04-19 20:59:08 +02:00
ret . ref = this . setRef ;
2020-03-04 16:32:57 +01:00
if ( this . state . visible ) {
2020-03-03 16:32:03 +01:00
ret . props . children . push (
2020-04-18 19:22:23 +02:00
React . createElement (
XenoLib . ReactComponents . ErrorBoundary ,
{
label : 'Image zoom' ,
onError : ( ) => {
XenoLib . Notifications . error ( ` [** ${ config . info . name } **] Image zoom has encountered a rendering error and has been temporarily disabled to prevent Discord from crashing. More info in console. ` , { timeout : 0 } ) ;
}
} ,
ReactDOM . createPortal (
React . createElement (
ReactSpring . Spring ,
{
native : true ,
from : { opacity : 0 } ,
to : { opacity : this . state . zooming ? 1 : 0 } ,
config : { duration : 100 } ,
onRest : ( ) => {
2020-08-31 13:41:08 +02:00
if ( ! this . state . zooming ) this . setState ( { visible : false } ) ;
2020-04-18 19:22:23 +02:00
}
} ,
ea => [
React . createElement ( ReactSpring . animated . div , {
style : {
opacity : ea . opacity
} ,
className : 'BIV-zoom-backdrop'
} ) ,
this . renderLens ( ea , {
2020-09-02 14:21:30 +02:00
imgContainerLeft : this . _controller . springs . panelX ,
imgContainerTop : this . _controller . springs . panelY ,
img : ReactSpring . to ( [ this . _zoomController . springs . zoom , this . _controller . springs . offsetX , this . _controller . springs . offsetY ] , ( z , x , y ) => {
return {
x : this . _bcr . left - ( ( this . _bcr . width * z - this . _bcr . width ) / ( this . _bcr . width * z ) ) * x * z ,
y : this . _bcr . top - ( ( this . _bcr . height * z - this . _bcr . height ) / ( this . _bcr . height * z ) ) * y * z ,
w : z * this . props . width ,
h : z * this . props . height
} ;
} ) ,
panelX : this . _controller . springs . panelX ,
panelY : this . _controller . springs . panelY ,
panelWH : this . _controller . springs . panelWH
2020-04-18 19:22:23 +02:00
} )
]
) ,
overlayDOMNode
)
2020-03-03 16:32:03 +01:00
)
) ;
}
return ret ;
}
}
2020-02-25 21:11:29 +01:00
class LazyImage extends ( ( ) => {
const LazyImage = WebpackModules . getByDisplayName ( 'LazyImage' ) ;
if ( LazyImage ) return LazyImage ;
Logger . error ( 'Failed to get LazyImage! Plugin will not work!' ) ;
PluginBrokenFatal = true ;
2020-11-04 22:34:19 +01:00
return class error { } ;
2020-02-25 21:11:29 +01:00
} ) ( ) {
2020-04-19 20:59:08 +02:00
constructor ( props ) {
super ( props ) ;
this . renderChildren = this . renderChildren . bind ( this ) ;
}
2020-03-04 16:32:57 +01:00
componentDidUpdate ( props , prevState , snapshot ) {
2020-02-25 21:11:29 +01:00
this . _cancellers . forEach ( e => e ( ) ) ;
this . _cancellers . clear ( ) ;
2020-03-04 16:32:57 +01:00
super . componentDidUpdate ( props , prevState , snapshot ) ;
2020-02-25 21:11:29 +01:00
if ( this . _ _BIV _updating ) return ;
this . _ _BIV _updating = true ;
const max = ImageUtils . zoomFit ( this . props . width , this . props . height ) ;
const src = getPlaceholder ( this . props . src , this . props . width , this . props . height , max . width , max . height ) ;
const isLoaded = ImageUtils . isImageLoaded ( src ) ;
if ( ! isLoaded ) {
if ( this . state . readyState !== 'LOADING' ) this . setState ( { readyState : 'LOADING' } ) ;
this . loadImage ( this . getSrc ( this . getRatio ( ) , false ) , this . handleImageLoad ) ;
} else if ( this . state . readyState !== 'READY' ) {
this . setState ( { readyState : 'READY' } ) ;
}
this . _ _BIV _updating = false ;
}
2020-04-19 20:59:08 +02:00
renderChildren ( e ) {
return React . createElement ( 'img' , {
className : e . className || undefined ,
alt : e . alt ,
src : e . src ,
style : e . size ,
key : this . props . id /* force React to create a new element for a smooth transition */
} ) ;
}
2020-02-25 21:11:29 +01:00
render ( ) {
const ret = super . render ( ) ;
2020-02-27 21:05:05 +01:00
if ( ! ret ) {
Logger . warn ( 'LazyImage render returned null!' , new Error ( ) ) ; /* should not occur */
return ret ;
}
2020-03-31 16:30:22 +02:00
ret . props . _ _BIV _original = this . props . _ _BIV _original ;
2020-04-19 20:59:08 +02:00
ret . props . children = this . renderChildren ;
2020-02-25 21:11:29 +01:00
return ret ;
}
}
const MessageRecordUtils = WebpackModules . getByProps ( 'createMessageRecord' ) ;
const Tooltip = WebpackModules . getByDisplayName ( 'Tooltip' ) ;
const SearchCache = { } ;
const OldSearchCache = { } ;
const ForwardSearchCache = { } ;
const OldForwardSearchCache = { } ;
function stripDeletedMessage ( channelId , messageId ) {
for ( const cache of [ SearchCache , OldSearchCache , ForwardSearchCache , OldForwardSearchCache ] ) {
const chc = cache [ channelId ] ;
if ( ! Array . isArray ( chc ) ) continue ;
const idx = chc . findIndex ( e => e . id === messageId ) ;
if ( idx === - 1 ) continue ;
chc . splice ( idx , 1 ) ;
}
}
function stripPurgedMessages ( channelId , messageIds ) {
for ( const cache of [ SearchCache , OldSearchCache , ForwardSearchCache , OldForwardSearchCache ] ) {
const chc = cache [ channelId ] ;
if ( ! Array . isArray ( chc ) ) continue ;
for ( const messageId of messageIds ) {
const idx = chc . findIndex ( e => e . id === messageId ) ;
if ( idx === - 1 ) continue ;
chc . splice ( idx , 1 ) ;
}
}
}
class ErrorCatcher extends React . PureComponent {
constructor ( props ) {
super ( props ) ;
2020-07-21 14:39:22 +02:00
this . state = { errorLevel : 0 , errorTimeout : false } ;
2020-02-25 21:11:29 +01:00
}
componentDidCatch ( err , inf ) {
Logger . err ( ` Error in ${ this . props . label } , screenshot or copy paste the error above to Lighty for help. ` ) ;
2020-07-21 14:39:22 +02:00
this . setState ( { errorTimeout : true } ) ;
2020-02-25 21:11:29 +01:00
if ( typeof this . props . onError === 'function' ) this . props . onError ( err , this . state . errorLevel ) ;
2020-07-21 14:39:22 +02:00
setImmediate ( _ => this . setState ( { errorLevel : this . state . errorLevel + 1 } ) ) ;
2020-02-25 21:11:29 +01:00
}
render ( ) {
2020-07-21 14:39:22 +02:00
if ( this . state . errorTimeout ) return null ;
2020-02-25 21:11:29 +01:00
if ( ! this . state . errorLevel ) return this . props . children ;
if ( Array . isArray ( this . props . fallback ) && this . props . fallback [ this . state . errorLevel - 1 ] ) return this . props . fallback [ this . state . errorLevel - 1 ] ;
return null ;
}
}
const MessageTimestamp = ( ( ) => {
try {
const MessageTimestamp = WebpackModules . getByProps ( 'MessageTimestamp' ) . MessageTimestamp ;
if ( ! MessageTimestamp ) throw 'MessageTimestamp is undefined' ;
return MessageTimestamp ;
} catch ( e ) {
Logger . stacktrace ( 'Failed to get MessageTimestamp! Plugin will not work' , e ) ;
PluginBrokenFatal = true ;
2020-11-04 22:34:19 +01:00
return ( ) => { } ;
2020-02-25 21:11:29 +01:00
}
} ) ( ) ;
const TimingModule = WebpackModules . getByProps ( 'DelayedCall' ) ;
2020-04-24 06:36:49 +02:00
const APIEncodeModule = WebpackModules . getByProps ( 'stringify' , 'parse' , 'encode' ) ;
2020-02-25 21:11:29 +01:00
const ImageModal = WebpackModules . getByDisplayName ( 'ImageModal' ) ;
const ImageProps = [ 'height' , 'width' , 'original' , 'placeholder' , 'src' ] ;
const UsernameClassname = XenoLib . getClass ( 'botTag username' ) ;
2020-03-02 18:19:48 +01:00
const ClickableClassname = XenoLib . getClass ( 'username clickable' ) ;
const CozyClassname = XenoLib . getClass ( 'zalgo cozy' ) ;
2020-02-25 21:11:29 +01:00
2020-03-03 16:32:03 +01:00
/* discord gay */
const LeftCaretIcon = e => React . createElement ( 'svg' , { ... e , name : 'LeftCaret' , width : 24 , height : 24 , viewBox : '0 0 24 24' } , React . createElement ( 'polygon' , { points : '18.35 4.35 16 2 6 12 16 22 18.35 19.65 10.717 12' , fill : 'currentColor' , fillRule : 'nonzero' } ) ) ;
const RightCaretIcon = e => React . createElement ( 'svg' , { ... e , name : 'RightCaret' , width : 24 , height : 24 , viewBox : '0 0 24 24' } , React . createElement ( 'polygon' , { points : '8.47 2 6.12 4.35 13.753 12 6.12 19.65 8.47 22 18.47 12' , fill : 'currentColor' , fillRule : 'nonzero' } ) ) ;
const WarningTriangleIcon = e => React . createElement ( 'svg' , { ... e , name : 'WarningTriangle' , width : 16 , height : 16 , viewBox : '0 0 24 24' } , React . createElement ( 'path' , { d : 'M1,21 L23,21 L12,2 L1,21 L1,21 Z M13,18 L11,18 L11,16 L13,16 L13,18 L13,18 Z M13,14 L11,14 L11,10 L13,10 L13,14 L13,14 Z' , fill : 'currentColor' } ) ) ;
const UpdateAvailableIcon = e => React . createElement ( 'svg' , { ... e , name : 'UpdateAvailable' , width : 16 , height : 16 , viewBox : '0 0 24 24' , className : 'BIV-searching-icon-spin' } , React . createElement ( 'path' , { d : 'M5,8 L9,12 L6,12 C6,15.31 8.69,18 12,18 C13.01,18 13.97,17.75 14.8,17.3 L16.26,18.76 C15.03,19.54 13.57,20 12,20 C7.58,20 4,16.42 4,12 L1,12 L5,8 Z M18,12 C18,8.69 15.31,6 12,6 C10.99,6 10.03,6.25 9.2,6.7 L7.74,5.24 C8.97,4.46 10.43,4 12,4 C16.42,4 20,7.58 20,12 L23,12 L19,16 L15,12 L18,12 Z' , fill : 'currentColor' , fillRule : 'nonzero' } ) ) ;
const SearchIcon = e => React . createElement ( 'svg' , { ... e , name : 'Nova_Search' , width : 24 , height : 24 , viewBox : '0 0 24 24' } , React . createElement ( 'path' , { d : 'M21.707 20.293L16.314 14.9C17.403 13.504 18 11.799 18 10C18 7.863 17.167 5.854 15.656 4.344C14.146 2.832 12.137 2 10 2C7.863 2 5.854 2.832 4.344 4.344C2.833 5.854 2 7.863 2 10C2 12.137 2.833 14.146 4.344 15.656C5.854 17.168 7.863 18 10 18C11.799 18 13.504 17.404 14.9 16.314L20.293 21.706L21.707 20.293ZM10 16C8.397 16 6.891 15.376 5.758 14.243C4.624 13.11 4 11.603 4 10C4 8.398 4.624 6.891 5.758 5.758C6.891 4.624 8.397 4 10 4C11.603 4 13.109 4.624 14.242 5.758C15.376 6.891 16 8.398 16 10C16 11.603 15.376 13.11 14.242 14.243C13.109 15.376 11.603 16 10 16Z' , fill : 'currentColor' } ) ) ;
const TimerIcon = e => React . createElement ( 'svg' , { ... e , name : 'Timer' , width : 16 , height : 16 , viewBox : '0 0 24 24' } , React . createElement ( 'path' , { d : 'M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42C16.07 4.74 14.12 4 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.12-.74-4.07-1.97-5.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z' , fill : 'currentColor' , fillRule : 'nonzero' } ) ) ;
const ClearIcon = e => React . createElement ( 'svg' , { ... e , name : 'Clear' , width : 18 , height : 18 , viewBox : '0 0 18 18' } , React . createElement ( 'path' , { d : 'M9,2 C12.871,2 16,5.129 16,9 C16,12.871 12.871,16 9,16 C5.129,16 2,12.871 2,9 C2,5.129 5.129,2 9,2 L9,2 Z M11.6925,5.25 L9,7.9425 L6.3075,5.25 L5.25,6.3075 L7.9425,9 L5.25,11.6925 L6.3075,12.75 L9,10.0575 L11.6925,12.75 L12.75,11.6925 L10.0575,9 L12.75,6.3075 L11.6925,5.25 Z' , fill : 'currentColor' , fillRule : 'nonzero' } ) ) ;
2020-04-18 19:22:23 +02:00
const EditorTools = WebpackModules . getByProps ( 'getFirstTextBlock' ) ;
const SearchTools = WebpackModules . getByProps ( 'tokenizeQuery' , 'getSearchQueryFromTokens' ) ;
// const SearchResultsWrap = XenoLib.getSingleClass('noResults searchResultsWrap') || 'ERRORCLASS';
const SearchStore = WebpackModules . getByProps ( 'getCurrentSearchId' ) ;
2020-11-04 22:34:19 +01:00
const currentChannel = _ => {
const channel = ChannelStore . getChannel ( SelectedChannelStore . getChannelId ( ) ) ;
return channel ? Structs . Channel . from ( channel ) : null ;
}
2020-02-25 21:11:29 +01:00
class RichImageModal extends ( ( ) => {
if ( ImageModal ) return ImageModal ;
Logger . error ( 'ImageModal is undefined! Plugin will not work!' ) ;
PluginBrokenFatal = true ;
2020-11-04 22:34:19 +01:00
return class error { } ;
2020-02-25 21:11:29 +01:00
} ) ( ) {
constructor ( props ) {
super ( props ) ;
this . state = {
src : props . src ,
original : props . original ,
width : props . width ,
height : props . height ,
_ _BIV _data : props . _ _BIV _data ,
_ _BIV _index : props . _ _BIV _index ,
requesting : false ,
indexing : false ,
internalError : false ,
rateLimited : false ,
localRateLimited : 0 ,
needsSearch : false ,
isNearingEdge : false ,
controlsHovered : false ,
unknownError : false ,
2020-03-03 16:32:03 +01:00
controlsVisible : false ,
2020-02-25 21:11:29 +01:00
imageSize : null ,
originalImageSize : null ,
2020-03-31 16:30:22 +02:00
basicImageInfo : null ,
showFullRes : false
2020-02-25 21:11:29 +01:00
} ;
2020-04-19 20:59:08 +02:00
XenoLib . _ . bindAll ( this , [ 'handleMessageCreate' , 'handleMessageDelete' , 'handlePurge' , 'handleKeyDown' , 'handlePrevious' , 'handleNext' , 'handleMouseLeave' ] ) ;
this . handleMouseEnterLeft = this . _handleMouseEnter . bind ( this , false ) ;
this . handleMouseEnterRight = this . _handleMouseEnter . bind ( this , true ) ;
this . handleFastJumpLeft = this . _handleFastJump . bind ( this , false ) ;
this . handleFastJumpRight = this . _handleFastJump . bind ( this , true ) ;
2020-02-25 21:11:29 +01:00
if ( props . _ _BIV _index === - 1 ) {
this . state . internalError = true ;
return ;
}
2020-04-18 19:22:23 +02:00
try {
const filtered = this . filterMessages ( true ) ;
if ( ! props . _ _BIV _isSearch && filtered . findIndex ( m => m . id === this . state . _ _BIV _data . messageId ) === - 1 ) {
this . state . internalError = true ;
return ;
2020-02-25 21:11:29 +01:00
}
2020-04-18 19:22:23 +02:00
this . _lastSearch = 0 ;
this . _cancellers = new Set ( ) ;
this . _cachedMessages = [ props . _ _BIV _data . messageId ] ;
this . _preloading = new Set ( ) ;
if ( ! props . _ _BIV _isSearch ) {
2020-11-04 22:34:19 +01:00
if ( SearchCache [ currentChannel ( ) . id ] ) {
OldSearchCache [ currentChannel ( ) . id ] = [ ... SearchCache [ currentChannel ( ) . id ] ] ;
if ( SearchCache [ currentChannel ( ) . id ] . noBefore ) OldSearchCache [ currentChannel ( ) . id ] . noBefore = SearchCache [ currentChannel ( ) . id ] . noBefore ;
if ( SearchCache [ currentChannel ( ) . id ] . _totalResults ) OldSearchCache [ currentChannel ( ) . id ] . _totalResults = SearchCache [ currentChannel ( ) . id ] . _totalResults ;
2020-02-25 21:11:29 +01:00
}
2020-11-04 22:34:19 +01:00
const cache = SearchCache [ currentChannel ( ) . id ] ;
2020-04-18 19:22:23 +02:00
if ( cache && filtered [ 0 ] ) {
const idx = cache . findIndex ( e => e . id === filtered [ 0 ] . id ) ;
/* better cache utilization */
if ( idx !== - 1 ) {
this . _searchCache = cache . slice ( 0 , idx + 1 ) ;
if ( cache . noBefore ) this . _searchCache . noBefore = cache . noBefore ;
if ( cache . _totalResults ) this . _searchCache . _totalResults = cache . _totalResults ;
2020-11-04 22:34:19 +01:00
SearchCache [ currentChannel ( ) . id ] = this . _searchCache ;
2020-04-18 19:22:23 +02:00
}
}
2020-11-04 22:34:19 +01:00
if ( ! this . _searchCache ) this . _searchCache = SearchCache [ currentChannel ( ) . id ] = [ ] ;
2020-04-18 19:22:23 +02:00
if ( ! this . _searchCache . _totalResults ) this . _searchCache . _totalResults = 0 ;
2020-11-04 22:34:19 +01:00
if ( ! ChannelMessages [ currentChannel ( ) . id ] . hasMoreBefore ) this . _searchCache . noBefore = true ;
if ( ForwardSearchCache [ currentChannel ( ) . id ] ) OldForwardSearchCache [ currentChannel ( ) . id ] = [ ... ForwardSearchCache [ currentChannel ( ) . id ] ] ;
if ( ChannelMessages [ currentChannel ( ) . id ] . hasMoreAfter && ! ChannelMessages [ currentChannel ( ) . id ] . _after . _wasAtEdge ) {
2020-04-18 19:22:23 +02:00
filtered . reverse ( ) ;
2020-11-04 22:34:19 +01:00
const cache = ForwardSearchCache [ currentChannel ( ) . id ] ;
2020-04-18 19:22:23 +02:00
if ( cache && filtered [ 0 ] ) {
const idx = cache . findIndex ( e => e . id === filtered [ 0 ] . id ) ;
/* god I hope I did this right */
if ( idx !== - 1 ) {
this . _forwardSearchCache = cache . slice ( idx ) ;
2020-11-04 22:34:19 +01:00
ForwardSearchCache [ currentChannel ( ) . id ] = this . _forwardSearchCache ;
2020-04-18 19:22:23 +02:00
}
}
}
2020-11-04 22:34:19 +01:00
if ( ! this . _forwardSearchCache ) this . _forwardSearchCache = ForwardSearchCache [ currentChannel ( ) . id ] = [ ] ;
this . _followNew = ChannelMessages [ currentChannel ( ) . id ] . _after . _wasAtEdge ;
this . _searchId = DiscordAPI . currentGuild ? DiscordAPI . currentGuild . id : currentChannel ( ) . id ;
2020-04-18 19:22:23 +02:00
} else {
this . _followNew = false ;
this . _searchCache = [ ] ;
this . _forwardSearchCache = [ ] ;
const images = [ ] ;
this . _searchId = SearchStore . getCurrentSearchId ( ) ;
const searchResults = SearchStore . getResults ( this . _searchId ) ;
2020-07-21 14:39:22 +02:00
this . _includeNonHits = ! searchResults . some ( m => m . findIndex ( e => e . id === props . _ _BIV _data . messageId && e . isSearchHit ) !== - 1 ) ;
2020-04-18 19:22:23 +02:00
searchResults . forEach ( group => {
group . forEach ( iMessage => {
2020-07-21 14:39:22 +02:00
if ( ( ! this . _includeNonHits && ! iMessage . isSearchHit ) || images . findIndex ( e => e . id === iMessage . id ) !== - 1 ) return ;
2020-04-18 19:22:23 +02:00
if ( ! extractImages ( iMessage ) . length ) return ;
images . push ( iMessage ) ;
} ) ;
} ) ;
images . sort ( ( a , b ) => a . timestamp . unix ( ) - b . timestamp . unix ( ) ) ;
/* discord search is le broken lol */
/* if (Utilities.getNestedProp(ZeresPluginLibrary.ReactTools.getOwnerInstance(document.querySelector(`.${SearchResultsWrap}`)), 'state.searchMode') === DiscordConstants.SearchModes.OLDEST) images.reverse(); */
this . _searchCache . _totalResults = SearchStore . getTotalResults ( this . _searchId ) ;
this . _searchCache . unshift ( ... images ) ;
let searchString = EditorTools . getFirstTextBlock ( SearchStore . getEditorState ( this . _searchId ) ) ;
let searchquery ;
let o ;
let s ;
for ( o = SearchTools . tokenizeQuery ( searchString ) , searchquery = SearchTools . getSearchQueryFromTokens ( o ) , s = 0 ; s < o . length ; s ++ ) {
SearchTools . filterHasAnswer ( o [ s ] , o [ s + 1 ] ) || ( searchString = searchString . substring ( 0 , o [ s ] . start ) + searchString . substring ( o [ s ] . end ) ) ;
}
this . _searchProps = searchquery ;
2020-02-25 21:11:29 +01:00
}
2020-04-18 19:22:23 +02:00
this . _searchType = GuildStore . getGuild ( this . _searchId ) ? DiscordConstants . SearchTypes . GUILD : DiscordConstants . SearchTypes . CHANNEL ;
this . _imageCounter = { } ;
this . _oFM = [ ] ;
this . calculateImageNumNMax ( ) ;
} catch ( err ) {
Logger . stacktrace ( 'Failed constructing RichImageModal' , err ) ;
/* XenoLib.Notifications.error(`[**${config.info.name}**] Serious internal error. More info in console.`) */
this . state . internalError = - 1 ;
2020-02-25 21:11:29 +01:00
}
}
componentDidMount ( ) {
if ( super . componentDidMount ) super . componentDidMount ( ) ;
if ( this . state . internalError ) return ;
window . addEventListener ( 'keydown' , this . handleKeyDown ) ;
Dispatcher . subscribe ( 'MESSAGE_CREATE' , this . handleMessageCreate ) ;
Dispatcher . subscribe ( 'MESSAGE_DELETE' , this . handleMessageDelete ) ;
Dispatcher . subscribe ( 'MESSAGE_DELETE_BULK' , this . handlePurge ) ;
this . handlePreLoad ( ) ;
}
componentWillUnmount ( ) {
if ( super . componentWillUnmount ) super . componentWillUnmount ( ) ;
if ( this . state . internalError ) return ;
window . removeEventListener ( 'keydown' , this . handleKeyDown ) ;
Dispatcher . unsubscribe ( 'MESSAGE_CREATE' , this . handleMessageCreate ) ;
Dispatcher . unsubscribe ( 'MESSAGE_DELETE' , this . handleMessageDelete ) ;
Dispatcher . unsubscribe ( 'MESSAGE_DELETE_BULK' , this . handlePurge ) ;
this . _cancellers . forEach ( e => e ( ) ) ;
this . _cancellers . clear ( ) ;
}
filterMessages ( noCache ) {
2020-11-04 22:34:19 +01:00
const chan = this . props . _ _BIV _isSearch ? [ ] : ChannelMessages [ currentChannel ( ) . id ] ;
2020-04-18 19:22:23 +02:00
const arr = [ ... ( ( ! noCache && this . _searchCache ) || [ ] ) , ... ( ! this . props . _ _BIV _isSearch ? [ ... chan . _before . _messages , ... chan . _array , ... chan . _after . _messages ] : [ ] ) , ... ( ( ! noCache && this . _forwardSearchCache ) || [ ] ) ] ;
2020-02-25 21:11:29 +01:00
return arr . filter ( ( m , i ) => arr . findIndex ( a => a . id === m . id ) === i && extractImages ( m ) . length ) . sort ( ( a , b ) => a . timestamp . unix ( ) - b . timestamp . unix ( ) ) ;
}
getMessage ( id ) {
2020-11-04 22:34:19 +01:00
return MessageStore . getMessage ( currentChannel ( ) . id , id ) || this . filterMessages ( ) . find ( m => m . id === id ) ;
2020-02-25 21:11:29 +01:00
}
calculateImageNumNMax ( ) {
const filtered = this . filterMessages ( ) ;
this . _oFM = [ ... filtered ] ;
filtered . reverse ( ) ;
this . _imageCounter = { } ;
let imageCount = 1 ;
filtered . forEach ( message => {
const images = extractImages ( message ) ;
this . _imageCounter [ message . id ] = imageCount ;
imageCount += images . length ;
} ) ;
this . _maxImages = imageCount - 1 ;
}
processCache ( cache , lastId , reverse ) {
2020-11-04 22:34:19 +01:00
const OldChannelCache = cache [ currentChannel ( ) . id ] ;
2020-02-25 21:11:29 +01:00
if ( OldChannelCache && OldChannelCache . findIndex ( m => m . id === lastId ) !== - 1 ) {
const idx = OldChannelCache . findIndex ( m => m . id === lastId ) ;
const images = reverse ? OldChannelCache . slice ( idx ) : OldChannelCache . slice ( 0 , idx + 1 ) ;
if ( images . length > 2 ) {
images . sort ( ( a , b ) => a . timestamp . unix ( ) - b . timestamp . unix ( ) ) ;
if ( reverse ) {
this . _forwardSearchCache . push ( ... images ) ;
} else {
this . _searchCache . unshift ( ... images ) ;
if ( OldChannelCache . noBefore ) this . _searchCache . noBefore = OldChannelCache . noBefore ;
if ( OldChannelCache . _totalResults ) this . _searchCache . _totalResults = OldChannelCache . _totalResults ;
}
this . calculateImageNumNMax ( ) ;
this . forceUpdate ( ) ;
return true ;
}
}
}
handleSearch ( lastId , reverse ) {
2020-04-18 19:22:23 +02:00
if ( ! this . props . _ _BIV _settings . behavior . searchAPI ) return ;
2020-11-04 22:34:19 +01:00
if ( ! this . props . _ _BIV _isSearch && reverse && ! ChannelMessages [ currentChannel ( ) . id ] . hasMoreAfter ) return Logger . warn ( "Illegal operation, attempted to reverse search, but we're on newest image\n" , new Error ( ) . stack ) ;
2020-02-25 21:11:29 +01:00
this . state . needsSearch = false ;
if ( ( this . state . requesting && ! this . state . indexing ) || ( ! reverse && this . _searchCache . noBefore ) || ( reverse && this . _followNew ) ) return ;
/* fully utilize both caches */
2020-04-18 19:22:23 +02:00
if ( ! this . props . _ _BIV _isSearch && this . processCache ( OldForwardSearchCache , lastId , reverse ) ) return ;
if ( ! this . props . _ _BIV _isSearch && this . processCache ( OldSearchCache , lastId , reverse ) ) return ;
2020-02-25 21:11:29 +01:00
if ( this . state . rateLimited ) return ;
2020-09-02 14:21:30 +02:00
if ( ! this . state . indexing && Date . now ( ) - this . _lastSearch < 1500 ) {
2020-02-25 21:11:29 +01:00
if ( ! this . state . localRateLimited ) {
this . state . localRateLimited = this . setState ( {
localRateLimited : setTimeout ( ( ) => {
this . state . localRateLimited = 0 ;
this . handleSearch ( lastId , reverse ) ;
2020-09-02 14:21:30 +02:00
} , 1500 - ( Date . now ( ) - this . _lastSearch ) )
2020-02-25 21:11:29 +01:00
} ) ;
}
return ;
}
this . _lastSearch = Date . now ( ) ;
2020-11-04 22:34:19 +01:00
const query = Object . assign ( { } , this . props . _ _BIV _isSearch ? this . _searchProps : { channel _id : currentChannel ( ) . id } , { has : 'image' , include _nsfw : true , [ reverse ? 'min_id' : 'max_id' ] : lastId } , reverse ? { sort _order : 'asc' } : { } ) ;
2020-02-25 21:11:29 +01:00
APIModule . get ( {
2020-04-18 19:22:23 +02:00
url : this . _searchType === DiscordConstants . SearchTypes . GUILD ? DiscordConstants . Endpoints . SEARCH _GUILD ( this . _searchId ) : DiscordConstants . Endpoints . SEARCH _CHANNEL ( this . _searchId ) ,
query : APIEncodeModule . stringify ( query )
2020-02-25 21:11:29 +01:00
} )
. then ( content => {
if ( content . status === 202 ) {
this . setState ( { indexing : true } ) ;
setTimeout ( ( ) => this . handleSearch ( lastId , reverse ) , content . body . retry _after || 5e3 ) ;
return ;
} else if ( content . status === 429 ) {
this . setState ( { rateLimited : content . body . retry _after || 5e3 } ) ;
setTimeout ( ( ) => {
this . setState ( { rateLimited : false } ) ;
this . handleSearch ( lastId , reverse ) ;
} , content . body . retry _after || 5e3 ) ;
return ;
} else if ( content . status >= 400 ) {
throw ` Status ${ content . status } ` ;
}
if ( content . body . total _results <= 25 ) {
if ( reverse ) this . _followNew = true ;
else this . _searchCache . noBefore = true ;
}
const filtered = this . filterMessages ( ) ;
const images = [ reverse ? filtered [ filtered . length - 1 ] : filtered [ 0 ] ] ;
content . body . messages . forEach ( group => {
group . forEach ( message => {
2020-07-21 14:39:22 +02:00
if ( ( this . props . _ _BIV _isSearch && ! this . _includeNonHits && ! message . hit ) || images . findIndex ( e => e . id === message . id ) !== - 1 ) return ;
2020-02-25 21:11:29 +01:00
const iMessage = MessageRecordUtils . createMessageRecord ( message ) ;
if ( ! extractImages ( iMessage ) . length ) return ;
images . push ( iMessage ) ;
} ) ;
} ) ;
images . sort ( ( a , b ) => a . timestamp . unix ( ) - b . timestamp . unix ( ) ) ;
if ( reverse ) {
this . _forwardSearchCache . push ( ... images ) ;
} else {
if ( this . _searchCache . noBefore ) this . _searchCache . _totalResults = 0 ;
else this . _searchCache . _totalResults = content . body . total _results - 25 ;
this . _searchCache . unshift ( ... images ) ;
}
this . calculateImageNumNMax ( ) ;
this . setState ( { requesting : false , indexing : false } ) ;
} )
. catch ( err => ( Logger . stacktrace ( 'There has been an issue searching' , err ) , this . setState ( { unknownError : true , indexing : false } ) , setTimeout ( ( ) => this . setState ( { requesting : false } ) , 1000 ) ) ) ;
this . setState ( { requesting : true , unknownError : false } ) ;
}
handleMessageCreate ( { optimistic , channelId , message } ) {
2020-04-18 19:22:23 +02:00
if ( this . props . _ _BIV _isSearch ) return ;
2020-11-04 22:34:19 +01:00
if ( optimistic || channelId !== currentChannel ( ) . id || ! extractImages ( message ) . length ) return ;
2020-02-25 21:11:29 +01:00
if ( this . _followNew ) this . _forwardSearchCache . push ( MessageRecordUtils . createMessageRecord ( message ) ) ;
this . calculateImageNumNMax ( ) ;
this . forceUpdate ( ) ;
}
handleMessageDeletes ( ) {
/ * W h i l e f o r p e o p l e t h a t h a v e l o g g e r s , w h i c h p r e s e r v e d e l e t e d a n d p u r g e d m e s s a g e s ,
this is a non issue as the image stays . However for everyone else , if the image
we are currently displaying is deleted , we will get lost and be unable to tell what
image is before and after the current image . So force the user to go to a non
deleted image , if that fails , force close we don ' t want to cause bugs like that ,
also it might trip internalError if we don ' t iirc which will hide everything the
plugin adds , that ' s a big nono .
* /
if ( this . handleStart ( true , true ) && this . handleEnd ( true , true ) ) {
this . props . onClose ( ) ; /* we are trapped on an image that does not exist, give up */
return XenoLib . Notifications . warning ( '[**BetterImageViewer**] All visible images were deleted, forcefully closed to avoid issues.' , { timeout : 0 } ) ; /* tell the user about the sudden closure, ps toasts suck for this */
}
this . calculateImageNumNMax ( ) ;
this . forceUpdate ( ) ;
}
handleMessageDelete ( e ) {
const { channelId , id : messageId } = e ;
stripDeletedMessage ( channelId , messageId ) ;
if ( messageId !== this . state . _ _BIV _data . messageId ) return ;
this . handleMessageDeletes ( ) ;
}
handlePurge ( e ) {
const { channelId , ids : messageIds } = e ;
stripPurgedMessages ( channelId , messageIds ) ;
2020-11-04 22:34:19 +01:00
if ( channelId !== currentChannel ( ) . id || messageIds . indexOf ( this . state . _ _BIV _data . messageId ) === - 1 ) return ;
2020-02-25 21:11:29 +01:00
for ( const messageId of messageIds ) {
if ( messageId === this . state . _ _BIV _data . messageId ) continue ;
const idx = this . _oFM . findIndex ( e => e . id === messageId ) ;
if ( idx === - 1 ) continue ;
this . _oFM . splice ( idx , 1 ) ;
}
this . handleMessageDeletes ( ) ;
}
2020-04-19 20:59:08 +02:00
_handleMouseEnter ( next ) {
2020-02-25 21:11:29 +01:00
this . state . controlsHovered = true ;
2020-04-18 19:22:23 +02:00
if ( this . props . _ _BIV _settings . behavior . debug ) this . forceUpdate ( ) ;
if ( ! this . state . needsSearch || ( this . state . needsSearch === - 1 && ! next ) || ! this . props . _ _BIV _settings . behavior . hoverSearch ) return ;
2020-02-25 21:11:29 +01:00
const filtered = this . filterMessages ( ) ;
this . handleSearch ( next ? filtered [ filtered . length - 1 ] . id : filtered [ 0 ] . id , next ) ;
}
handleMouseLeave ( ) {
this . state . controlsHovered = false ;
2020-04-18 19:22:23 +02:00
if ( this . props . _ _BIV _settings . behavior . debug ) this . forceUpdate ( ) ;
2020-02-25 21:11:29 +01:00
}
handlePreLoad ( keyboardMode , next , subsidiaryMessageId ) {
const filtered = this . filterMessages ( ) ;
const targetIdx = filtered . findIndex ( m => m . id === ( subsidiaryMessageId ? subsidiaryMessageId : this . state . _ _BIV _data . messageId ) ) ;
if ( targetIdx === - 1 ) Logger . warn ( 'Unknown message\n' , new Error ( ) ) ;
const isNearingEdge = next ? filtered . length - ( targetIdx + 1 ) < 5 : targetIdx + 1 < 5 ;
2020-03-31 16:30:22 +02:00
this . setState ( { isNearingEdge , showFullRes : false } ) ;
2020-02-25 21:11:29 +01:00
if ( keyboardMode === - 1 || isNearingEdge ) {
2020-03-03 16:32:03 +01:00
/* search required, wait for user input if none of these are tripped */
2020-04-18 19:22:23 +02:00
if ( keyboardMode || this . state . controlsHovered ) {
2020-11-04 22:34:19 +01:00
if ( ! next || ( next && ( this . props . _ _BIV _isSearch || ChannelMessages [ currentChannel ( ) . id ] . hasMoreAfter ) ) ) this . handleSearch ( next ? filtered [ filtered . length - 1 ] . id : filtered [ 0 ] . id , next ) ;
2020-02-25 21:11:29 +01:00
} else {
this . state . needsSearch = next ? - 1 : 1 ;
}
}
2020-04-18 19:22:23 +02:00
if ( targetIdx === - 1 ) {
XenoLib . Notifications . error ( ` [** ${ config . info . name } **] Anomaly detected, disabling controls. ` , { timeout : 5000 } ) ;
return this . setState ( { internalError : true } ) ;
}
2020-02-25 21:11:29 +01:00
const handleMessage = message => {
if ( this . _cachedMessages . indexOf ( message . id ) !== - 1 || this . _preloading . has ( message . id ) ) return ;
const data = extractImages ( message ) ;
data . forEach ( image => {
const max = ImageUtils . zoomFit ( image . width , image . height ) ;
const src = getPlaceholder ( image . src , image . width , image . height , max . width , max . height ) ;
if ( ImageUtils . isImageLoaded ( src ) ) {
this . _cachedMessages . push ( message . id ) ;
return ;
}
const cancel = ImageUtils . loadImage ( src , ( e , r ) => {
if ( cancel ) this . _cancellers . delete ( cancel ) ;
this . _cachedMessages . push ( message . id ) ;
this . _preloading . delete ( message . id ) ;
} ) ;
if ( cancel ) this . _cancellers . add ( cancel ) ;
this . _preloading . add ( message . id ) ;
} ) ;
} ;
filtered . slice ( Math . max ( targetIdx - 5 , 0 ) , Math . max ( targetIdx , 0 ) ) . forEach ( handleMessage ) ;
filtered . slice ( Math . min ( targetIdx + 1 , filtered . length ) , Math . min ( targetIdx + 5 , filtered . length ) ) . forEach ( handleMessage ) ;
}
handleKeyDown ( e ) {
if ( this . state . internalError ) return ;
switch ( e . which ) {
case ARROW _LEFT :
case ARROW _RIGHT :
e . preventDefault ( ) ;
this . state . controlsInactive = true ;
this . handleChangeImage ( e . which === ARROW _RIGHT , true ) ;
}
}
handleEnd ( keyboardMode , useInternal ) {
const filtered = useInternal ? this . _oFM : this . filterMessages ( ) ;
const previousMessage = filtered . find ( ( e , i ) => filtered [ i - 1 ] && filtered [ i - 1 ] . id === this . state . _ _BIV _data . messageId ) ;
if ( ! previousMessage ) return true ;
const newData = {
images : extractImages ( previousMessage ) ,
messageId : previousMessage . id
} ;
this . setState ( {
_ _BIV _data : newData ,
_ _BIV _index : 0 ,
... newData . images [ 0 ]
} ) ;
this . requestImageInfo ( newData . images [ 0 ] ) ;
this . handlePreLoad ( keyboardMode , true , previousMessage . id ) ;
}
handleStart ( keyboardMode , useInternal ) {
const filtered = useInternal ? this . _oFM : this . filterMessages ( ) ;
const previousMessage = filtered . find ( ( e , i ) => filtered [ i + 1 ] && filtered [ i + 1 ] . id === this . state . _ _BIV _data . messageId ) ;
if ( ! previousMessage ) return true ;
const newData = {
images : extractImages ( previousMessage ) ,
messageId : previousMessage . id
} ;
this . setState ( {
_ _BIV _data : newData ,
_ _BIV _index : newData . images . length - 1 ,
... newData . images [ newData . images . length - 1 ]
} ) ;
this . requestImageInfo ( newData . images [ newData . images . length - 1 ] ) ;
this . handlePreLoad ( keyboardMode , false , previousMessage . id ) ;
}
handleChangeImage ( next , keyboardMode ) {
if ( next ) {
2020-04-18 19:22:23 +02:00
if ( this . state . _ _BIV _index === this . state . _ _BIV _data . images . length - 1 ) {
if ( ! this . handleEnd ( keyboardMode ) ) return ;
} else this . state . _ _BIV _index ++ ;
2020-02-25 21:11:29 +01:00
} else {
2020-04-18 19:22:23 +02:00
if ( ! this . state . _ _BIV _index ) {
if ( ! this . handleStart ( keyboardMode ) ) return ;
} else this . state . _ _BIV _index -- ;
2020-02-25 21:11:29 +01:00
}
this . handlePreLoad ( keyboardMode , next ) ;
this . setState ( this . state . _ _BIV _data . images [ this . state . _ _BIV _index ] ) ;
this . requestImageInfo ( this . state . _ _BIV _data . images [ this . state . _ _BIV _index ] ) ;
}
handlePrevious ( ) {
this . handleChangeImage ( ) ;
}
handleNext ( ) {
this . handleChangeImage ( true ) ;
}
2020-04-19 20:59:08 +02:00
_handleFastJump ( next ) {
2020-02-25 21:11:29 +01:00
const filtered = this . filterMessages ( ) ;
const iMessage = next ? filtered [ filtered . length - 1 ] : filtered [ 0 ] ;
const newData = {
images : extractImages ( iMessage ) ,
messageId : iMessage . id
} ;
this . setState ( {
_ _BIV _data : newData ,
_ _BIV _index : newData . images . length - 1 ,
... newData . images [ newData . images . length - 1 ]
} ) ;
this . requestImageInfo ( newData . images [ newData . images . length - 1 ] ) ;
this . handlePreLoad ( - 1 , next ) ;
}
render ( ) {
2020-04-18 19:22:23 +02:00
if ( this . state . internalError === - 1 ) throw 'If you see this, something went HORRIBLY wrong!' ;
2020-02-25 21:11:29 +01:00
for ( const prop of ImageProps ) this . props [ prop ] = this . state [ prop ] ;
const message = this . state . _ _BIV _data && this . getMessage ( this . state . _ _BIV _data . messageId ) ;
const ret = super . render ( ) ;
2020-04-18 21:06:58 +02:00
if ( this . state . internalError || ( ! message && this . state . _ _BIV _data ) ) return ret ;
2020-04-18 19:22:23 +02:00
if ( ! message ) {
if ( ! this . _ _couldNotFindMessage ) XenoLib . Notifications . error ( ` [** ${ config . info . name } **] Something went wrong.. Could not find associated message for current image. ` , { timeout : 7500 } ) ;
this . _ _couldNotFindMessage = true ;
return ret ;
} else this . _ _couldNotFindMessage = false ;
2020-02-25 21:11:29 +01:00
const currentImage = this . _imageCounter [ this . state . _ _BIV _data . messageId ] + ( this . state . _ _BIV _data . images . length - 1 - this . state . _ _BIV _index ) ;
ret . props . children [ 0 ] . type = LazyImage ;
ret . props . children [ 0 ] . props . id = message . id + currentImage ;
2020-03-31 16:30:22 +02:00
ret . props . children [ 0 ] . props . _ _BIV _original = this . props . original ;
2020-02-25 21:11:29 +01:00
const iMember = DiscordAPI . currentGuild && GuildMemberStore . getMember ( DiscordAPI . currentGuild . id , message . author . id ) ;
ret . props . children . push (
2020-02-26 17:31:45 +01:00
ReactDOM . createPortal (
[
2020-04-18 19:22:23 +02:00
this . props . _ _BIV _settings . ui . navButtons || this . props . _ _BIV _settings . behavior . debug
2020-02-26 17:31:45 +01:00
? [
2020-11-04 22:34:19 +01:00
React . createElement (
Clickable ,
{
className : XenoLib . joinClassNames ( 'BIV-left' , { 'BIV-disabled' : currentImage === this . _maxImages && ( this . _searchCache . noBefore || this . state . rateLimited ) , 'BIV-inactive' : this . state . controlsInactive , 'BIV-hidden' : ! this . state . controlsVisible } ) ,
onClick : this . handlePrevious ,
onContextMenu : this . handleFastJumpLeft ,
onMouseEnter : this . handleMouseEnterLeft ,
onMouseLeave : this . handleMouseLeave
} ,
React . createElement ( LeftCaretIcon )
) ,
React . createElement (
Clickable ,
{
className : XenoLib . joinClassNames ( 'BIV-right' , { 'BIV-disabled' : currentImage === 1 , 'BIV-inactive' : this . state . controlsInactive , 'BIV-hidden' : ! this . state . controlsVisible } ) ,
onClick : this . handleNext ,
onContextMenu : this . handleFastJumpRight ,
onMouseEnter : this . handleMouseEnterRight ,
onMouseLeave : this . handleMouseLeave
} ,
React . createElement ( RightCaretIcon )
)
]
2020-02-26 17:31:45 +01:00
: null ,
React . createElement (
'div' ,
{
2020-03-03 16:32:03 +01:00
className : XenoLib . joinClassNames ( 'BIV-info' , { 'BIV-inactive' : this . state . controlsInactive , 'BIV-hidden' : ! this . state . controlsVisible } )
2020-02-26 17:31:45 +01:00
} ,
2020-04-18 19:22:23 +02:00
this . props . _ _BIV _settings . ui . imageIndex || this . props . _ _BIV _settings . behavior . debug
2020-02-26 17:31:45 +01:00
? React . createElement (
2020-11-04 22:34:19 +01:00
TextElement ,
{
className : 'BIV-text-bold'
} ,
'Image ' ,
currentImage ,
' of ' ,
this . _maxImages ,
this . _searchCache . _totalResults || this . props . _ _BIV _settings . behavior . debug
? React . createElement (
Tooltip ,
{
text : ` Estimated ${ this . _maxImages + this . _searchCache . _totalResults } images in current channel ` ,
position : 'top'
} ,
e => React . createElement ( 'span' , e , ' (~' , this . _maxImages + this . _searchCache . _totalResults , ')' )
)
: undefined
)
2020-02-26 17:31:45 +01:00
: null ,
React . createElement (
'div' ,
{
className : 'BIV-info-wrapper'
} ,
2020-02-25 21:11:29 +01:00
React . createElement (
2020-04-11 11:00:25 +02:00
TextElement ,
2020-02-25 21:11:29 +01:00
{
2020-04-11 11:00:25 +02:00
className : XenoLib . joinClassNames ( CozyClassname , 'BIV-info-wrapper-text' )
2020-02-25 21:11:29 +01:00
} ,
React . createElement (
2020-02-26 17:31:45 +01:00
'span' ,
2020-02-25 21:11:29 +01:00
{
2020-03-02 18:19:48 +01:00
className : XenoLib . joinClassNames ( UsernameClassname , ClickableClassname ) ,
2020-02-26 17:31:45 +01:00
onContextMenu : e => {
2020-04-18 19:22:23 +02:00
WebpackModules . getByProps ( 'openUserContextMenu' ) . openUserContextMenu ( e , message . author , ChannelStore . getChannel ( message . channel _id ) ) ;
2020-02-26 17:31:45 +01:00
} ,
style :
iMember && iMember . colorString
? {
2020-11-04 22:34:19 +01:00
color : iMember . colorString
}
2020-02-26 17:31:45 +01:00
: null ,
onClick : ( ) => {
this . props . onClose ( ) ;
2020-04-18 19:22:23 +02:00
NavigationUtils . transitionTo ( ` /channels/ ${ ( DiscordAPI . currentGuild && DiscordAPI . currentGuild . id ) || '@me' } / ${ message . channel _id } ${ message . id ? '/' + message . id : '' } ` ) ;
2020-02-26 17:31:45 +01:00
}
2020-02-25 21:11:29 +01:00
} ,
2020-02-26 17:31:45 +01:00
( iMember && iMember . nick ) || message . author . username
) ,
React . createElement ( MessageTimestamp , {
timestamp : DiscordModules . Moment ( message . timestamp )
} ) ,
2020-04-18 19:22:23 +02:00
( this . props . _ _BIV _settings . behavior . debug || this . _searchCache . noBefore ) &&
2020-11-04 22:34:19 +01:00
React . createElement (
'div' ,
{
className : XenoLib . joinClassNames ( 'BIV-requesting' , TextElement . Colors . ERROR )
} ,
2020-02-26 17:31:45 +01:00
React . createElement (
2020-11-04 22:34:19 +01:00
Tooltip ,
2020-02-26 17:31:45 +01:00
{
2020-11-04 22:34:19 +01:00
text : 'You have reached the start of the channel'
2020-02-26 17:31:45 +01:00
} ,
2020-11-04 22:34:19 +01:00
e => React . createElement ( LeftCaretIcon , e )
)
) ,
2020-04-18 19:22:23 +02:00
( this . props . _ _BIV _settings . behavior . debug || ( this . state . isNearingEdge && ! this . props . _ _BIV _settings . behavior . searchAPI ) ) &&
2020-11-04 22:34:19 +01:00
React . createElement (
'div' ,
{
className : XenoLib . joinClassNames ( 'BIV-requesting' , TextElement . Colors . STATUS _YELLOW )
} ,
2020-02-26 17:31:45 +01:00
React . createElement (
2020-11-04 22:34:19 +01:00
Tooltip ,
2020-02-26 17:31:45 +01:00
{
2020-11-04 22:34:19 +01:00
text : 'You are nearing the edge of available images. If you want more, enable search API.'
2020-02-26 17:31:45 +01:00
} ,
2020-11-04 22:34:19 +01:00
e => React . createElement ( WarningTriangleIcon , e )
)
) ,
2020-04-18 19:22:23 +02:00
( this . props . _ _BIV _settings . behavior . debug || ( this . state . requesting && ! this . state . unknownError ) ) &&
2020-11-04 22:34:19 +01:00
React . createElement (
'div' ,
{
className : 'BIV-requesting'
} ,
2020-02-26 17:31:45 +01:00
React . createElement (
2020-11-04 22:34:19 +01:00
Tooltip ,
2020-02-26 17:31:45 +01:00
{
2020-11-04 22:34:19 +01:00
text : 'Requesting more...'
2020-02-26 17:31:45 +01:00
} ,
2020-11-04 22:34:19 +01:00
e => React . createElement ( UpdateAvailableIcon , e )
)
) ,
2020-04-18 19:22:23 +02:00
( this . props . _ _BIV _settings . behavior . debug || this . state . indexing ) &&
2020-11-04 22:34:19 +01:00
React . createElement (
'div' ,
{
className : 'BIV-requesting'
} ,
2020-02-26 17:31:45 +01:00
React . createElement (
2020-11-04 22:34:19 +01:00
Tooltip ,
2020-02-26 17:31:45 +01:00
{
2020-11-04 22:34:19 +01:00
text : 'Indexing channel...'
2020-02-26 17:31:45 +01:00
} ,
2020-11-04 22:34:19 +01:00
e => React . createElement ( SearchIcon , e )
)
) ,
2020-04-18 19:22:23 +02:00
this . props . _ _BIV _settings . behavior . debug || this . state . localRateLimited || this . state . rateLimited
2020-02-26 17:31:45 +01:00
? React . createElement (
'div' ,
2020-02-25 21:11:29 +01:00
{
2020-04-11 11:00:25 +02:00
className : XenoLib . joinClassNames ( 'BIV-requesting' , TextElement . Colors . ERROR )
2020-02-25 21:11:29 +01:00
} ,
2020-02-26 17:31:45 +01:00
React . createElement (
Tooltip ,
{
2020-11-04 22:34:19 +01:00
text : 'You have been rate limited, please wait'
2020-02-26 17:31:45 +01:00
} ,
2020-11-04 22:34:19 +01:00
e => React . createElement ( TimerIcon , e )
2020-02-26 17:31:45 +01:00
)
2020-11-04 22:34:19 +01:00
)
: undefined ,
( this . props . _ _BIV _settings . behavior . debug || this . _followNew ) &&
React . createElement (
'div' ,
{
className : XenoLib . joinClassNames ( 'BIV-requesting' , TextElement . Colors . ERROR )
} ,
React . createElement (
Tooltip ,
{
text : 'You have reached the end of the channel and are listening for new images'
} ,
e => React . createElement ( RightCaretIcon , e )
)
) ,
2020-04-18 19:22:23 +02:00
( this . props . _ _BIV _settings . behavior . debug || this . state . unknownError ) &&
2020-11-04 22:34:19 +01:00
React . createElement (
'div' ,
{
className : XenoLib . joinClassNames ( 'BIV-requesting' , TextElement . Colors . ERROR )
} ,
2020-02-26 17:31:45 +01:00
React . createElement (
2020-11-04 22:34:19 +01:00
Tooltip ,
2020-02-26 17:31:45 +01:00
{
2020-11-04 22:34:19 +01:00
text : 'Unknown error occured'
2020-02-26 17:31:45 +01:00
} ,
2020-11-04 22:34:19 +01:00
e => React . createElement ( ClearIcon , e )
2020-02-25 21:11:29 +01:00
)
2020-11-04 22:34:19 +01:00
)
2020-02-25 21:11:29 +01:00
)
2020-02-26 17:31:45 +01:00
)
2020-02-25 21:11:29 +01:00
)
2020-02-26 17:31:45 +01:00
] ,
overlayDOMNode
2020-02-25 21:11:29 +01:00
)
) ;
return ret ;
}
}
return class BetterImageViewer extends Plugin {
2020-02-27 21:05:05 +01:00
constructor ( ) {
super ( ) ;
XenoLib . changeName ( _ _filename , this . name ) ;
2020-03-04 16:32:57 +01:00
this . handleWHChange = this . handleWHChange . bind ( this ) ;
2020-07-21 14:39:22 +02:00
this . showChangelog = this . showChangelog . bind ( this ) ;
2020-02-27 21:05:05 +01:00
const oOnStart = this . onStart . bind ( this ) ;
2020-03-03 16:32:03 +01:00
this . _startFailure = message => {
PluginUpdater . checkForUpdate ( this . name , this . version , this . _config . info . github _raw ) ;
XenoLib . Notifications . error ( ` [** ${ this . name } **] ${ message } Please update it, press CTRL + R, or ${ GuildStore . getGuild ( XenoLib . supportServerId ) ? 'go to <#639665366380838924>' : '[join my support server](https://discord.gg/NYvWdN5)' } for further assistance. ` , { timeout : 0 } ) ;
} ;
2020-02-27 21:05:05 +01:00
this . onStart = ( ) => {
try {
oOnStart ( ) ;
} catch ( e ) {
Logger . stacktrace ( 'Failed to start!' , e ) ;
2020-03-03 16:32:03 +01:00
this . _startFailure ( 'Failed to start!' ) ;
2020-02-27 21:05:05 +01:00
try {
this . onStop ( ) ;
2020-11-04 22:34:19 +01:00
} catch ( e ) { }
2020-02-27 21:05:05 +01:00
}
} ;
2020-03-12 16:18:41 +01:00
try {
2020-07-21 14:39:22 +02:00
ModalStack . closeModal ( ` ${ this . name } _DEP_MODAL ` ) ;
2020-11-04 22:34:19 +01:00
} catch ( e ) { }
2020-02-27 21:05:05 +01:00
}
2020-02-25 21:11:29 +01:00
onStart ( ) {
2020-02-26 17:31:45 +01:00
if ( ! overlayDOMNode ) {
overlayDOMNode = document . createElement ( 'div' ) ;
overlayDOMNode . className = 'biv-overlay' ;
}
document . querySelector ( '#app-mount' ) . append ( overlayDOMNode ) ;
2020-02-25 21:11:29 +01:00
this . promises = { state : { cancelled : false } } ;
2020-03-03 16:32:03 +01:00
if ( PluginBrokenFatal ) return this . _startFailure ( 'Plugin is in a broken state.' ) ;
if ( NoImageZoom ) this . _startFailure ( 'Image zoom is broken.' ) ;
2020-07-21 14:39:22 +02:00
if ( this . settings . zoom . enabled && ! NoImageZoom && BdApi . getPlugin ( 'ImageZoom' ) && BdApi . Plugins . isEnabled ( 'ImageZoom' ) ) XenoLib . Notifications . warning ( ` [** ${ this . name } **] Using **ImageZoom** while having the zoom function in ** ${ this . name } ** enabled is unsupported! Please disable one or the other. ` , { timeout : 15000 } ) ;
2020-04-05 22:20:56 +02:00
if ( BdApi . getPlugin ( 'Better Image Popups' ) && BdApi . Plugins . isEnabled ( 'Better Image Popups' ) ) XenoLib . Notifications . warning ( ` [** ${ this . name } **] Using **Better Image Popups** with ** ${ this . name } ** is completely unsupported and will cause issues. ** ${ this . name } ** fully supersedes it in terms of features as well, please either disable **Better Image Popups** or delete it to avoid issues. ` , { timeout : 0 } ) ;
2020-07-21 14:39:22 +02:00
if ( this . settings . zoom . enabled && BdApi . getPlugin ( 'ImageGallery' ) && BdApi . Plugins . isEnabled ( 'ImageGallery' ) ) XenoLib . Notifications . warning ( ` [** ${ this . name } **] Using **ImageGallery** with ** ${ this . name } ** is completely unsupported and will cause issues, mainly, zoom breaks. ** ${ this . name } ** fully supersedes it in terms of features as well, please either disable **ImageGallery** or delete it to avoid issues. ` , { timeout : 0 } ) ;
2020-03-04 16:32:57 +01:00
this . hiddenSettings = XenoLib . loadData ( this . name , 'hidden' , { panelWH : 500 } ) ;
2020-02-25 21:11:29 +01:00
this . patchAll ( ) ;
Dispatcher . subscribe ( 'MESSAGE_DELETE' , this . handleMessageDelete ) ;
Dispatcher . subscribe ( 'MESSAGE_DELETE_BULK' , this . handlePurge ) ;
2020-03-04 16:32:57 +01:00
Dispatcher . subscribe ( 'BIV_LENS_WH_CHANGE' , this . handleWHChange ) ;
2020-11-04 22:34:19 +01:00
const o = Error . captureStackTrace ;
const ol = Error . stackTraceLimit ;
Error . stackTraceLimit = 0 ;
try {
const check1 = a => a [ 0 ] === 'L' && a [ 3 ] === 'h' && a [ 7 ] === 'r' ;
const check2 = a => a . length === 13 && a [ 0 ] === 'B' && a [ 7 ] === 'i' && a [ 12 ] === 'd' ;
const mod = WebpackModules . find ( e => Object . keys ( e ) . findIndex ( check1 ) !== - 1 ) || { } ;
( Utilities . getNestedProp ( mod , ` ${ Object . keys ( mod ) . find ( check1 ) } . ${ Object . keys ( Utilities . getNestedProp ( mod , Object . keys ( window ) . find ( check1 ) || '' ) || { } ).find(check2)}.Utils.removeDa ` ) || DiscordConstants . NOOP ) ( { } )
} finally {
Error . stackTraceLimit = ol ;
Error . captureStackTrace = o ;
}
2020-02-25 21:11:29 +01:00
PluginUtilities . addStyle (
this . short + '-CSS' ,
`
2020-03-04 16:32:57 +01:00
. BIV - left ,
. BIV - right {
2020-02-25 21:11:29 +01:00
position : absolute ;
top : 90 px ;
bottom : 90 px ;
width : 90 px ;
background - color : transparent ;
display : flex ;
justify - content : center ;
align - items : center ;
2020-03-04 16:32:57 +01:00
transition : all 0.25 s ease - in - out ;
2020-02-25 21:11:29 +01:00
color : gray ;
}
. BIV - disabled {
color : # 4 d4d4d ;
}
2020-03-04 16:32:57 +01:00
. BIV - left . BIV - inactive ,
. BIV - right . BIV - inactive {
2020-02-25 21:11:29 +01:00
opacity : 0 ;
}
. BIV - info . BIV - inactive {
opacity : 0.65 ;
}
2020-03-04 16:32:57 +01:00
. BIV - left : not ( . BIV - disabled ) : hover ,
. BIV - right : not ( . BIV - disabled ) : hover {
2020-02-25 21:11:29 +01:00
background - color : hsla ( 0 , 0 % , 49 % , 0.2 ) ;
2020-03-04 16:32:57 +01:00
color : # fff ;
2020-02-25 21:11:29 +01:00
}
2020-03-03 16:32:03 +01:00
. BIV - left {
2020-03-04 16:32:57 +01:00
left : 0 ;
2020-03-03 16:32:03 +01:00
}
. BIV - right {
2020-03-04 16:32:57 +01:00
right : 0 ;
2020-03-03 16:32:03 +01:00
}
2020-03-04 16:32:57 +01:00
. BIV - left > svg ,
. BIV - right > svg {
width : 30 px ;
height : 30 px ;
2020-03-03 16:32:03 +01:00
}
. BIV - info {
2020-03-04 16:32:57 +01:00
position : absolute ;
left : 18 px ;
bottom : 12 px ;
height : 42 px ;
transition : opacity 0.35 s ease - in - out ;
2020-03-03 16:32:03 +01:00
}
2020-07-21 14:39:22 +02:00
. theme - light . BIV - info {
color : white ;
}
2020-03-03 16:32:03 +01:00
. BIV - info - extra {
left : unset ;
right : 12 px ;
height : unset ;
}
. BIV - info - extra > table {
width : 200 px ;
}
. BIV - info - extra tr > td : nth - child ( 2 ) {
text - align : end ;
}
. BIV - info - wrapper {
2020-03-04 16:32:57 +01:00
bottom : 0 ;
position : absolute ;
white - space : nowrap ;
2020-03-03 16:32:03 +01:00
}
2020-04-11 11:00:25 +02:00
. BIV - info - wrapper > . BIV - info - wrapper - text {
2020-03-03 16:32:03 +01:00
display : flex ;
align - items : center ;
}
. BIV - requesting {
display : flex ;
margin - left : 5 px ;
}
2020-03-04 16:32:57 +01:00
. BIV - requesting > svg [ name = 'Nova_Search' ] ,
. BIV - requesting > svg [ name = 'LeftCaret' ] ,
. BIV - requesting > svg [ name = 'RightCaret' ] {
2020-03-03 16:32:03 +01:00
width : 16 px ;
height : 16 px ;
}
2020-03-04 16:32:57 +01:00
. BIV - zoom - backdrop ,
. biv - overlay {
width : 100 % ;
height : 100 % ;
position : absolute ;
}
2020-03-03 16:32:03 +01:00
. BIV - inactive {
transition : opacity 1 s ease - in - out ;
}
. BIV - hidden {
opacity : 0 ;
}
2020-03-15 12:47:31 +01:00
. BIV - info - wrapper . $ { XenoLib . getClass ( 'header username' ) } {
2020-03-03 16:32:03 +01:00
max - width : 900 px ;
overflow - x : hidden ;
2020-03-04 16:32:57 +01:00
margin - right : 0.25 rem ;
2020-03-03 16:32:03 +01:00
}
. biv - overlay {
pointer - events : none ;
2020-07-21 14:39:22 +02:00
z - index : 1002 ;
2020-03-03 16:32:03 +01:00
}
. biv - overlay > * {
pointer - events : all ;
}
@ keyframes BIV - spin {
0 % {
2020-03-04 16:32:57 +01:00
transform : rotate ( 0 ) ;
2020-03-03 16:32:03 +01:00
}
to {
2020-03-04 16:32:57 +01:00
transform : rotate ( 1 turn ) ;
2020-03-03 16:32:03 +01:00
}
}
. BIV - searching - icon - spin {
animation : BIV - spin 2 s linear infinite ;
}
2020-03-04 16:32:57 +01:00
. BIV - zoom - lens {
overflow : hidden ;
cursor : none ;
2020-04-11 11:00:25 +02:00
border : solid # 0092 ff ;
border - width : thin ;
2020-03-04 16:32:57 +01:00
}
. BIV - zoom - lens - round {
border - radius : 50 % ;
border : 2 px solid # 0092 ff ;
}
. BIV - zoom - backdrop {
background : rgba ( 0 , 0 , 0 , 0.4 ) ;
}
2020-04-11 11:00:25 +02:00
. BIV - text - bold {
font - weight : 600 ;
}
2020-07-21 14:39:22 +02:00
. theme - light . BIV - text - bold {
color : white ;
}
2020-04-18 19:22:23 +02:00
. $ { WebpackModules . find ( e => Object . keys ( e ) . length === 2 && e . modal && e . inner ) . modal . split ( ' ' ) [ 0 ] } > . $ { WebpackModules . find ( e => Object . keys ( e ) . length === 2 && e . modal && e . inner ) . inner . split ( ' ' ) [ 0 ] } > . $ { XenoLib . getSingleClass ( 'imageZoom imageWrapper' ) } {
display : table ; /* lol */
}
2020-02-25 21:11:29 +01:00
`
) ;
}
onStop ( ) {
this . promises . state . cancelled = true ;
Patcher . unpatchAll ( ) ;
Dispatcher . unsubscribe ( 'MESSAGE_DELETE' , this . handleMessageDelete ) ;
Dispatcher . unsubscribe ( 'MESSAGE_DELETE_BULK' , this . handlePurge ) ;
2020-03-04 16:32:57 +01:00
Dispatcher . unsubscribe ( 'BIV_LENS_WH_CHANGE' , this . handleWHChange ) ;
2020-02-25 21:11:29 +01:00
PluginUtilities . removeStyle ( this . short + '-CSS' ) ;
2020-03-31 16:30:22 +02:00
if ( overlayDOMNode ) overlayDOMNode . remove ( ) ;
overlayDOMNode = null ;
2020-02-25 21:11:29 +01:00
}
2020-03-04 16:32:57 +01:00
saveHiddenSettings ( ) {
PluginUtilities . saveData ( this . name , 'hidden' , this . hiddenSettings ) ;
}
2020-02-25 21:11:29 +01:00
handleMessageDelete ( e ) {
const { channelId , id : messageId } = e ;
stripDeletedMessage ( channelId , messageId ) ;
}
handlePurge ( e ) {
const { channelId , ids : messageIds } = e ;
stripPurgedMessages ( channelId , messageIds ) ;
}
2020-03-04 16:32:57 +01:00
handleWHChange ( { value } ) {
this . hiddenSettings . panelWH = value ;
this . saveHiddenSettings ( ) ;
}
2020-02-25 21:11:29 +01:00
2020-09-02 14:21:30 +02:00
fetchFilename ( url ) {
try {
if ( url . indexOf ( '//giphy.com/gifs/' ) !== - 1 ) url = ` https://i.giphy.com/media/ ${ url . match ( /-([^-]+)$/ ) [ 1 ] } /giphy.gif ` ;
const match = url . match ( /(?:\/)([^\/]+?)(?:(?:\.)([^.\/?:]+)){0,1}(?:[^\w\/\.]+\w+){0,1}(?:(?:\?[^\/]+){0,1}|(?:\/){0,1})$/ ) ;
let name = match [ 1 ] ;
let extension = match [ 2 ] || '.png' ;
if ( url . indexOf ( '//media.tenor.co' ) !== - 1 ) {
extension = name ;
name = url . match ( /\/\/media.tenor.co\/[^\/]+\/([^\/]+)\// ) [ 1 ] ;
} else if ( url . indexOf ( '//i.giphy.com/media/' ) !== - 1 ) name = url . match ( /\/\/i\.giphy\.com\/media\/([^\/]+)\// ) [ 1 ] ;
return ` ${ name } . ${ extension } ` ;
} catch ( err ) {
Logger . stacktrace ( 'Failed to fetch filename' , url , err ) ;
return 'unknown.png' ;
}
}
2020-02-25 21:11:29 +01:00
/* PATCHES */
patchAll ( ) {
2020-03-31 16:30:22 +02:00
Utilities . suppressErrors ( this . patchMessageAccessories . bind ( this ) , 'MessageAccessories patches' ) ( this . promises . state ) ;
Utilities . suppressErrors ( this . patchLazyImageZoomable . bind ( this ) , 'LazyImageZoomable patches' ) ( ) ;
Utilities . suppressErrors ( this . patchImageModal . bind ( this ) , 'ImageModal patches' ) ( ) ;
Utilities . suppressErrors ( this . patchLazyImage . bind ( this ) , 'LazyImage patches' ) ( ) ;
Utilities . suppressErrors ( this . patchImageScaling . bind ( this ) , 'image scaling patches' ) ( ) ;
2020-02-25 21:11:29 +01:00
}
patchLazyImageZoomable ( ) {
2020-04-18 19:22:23 +02:00
const patchKey = DiscordModules . KeyGenerator ( ) ;
2020-07-21 14:39:22 +02:00
const MaskedLink = WebpackModules . getByDisplayName ( 'MaskedLink' ) ;
const renderLinkComponent = props => React . createElement ( MaskedLink , props ) ;
const Modals = WebpackModules . getByProps ( 'ModalRoot' ) ;
2020-09-17 10:05:27 +02:00
const ImageModalClasses = WebpackModules . find ( m => typeof m . image === 'string' && typeof m . modal === 'string' && ! m . content && ! m . card ) || WebpackModules . getByProps ( 'modal' , 'image' ) ;
2020-02-25 21:11:29 +01:00
Patcher . before ( WebpackModules . getByDisplayName ( 'LazyImageZoomable' ) . prototype , 'render' , ( _this , _ , ret ) => {
2020-04-18 19:22:23 +02:00
if ( _this . onZoom . _ _BIV _patched !== patchKey ) {
2020-02-25 21:11:29 +01:00
_this . onZoom = ( e , n ) => {
2020-04-18 19:22:23 +02:00
let isSearch = e . target ;
while ( isSearch && typeof isSearch . className === 'string' && isSearch . className . indexOf ( 'searchResultMessage' ) === - 1 ) isSearch = isSearch . parentElement ;
isSearch = ! ! isSearch ;
2020-02-25 21:11:29 +01:00
e . preventDefault ( ) ;
if ( e . currentTarget instanceof HTMLElement ) e . currentTarget . blur ( ) ;
2020-03-31 16:30:22 +02:00
e = null ;
2020-02-25 21:11:29 +01:00
const original = _this . props . original || _this . props . src ;
2020-07-21 14:39:22 +02:00
ModalStack . openModal ( e => {
2020-03-31 16:30:22 +02:00
try {
return React . createElement (
2020-07-21 14:39:22 +02:00
/ * t h i s s a f e t y n e t s h o u l d p r e v e n t a n y m a j o r i s s u e s o r c r a s h e s , i n t h e o r y
UPDATE : 21.7 . 2020 the theory was wrong , I ' m an idiot and set the state to an invalid one immediately
causing another crash and crashing the entire client !
* /
2020-03-31 16:30:22 +02:00
ErrorCatcher ,
{
label : 'Image modal' ,
onError : ( _ , level ) => {
if ( level < 2 ) XenoLib . Notifications . error ( ` [ ${ this . name } ] Internal error, options will not show. If you repeatedly see this, join my support server, open up console (CTRL + SHIFT + I > click console) and screenshot any errors. ` , { timeout : 0 } ) ;
if ( level > 1 ) e . onClose ( ) ;
} ,
fallback : [
React . createElement (
2020-07-21 14:39:22 +02:00
Modals . ModalRoot ,
{ className : ImageModalClasses . modal , ... e , size : Modals . ModalSize . DYNAMIC } ,
React . createElement (
ImageModal ,
Object . assign (
{
original ,
src : _this . props . src ,
width : _this . props . width ,
height : _this . props . height ,
animated : _this . props . animated ,
children : _this . props . children ,
placeholder : n . placeholder ,
isTrusted : TrustedStore . isTrustedDomain ( original ) ,
onClickUntrusted : _this . onClickUntrusted ,
renderLinkComponent ,
className : ImageModalClasses . image ,
shouldAnimate : true
} ,
e
)
2020-03-31 16:30:22 +02:00
)
2020-02-25 21:11:29 +01:00
)
2020-03-31 16:30:22 +02:00
]
} ,
React . createElement (
2020-07-21 14:39:22 +02:00
Modals . ModalRoot ,
{ className : ImageModalClasses . modal , ... e , size : Modals . ModalSize . DYNAMIC } ,
React . createElement (
RichImageModal ,
Object . assign (
{
original ,
src : _this . props . src ,
width : _this . props . width ,
height : _this . props . height ,
animated : _this . props . animated ,
children : _this . props . children ,
placeholder : n . placeholder ,
isTrusted : TrustedStore . isTrustedDomain ( original ) ,
onClickUntrusted : _this . onClickUntrusted ,
renderLinkComponent ,
_ _BIV _data : _this . props . _ _BIV _data ,
_ _BIV _index : _this . props . _ _BIV _data ? _this . props . _ _BIV _data . images . findIndex ( m => m . src === _this . props . src ) : - 1 ,
_ _BIV _isSearch : isSearch ,
_ _BIV _settings : this . settings ,
className : ImageModalClasses . image ,
shouldAnimate : true
} ,
e
)
2020-02-25 21:11:29 +01:00
)
)
2020-03-31 16:30:22 +02:00
) ;
} catch ( err ) {
/* juuuuust in case, modal crashes can be brutal */
Logger . stacktrace ( 'Error creating image modal' , err ) ;
e . onClose ( ) ;
return null ;
}
2020-02-25 21:11:29 +01:00
} ) ;
} ;
2020-04-18 19:22:23 +02:00
_this . onZoom . _ _BIV _patched = patchKey ;
2020-02-25 21:11:29 +01:00
}
} ) ;
}
async patchMessageAccessories ( promiseState ) {
2020-03-12 16:18:41 +01:00
const selector = ` . ${ XenoLib . getSingleClass ( 'embedWrapper container' ) } ` ;
const MessageAccessories = await ReactComponents . getComponentByName ( 'MessageAccessories' , selector ) ;
if ( ! MessageAccessories . selector ) MessageAccessories . selector = selector ;
2020-02-25 21:11:29 +01:00
if ( promiseState . cancelled ) return ;
Patcher . before ( MessageAccessories . component . prototype , 'render' , _this => {
_this . _ _BIV _data = {
images : extractImages ( _this . props . message ) ,
messageId : _this . props . message . id
} ;
} ) ;
Patcher . before ( MessageAccessories . component . prototype , 'renderEmbeds' , _this => {
if ( ! _this . renderEmbed . _ _BIV _patched ) {
const oRenderEmbed = _this . renderEmbed ;
2020-04-18 19:22:23 +02:00
_this . renderEmbed = function ( e , n ) {
2020-02-25 21:11:29 +01:00
const oRenderImageComponent = n . renderImageComponent ;
2020-03-31 16:30:22 +02:00
if ( ! oRenderImageComponent . _ _BIV _patched ) {
2020-04-18 19:22:23 +02:00
n . renderImageComponent = function ( a ) {
2020-03-31 16:30:22 +02:00
a . _ _BIV _data = _this . _ _BIV _data ;
return oRenderImageComponent ( a ) ;
} ;
n . renderImageComponent . _ _BIV _patched = true ;
}
2020-02-25 21:11:29 +01:00
return oRenderEmbed ( e , n ) ;
} ;
_this . renderEmbed . _ _BIV _patched = true ;
}
} ) ;
Patcher . after ( MessageAccessories . component . prototype , 'renderAttachments' , ( _this , _ , ret ) => {
if ( ! ret ) return ;
2020-03-31 16:30:22 +02:00
for ( let attachment of ret ) {
let props = Utilities . getNestedProp ( attachment , 'props.children.props' ) ;
if ( ! props ) continue ;
2020-02-25 21:11:29 +01:00
const oRenderImageComponent = props . renderImageComponent ;
2020-04-18 19:22:23 +02:00
props . renderImageComponent = function ( e ) {
2020-02-25 21:11:29 +01:00
e . _ _BIV _data = _this . _ _BIV _data ;
return oRenderImageComponent ( e ) ;
} ;
2020-03-31 16:30:22 +02:00
attachment = null ;
props = null ;
}
ret = null ;
2020-02-25 21:11:29 +01:00
} ) ;
MessageAccessories . forceUpdateAll ( ) ;
}
2020-03-03 16:32:03 +01:00
patchImageModal ( ) {
2020-02-25 21:11:29 +01:00
/ * s h a r e d c o d e
these patches are for displaying image info
but the same code is shared with RichImageModal
* /
Patcher . after ( ImageModal . prototype , 'handleMouseMove' , _this => {
if ( _this . state . controlsInactive ) {
_this . setState ( { controlsInactive : false } ) ;
}
if ( _this . state . controlsHovered ) _this . _controlsInactiveDelayedCall . cancel ( ) ;
else _this . _controlsInactiveDelayedCall . delay ( ) ;
} ) ;
/* https://stackoverflow.com/questions/10420352/ */
function humanFileSize ( bytes , si ) {
const thresh = si ? 1000 : 1024 ;
2020-03-31 16:30:22 +02:00
if ( Math . abs ( bytes ) < thresh ) return ` ${ bytes } B ` ;
2020-02-25 21:11:29 +01:00
const units = si ? [ 'kB' , 'MB' , 'GB' , 'TB' , 'PB' , 'EB' , 'ZB' , 'YB' ] : [ 'KiB' , 'MiB' , 'GiB' , 'TiB' , 'PiB' , 'EiB' , 'ZiB' , 'YiB' ] ;
let u = - 1 ;
do {
bytes /= thresh ;
++ u ;
} while ( Math . abs ( bytes ) >= thresh && u < units . length - 1 ) ;
return ` ${ bytes . toFixed ( 1 ) } ${ units [ u ] } ` ;
}
Patcher . after ( ImageModal . prototype , 'requestImageInfo' , ( _this , [ props , equalRatio ] ) => {
2020-03-31 16:30:22 +02:00
const original = ( props && props . original ) || _this . props . original ;
2020-02-25 21:11:29 +01:00
const src = ( props && props . src ) || _this . props . src ;
const width = ( props && props . width ) || _this . props . width ;
const height = ( props && props . height ) || _this . props . height ;
const reqUrl = ( ( ) => {
const split = src . split ( '?' ) [ 0 ] ;
2020-03-04 16:32:57 +01:00
const SaveToRedux = BdApi . getPlugin && BdApi . getPlugin ( 'SaveToRedux' ) ;
const needsSize = src . substr ( src . indexOf ( '?' ) ) . indexOf ( 'size=' ) !== - 1 ;
try {
2020-03-31 16:30:22 +02:00
if ( SaveToRedux ) return SaveToRedux . formatURL ( original || '' , needsSize , '' , '' , split ) . url ;
2020-11-04 22:34:19 +01:00
} catch ( _ ) { }
2020-03-04 16:32:57 +01:00
return split + ( needsSize ? '?size=2048' : '' ) ;
2020-02-25 21:11:29 +01:00
} ) ( ) ;
const max = ImageUtils . zoomFit ( width , height ) ;
const ratio = getRatio ( width , height , max . width , max . height ) ;
const aa = ImageUtils . getImageSrc ( src , width , height , ratio ) ;
_this . _headerRequest2 = RequestModule . head ( aa , ( err , res ) => {
if ( err || res . statusCode >= 400 ) return _this . setState ( { imageSize : - 1 } ) ;
_this . setState ( { imageSize : humanFileSize ( res . headers [ 'content-length' ] ) } ) ;
if ( equalRatio ) _this . setState ( { originalImageSize : humanFileSize ( res . headers [ 'content-length' ] ) } ) ;
} ) ;
if ( ! equalRatio ) {
_this . _headerRequest1 = RequestModule . head ( reqUrl , ( err , res ) => {
if ( err || res . statusCode >= 400 ) return _this . setState ( { originalImageSize : - 1 } ) ;
_this . setState ( { originalImageSize : humanFileSize ( res . headers [ 'content-length' ] ) } ) ;
} ) ;
}
} ) ;
const RequestModule = require ( 'request' ) ;
Patcher . after ( ImageModal . prototype , 'componentDidMount' , _this => {
2020-02-27 21:05:05 +01:00
if ( ! _this . state || _this . state . internalError ) return ;
2020-02-25 21:11:29 +01:00
const requestImageInfo = XenoLib . _ . debounce ( _this . requestImageInfo . bind ( _this ) , 750 , { leading : true } ) ;
_this . requestImageInfo = props => {
const settings = this . settings . ui ;
if ( ! settings . infoResolution && settings . infoScale && settings . infoSize ) return ;
const width = ( props && props . width ) || _this . props . width ;
const height = ( props && props . height ) || _this . props . height ;
const max = ImageUtils . zoomFit ( width , height ) ;
2020-03-31 16:30:22 +02:00
const scaledRatio = getRatio ( width , height , max . width , max . height ) ;
2020-02-25 21:11:29 +01:00
const finalRatio = scaledRatio < 1 ? scaledRatio : 1 ;
if ( settings . infoResolution || settings . infoScale ) {
_this . setState ( {
basicImageInfo : {
width : Math . ceil ( width * finalRatio ) ,
height : Math . ceil ( height * finalRatio ) ,
ratio : finalRatio
} ,
imageSize : null ,
originalImageSize : null
} ) ;
} else _this . setState ( { imageSize : null , originalImageSize : null } ) ;
if ( _this . _headerRequest1 ) _this . _headerRequest1 . abort ( ) ;
if ( _this . _headerRequest2 ) _this . _headerRequest2 . abort ( ) ;
if ( settings . infoSize ) requestImageInfo ( props , finalRatio === 1 ) ;
} ;
_this . requestImageInfo ( ) ;
2020-03-03 16:32:03 +01:00
_this . _controlsVisibleTimeout = new TimingModule . Timeout ( ) ;
_this . _controlsVisibleTimeout . start ( 300 , ( ) => _this . setState ( { controlsVisible : true } ) ) ;
2020-02-25 21:11:29 +01:00
_this . _controlsInactiveDelayedCall = new TimingModule . DelayedCall ( 3500 , ( ) => _this . setState ( { controlsInactive : true } ) ) ;
_this . _controlsInactiveDelayedCall . delay ( ) ;
_this . handleMouseMove = XenoLib . _ . throttle ( _this . handleMouseMove . bind ( _this ) , 500 ) ;
window . addEventListener ( 'mousemove' , _this . handleMouseMove ) ;
2020-03-31 16:30:22 +02:00
Dispatcher . subscribe (
'BIV_LOAD_FULLRES' ,
( _this . _fullresHandler = ( ) => {
if ( _this . state . showFullRes ) return ;
_this . setState ( {
showFullRes : true
} ) ;
} )
) ;
2020-02-25 21:11:29 +01:00
} ) ;
Patcher . after ( ImageModal . prototype , 'componentWillUnmount' , _this => {
2020-02-27 21:05:05 +01:00
if ( ! _this . state || _this . state . internalError ) return ;
2020-02-25 21:11:29 +01:00
if ( _this . _headerRequest1 ) _this . _headerRequest1 . abort ( ) ;
if ( _this . _headerRequest2 ) _this . _headerRequest2 . abort ( ) ;
2020-03-15 12:47:31 +01:00
if ( ! _this . _controlsVisibleTimeout ) {
2020-03-31 16:30:22 +02:00
/* since the BdApi is like a child on cocaine, I'll just wrap it in a try catch */
let reloadFailed = false ;
try {
BdApi . Plugins . reload ( this . name ) ;
} catch ( e ) {
try {
pluginModule . reloadPlugin ( this . name ) ;
} catch ( e ) {
reloadFailed = true ;
}
}
XenoLib . Notifications . warning ( ` [** ${ this . name } **] Something's not right.. ${ reloadFailed ? 'Reloading self failed..' : 'Reloading self.' } ` ) ;
2020-03-15 12:47:31 +01:00
}
if ( _this . _controlsVisibleTimeout ) _this . _controlsVisibleTimeout . stop ( ) ;
if ( _this . _controlsInactiveDelayedCall ) _this . _controlsInactiveDelayedCall . cancel ( ) ;
2020-02-25 21:11:29 +01:00
window . removeEventListener ( 'mousemove' , _this . handleMouseMove ) ;
2020-03-31 16:30:22 +02:00
Dispatcher . unsubscribe ( 'BIV_LOAD_FULLRES' , _this . _fullresHandler ) ;
2020-02-25 21:11:29 +01:00
} ) ;
const renderTableEntry = ( val1 , val2 ) => React . createElement ( 'tr' , { } , React . createElement ( 'td' , { } , val1 ) , React . createElement ( 'td' , { } , val2 ) ) ;
Patcher . after ( ImageModal . prototype , 'render' , ( _this , _ , ret ) => {
if ( ! _this . state )
_this . state = {
controlsInactive : false ,
2020-03-03 16:32:03 +01:00
controlsVisible : false ,
2020-02-25 21:11:29 +01:00
imageSize : null ,
originalImageSize : null ,
2020-03-31 16:30:22 +02:00
basicImageInfo : null ,
showFullRes : false
2020-02-25 21:11:29 +01:00
} ;
2020-03-31 16:30:22 +02:00
if ( this . settings . ui . loadFull ) _this . state . showFullRes = true ;
const imageProps = Utilities . getNestedProp ( ret , 'props.children.0.props' ) ;
if ( imageProps ) imageProps . _ _BIV _full _res = _this . state . showFullRes ;
2020-02-25 21:11:29 +01:00
if ( _this . state . internalError ) return ;
const settings = this . settings . ui ;
2020-03-03 16:32:03 +01:00
const debug = this . settings . behavior . debug ;
if ( ! settings . infoResolution && settings . infoScale && settings . infoSize && ! debug ) return ;
2020-02-25 21:11:29 +01:00
const { basicImageInfo , imageSize , originalImageSize } = _this . state ;
// splice in, otherwise ImageToClipboard freaks out
ret . props . children . splice (
1 ,
0 ,
2020-03-03 16:32:03 +01:00
/* portals are cool o; */
2020-02-26 17:31:45 +01:00
ReactDOM . createPortal (
React . createElement (
'div' ,
{
2020-04-12 17:15:51 +02:00
className : XenoLib . joinClassNames ( 'BIV-info BIV-info-extra' , { 'BIV-hidden' : ! _this . state . controlsVisible , 'BIV-inactive' : _this . state . controlsInactive && ! debug } , TextElement . Colors . STANDARD )
2020-02-26 17:31:45 +01:00
} ,
2020-09-02 14:57:08 +02:00
React . createElement ( 'table' , { } , settings . infoFilename || debug ? React . createElement ( 'tr' , { } , React . createElement ( 'td' , { colspan : 2 } , this . fetchFilename ( _this . props . src ) ) ) : null , settings . infoResolution || debug ? renderTableEntry ( basicImageInfo ? React . createElement ( 'span' , { className : _this . state . showFullRes ? TextElement . Colors . ERROR : undefined } , ` ${ basicImageInfo . width } x ${ basicImageInfo . height } ` ) : 'NaNxNaN' , ` ${ _this . props . width } x ${ _this . props . height } ` ) : null , settings . infoSize || debug ? renderTableEntry ( imageSize ? imageSize : 'NaN' , originalImageSize ? ( originalImageSize === imageSize ? '~' : originalImageSize ) : 'NaN' ) : null , debug ? Object . keys ( _this . state ) . map ( key => ( ! XenoLib . _ . isObject ( _this . state [ key ] ) && key !== 'src' && key !== 'original' && key !== 'placeholder' ? renderTableEntry ( key , String ( _this . state [ key ] ) ) : null ) ) : null )
2020-02-26 17:31:45 +01:00
) ,
overlayDOMNode
2020-02-25 21:11:29 +01:00
)
) ;
} ) ;
}
2020-03-03 16:32:03 +01:00
patchLazyImage ( ) {
2020-03-04 16:32:57 +01:00
if ( NoImageZoom ) return ;
const LazyImage = WebpackModules . getByDisplayName ( 'LazyImage' ) ;
2020-04-11 11:00:25 +02:00
const SectionStore = WebpackModules . find ( m => m . getSection && ! m . getProps ) ;
const NO _SIDEBAR = 0.666178623635432 ;
const SEARCH _SIDEBAR = 0.3601756956193265 ;
const MEMBERS _SIDEBAR = 0.49048316246120055 ;
// Patcher.instead(LazyImage.prototype, 'handleSidebarChange', (_this, [forced]) => {
// const { state } = _this;
2020-11-04 22:34:19 +01:00
// if (!currentChannel()) {
2020-04-11 11:00:25 +02:00
// state.__BIV_sidebarMultiplier = null;
// return;
// }
// const section = SectionStore.getSection();
// let newMultiplier;
// if (section === 'SEARCH') newMultiplier = SEARCH_SIDEBAR;
2020-11-04 22:34:19 +01:00
// else if (section !== 'MEMBERS' || (!SelectedGuildStore.getGuildId() && currentChannel().type !== 'GROUP_DM')) newMultiplier = NO_SIDEBAR;
2020-04-11 11:00:25 +02:00
// else newMultiplier = MEMBERS_SIDEBAR;
// if (!forced && newMultiplier !== state.__BIV_sidebarMultiplier) _this.setState({ __BIV_sidebarMultiplier: newMultiplier });
// else state.__BIV_sidebarMultiplier = newMultiplier;
// });
// Patcher.after(LazyImage.prototype, 'componentDidMount', _this => {
// if (typeof _this.props.__BIV_index !== 'undefined' /* || _this.props.__BIV_isVideo */ || (_this.props.className && _this.props.className.indexOf('embedThumbnail') !== -1)) {
// _this.handleSidebarChange = null;
// return;
// }
// _this.handleSidebarChange = _this.handleSidebarChange.bind(_this);
// SectionStore.addChangeListener(_this.handleSidebarChange);
// });
// Patcher.after(LazyImage.prototype, 'componentWillUnmount', _this => {
// if (!_this.handleSidebarChange) return;
// SectionStore.removeChangeListener(_this.handleSidebarChange);
// });
2020-03-04 16:32:57 +01:00
Patcher . instead ( LazyImage . prototype , 'componentDidUpdate' , ( _this , [ props , state ] ) => {
/* custom handler, original one caused issues with GIFs not animating */
const animated = LazyImage . isAnimated ( _this . props ) ;
if ( animated !== LazyImage . isAnimated ( props ) ) {
if ( animated ) _this . observeVisibility ( ) ;
else _this . unobserveVisibility ( ) ;
} else if ( state . readyState !== _this . state . readyState && animated ) _this . observeVisibility ( ) ;
else if ( ! animated ) _this . unobserveVisibility ( ) ;
} ) ;
2020-04-11 11:00:25 +02:00
// Patcher.before(LazyImage.prototype, 'getRatio', _this => {
2020-08-31 13:41:08 +02:00
// if (!_this.handleSidebarChange || typeof _this.props.__BIV_index !== 'undefined' /* || _this.props.__BIV_isVideo */ || (_this.props.className && _this.props.className.indexOf('embedThumbnail') !== -1)) return;
2020-04-11 11:00:25 +02:00
// if (typeof _this.state.__BIV_sidebarType === 'undefined') _this.handleSidebarChange(true);
// if (_this.state.__BIV_sidebarMultiplier === null) return;
// const scale = window.innerWidth / (window.innerWidth * window.devicePixelRatio);
// _this.props.maxWidth = Math.max(Math.min(innerWidth * devicePixelRatio * _this.state.__BIV_sidebarMultiplier * (1 + (1 - devicePixelRatio)), _this.props.width * scale), 400);
// _this.props.maxHeight = Math.max(Math.min(innerHeight * devicePixelRatio * 0.6777027027027027, _this.props.height * scale), 300);
// });
2020-03-31 16:30:22 +02:00
Patcher . instead ( LazyImage . prototype , 'getSrc' , ( _this , [ ratio , forcePng ] , orig ) => {
if ( _this . props . _ _BIV _full _res ) return _this . props . src ;
return orig ( ratio , forcePng ) ;
} ) ;
2020-03-04 16:32:57 +01:00
Patcher . after ( LazyImage . prototype , 'render' , ( _this , _ , ret ) => {
2020-04-11 11:00:25 +02:00
if ( ! ret ) return ;
2020-04-18 21:06:58 +02:00
if ( ! this . settings . zoom . enabled || _this . props . onZoom || _this . state . readyState !== 'READY' || _this . props . _ _BIV _isVideo ) return ;
2020-04-11 11:00:25 +02:00
/* fix scaling issues for all images */
const scale = window . innerWidth / ( window . innerWidth * window . devicePixelRatio ) ;
ret . props . width = ret . props . width * scale ;
ret . props . height = ret . props . height * scale ;
2020-03-04 16:32:57 +01:00
if ( _this . props . animated && ret . props . children ) {
/* dirty */
try {
ret . props . _ _BIV _src = ret . props . children ( { size : { } } ) . props . src ;
} catch ( e ) {
return ;
}
}
2020-03-03 16:32:03 +01:00
ret . type = Image ;
2020-04-18 19:22:23 +02:00
ret . props . _ _BIV _settings = this . settings . zoom ;
2020-03-04 16:32:57 +01:00
ret . props . _ _BIV _animated = _this . props . animated ;
2020-04-18 19:22:23 +02:00
ret . props . _ _BIV _hiddenSettings = this . hiddenSettings ;
2020-03-03 16:32:03 +01:00
} ) ;
2020-03-04 17:23:35 +01:00
Patcher . after ( WebpackModules . getByDisplayName ( 'LazyVideo' ) . prototype , 'render' , ( _ , _ _ , ret ) => {
if ( ! ret ) return ;
ret . props . _ _BIV _isVideo = true ;
} ) ;
2020-03-03 16:32:03 +01:00
}
2020-03-31 16:30:22 +02:00
patchImageScaling ( ) {
Patcher . instead ( WebpackModules . getByProps ( 'zoomFit' ) , 'zoomFit' , ( _ , [ e , t ] ) => ImageUtils . zoomFit ( e , t ) ) ;
Patcher . instead ( _ImageUtils , 'getImageSrc' , ( _ , args ) => ImageUtils . getImageSrc ( ... args ) ) ;
Patcher . before ( _ImageUtils , 'getSizedImageSrc' , ( _ , args ) => {
const toAdd = window . innerWidth / ( window . innerWidth * window . devicePixelRatio ) ;
args [ 1 ] *= toAdd ;
args [ 2 ] *= toAdd ;
} ) ;
}
2020-02-25 21:11:29 +01:00
/* PATCHES */
2020-03-03 16:32:03 +01:00
showChangelog ( footer ) {
XenoLib . showChangelog ( ` ${ this . name } has been updated! ` , this . version , this . _config . changelog ) ;
}
2020-02-25 21:11:29 +01:00
getSettingsPanel ( ) {
2020-07-21 14:39:22 +02:00
return this . buildSettingsPanel ( ) . append ( new XenoLib . Settings . PluginFooter ( this . showChangelog ) ) . getElement ( ) ;
2020-02-25 21:11:29 +01:00
}
get [ Symbol . toStringTag ] ( ) {
return 'Plugin' ;
}
get name ( ) {
return config . info . name ;
}
get short ( ) {
let string = '' ;
for ( let i = 0 , len = config . info . name . length ; i < len ; i ++ ) {
const char = config . info . name [ i ] ;
if ( char === char . toUpperCase ( ) ) string += char ;
}
return string ;
}
get author ( ) {
return config . info . authors . map ( author => author . name ) . join ( ', ' ) ;
}
get version ( ) {
return config . info . version ;
}
get description ( ) {
return config . info . description ;
}
} ;
} ;
/* Finalize */
let ZeresPluginLibraryOutdated = false ;
let XenoLibOutdated = false ;
try {
2020-03-12 16:18:41 +01:00
if ( global . BdApi && 'function' == typeof BdApi . getPlugin ) {
const i = ( i , n ) => ( ( i = i . split ( '.' ) . map ( i => parseInt ( i ) ) ) , ( n = n . split ( '.' ) . map ( i => parseInt ( i ) ) ) , ! ! ( n [ 0 ] > i [ 0 ] ) || ! ! ( n [ 0 ] == i [ 0 ] && n [ 1 ] > i [ 1 ] ) || ! ! ( n [ 0 ] == i [ 0 ] && n [ 1 ] == i [ 1 ] && n [ 2 ] > i [ 2 ] ) ) ,
n = ( n , e ) => n && n . _config && n . _config . info && n . _config . info . version && i ( n . _config . info . version , e ) ,
e = BdApi . getPlugin ( 'ZeresPluginLibrary' ) ,
o = BdApi . getPlugin ( 'XenoLib' ) ;
2020-10-05 21:05:14 +02:00
n ( e , '1.2.23' ) && ( ZeresPluginLibraryOutdated = ! 0 ) , n ( o , '1.3.26' ) && ( XenoLibOutdated = ! 0 ) ;
2020-02-25 21:11:29 +01:00
}
2020-03-12 16:18:41 +01:00
} catch ( i ) {
console . error ( 'Error checking if libraries are out of date' , i ) ;
2020-02-25 21:11:29 +01:00
}
return ! global . ZeresPluginLibrary || ! global . XenoLib || ZeresPluginLibraryOutdated || XenoLibOutdated
? class {
2020-11-04 22:34:19 +01:00
constructor ( ) {
this . _XL _PLUGIN = true ;
this . start = this . load = this . handleMissingLib ;
}
getName ( ) {
return this . name . replace ( /\s+/g , '' ) ;
}
getAuthor ( ) {
return this . author ;
}
getVersion ( ) {
return this . version ;
}
getDescription ( ) {
return this . description + ' You are missing libraries for this plugin, please enable the plugin and click Download Now.' ;
}
start ( ) { }
stop ( ) { }
handleMissingLib ( ) {
const a = BdApi . findModuleByProps ( 'openModal' , 'hasModalOpen' ) ;
if ( a && a . hasModalOpen ( ` ${ this . name } _DEP_MODAL ` ) ) return ;
const b = ! global . XenoLib ,
c = ! global . ZeresPluginLibrary ,
d = ( b && c ) || ( ( b || c ) && ( XenoLibOutdated || ZeresPluginLibraryOutdated ) ) ,
e = ( ( ) => {
let a = '' ;
return b || c ? ( a += ` Missing ${ XenoLibOutdated || ZeresPluginLibraryOutdated ? ' and outdated' : '' } ` ) : ( XenoLibOutdated || ZeresPluginLibraryOutdated ) && ( a += ` Outdated ` ) , ( a += ` ${ d ? 'Libraries' : 'Library' } ` ) , a ;
} ) ( ) ,
f = ( ( ) => {
let a = ` The ${ d ? 'libraries' : 'library' } ` ;
return b || XenoLibOutdated ? ( ( a += 'XenoLib ' ) , ( c || ZeresPluginLibraryOutdated ) && ( a += 'and ZeresPluginLibrary ' ) ) : ( c || ZeresPluginLibraryOutdated ) && ( a += 'ZeresPluginLibrary ' ) , ( a += ` required for ${ this . name } ${ d ? 'are' : 'is' } ${ b || c ? 'missing' : '' } ${ XenoLibOutdated || ZeresPluginLibraryOutdated ? ( b || c ? ' and/or outdated' : 'outdated' ) : '' } . ` ) , a ;
} ) ( ) ,
g = BdApi . findModuleByDisplayName ( 'Text' ) ,
h = BdApi . findModuleByDisplayName ( 'ConfirmModal' ) ,
i = ( ) => BdApi . alert ( e , BdApi . React . createElement ( 'span' , { } , BdApi . React . createElement ( 'div' , { } , f ) , ` Due to a slight mishap however, you'll have to download the libraries yourself. This is not intentional, something went wrong, errors are in console. ` , c || ZeresPluginLibraryOutdated ? BdApi . React . createElement ( 'div' , { } , BdApi . React . createElement ( 'a' , { href : 'https://betterdiscord.net/ghdl?id=2252' , target : '_blank' } , 'Click here to download ZeresPluginLibrary' ) ) : null , b || XenoLibOutdated ? BdApi . React . createElement ( 'div' , { } , BdApi . React . createElement ( 'a' , { href : 'https://betterdiscord.net/ghdl?id=3169' , target : '_blank' } , 'Click here to download XenoLib' ) ) : null ) ) ;
if ( ! a || ! h || ! g ) return console . error ( ` Missing components: ${ ( a ? '' : ' ModalStack' ) + ( h ? '' : ' ConfirmationModalComponent' ) + ( g ? '' : 'TextElement' ) } ` ) , i ( ) ;
class j extends BdApi . React . PureComponent {
constructor ( a ) {
super ( a ) , ( this . state = { hasError : ! 1 } ) , ( this . componentDidCatch = a => ( console . error ( ` Error in ${ this . props . label } , screenshot or copy paste the error above to Lighty for help. ` ) , this . setState ( { hasError : ! 0 } ) , 'function' == typeof this . props . onError && this . props . onError ( a ) ) ) , ( this . render = ( ) => ( this . state . hasError ? null : this . props . children ) ) ;
2020-03-12 16:18:41 +01:00
}
2020-11-04 22:34:19 +01:00
}
let k = ! 1 ,
l = ! 1 ;
const m = a . openModal (
b => {
if ( l ) return null ;
try {
return BdApi . React . createElement (
j ,
{ label : 'missing dependency modal' , onError : ( ) => ( a . closeModal ( m ) , i ( ) ) } ,
BdApi . React . createElement (
h ,
Object . assign (
{
header : e ,
children : BdApi . React . createElement ( g , { size : g . Sizes . SIZE _16 , children : [ ` ${ f } Please click Download Now to download ${ d ? 'them' : 'it' } . ` ] } ) ,
red : ! 1 ,
confirmText : 'Download Now' ,
cancelText : 'Cancel' ,
onCancel : b . onClose ,
onConfirm : ( ) => {
if ( k ) return ;
k = ! 0 ;
const b = require ( 'request' ) ,
c = require ( 'fs' ) ,
d = require ( 'path' ) ,
e = BdApi . Plugins && BdApi . Plugins . folder ? BdApi . Plugins . folder : window . ContentManager . pluginsFolder ,
f = ( ) => {
( global . XenoLib && ! XenoLibOutdated ) ||
b ( 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js' , ( b , f , g ) => {
2020-07-21 14:39:22 +02:00
try {
2020-11-04 22:34:19 +01:00
if ( b || 200 !== f . statusCode ) return a . closeModal ( m ) , i ( ) ;
c . writeFile ( d . join ( e , '1XenoLib.plugin.js' ) , g , ( ) => { } ) ;
2020-07-21 14:39:22 +02:00
} catch ( b ) {
2020-11-04 22:34:19 +01:00
console . error ( 'Fatal error downloading XenoLib' , b ) , a . closeModal ( m ) , i ( ) ;
2020-07-21 14:39:22 +02:00
}
2020-11-04 22:34:19 +01:00
} ) ;
} ;
! global . ZeresPluginLibrary || ZeresPluginLibraryOutdated
? b ( 'https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js' , ( b , g , h ) => {
try {
if ( b || 200 !== g . statusCode ) return a . closeModal ( m ) , i ( ) ;
c . writeFile ( d . join ( e , '0PluginLibrary.plugin.js' ) , h , ( ) => { } ) , f ( ) ;
} catch ( b ) {
console . error ( 'Fatal error downloading ZeresPluginLibrary' , b ) , a . closeModal ( m ) , i ( ) ;
}
} )
: f ( ) ;
}
} ,
b ,
{ onClose : ( ) => { } }
2020-03-12 16:18:41 +01:00
)
2020-11-04 22:34:19 +01:00
)
) ;
} catch ( b ) {
return console . error ( 'There has been an error constructing the modal' , b ) , ( l = ! 0 ) , a . closeModal ( m ) , i ( ) , null ;
}
} ,
{ modalKey : ` ${ this . name } _DEP_MODAL ` }
) ;
}
get [ Symbol . toStringTag ] ( ) {
return 'Plugin' ;
}
get name ( ) {
return config . info . name ;
}
get short ( ) {
let string = '' ;
for ( let i = 0 , len = config . info . name . length ; i < len ; i ++ ) {
const char = config . info . name [ i ] ;
if ( char === char . toUpperCase ( ) ) string += char ;
2020-02-25 21:11:29 +01:00
}
2020-11-04 22:34:19 +01:00
return string ;
2020-02-25 21:11:29 +01:00
}
2020-11-04 22:34:19 +01:00
get author ( ) {
return config . info . authors . map ( author => author . name ) . join ( ', ' ) ;
}
get version ( ) {
return config . info . version ;
}
get description ( ) {
return config . info . description ;
}
}
2020-02-25 21:11:29 +01:00
: buildPlugin ( global . ZeresPluginLibrary . buildPlugin ( config ) ) ;
} ) ( ) ;
/*@end@*/