2022-11-18 13:20:22 +00:00
// prep
2022-11-23 14:30:05 +00:00
const followButtonPaths = [ "div.account__header button.logo-button" , "div.public-account-header a.logo-button" , "div.account-card a.logo-button" ] ;
2022-11-22 12:09:44 +00:00
const tootButtonsPaths = [ "div.status__action-bar button:not(.disabled):not(:has(i.fa-share-alt))" , "div.detailed-status__action-bar button:not(.disabled):not(:has(i.fa-share-alt))" , "div.status__action-bar a.modal-button" , "a.detailed-status__link" ] ;
2022-11-21 22:47:32 +00:00
const tokenPaths = [ "head script#initial-state" ] ;
2022-11-22 11:18:20 +00:00
const appHolderPaths = [ "body > div.app-holder" , "body > div.public-layout" ] ;
const profileNamePaths = [ "div.account__header__tabs__name small" , "div.public-account-header__tabs__name small" ] ;
2022-11-18 13:20:22 +00:00
const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/ ;
2022-11-23 14:30:05 +00:00
const profileRegex = /^(?:https?:\/\/(www\.)?.*\..*?\/)((?<handle>@\w+(?:@([\w-]+\.)+?\w+)?)|explore)\/?$/ ;
2022-11-21 22:47:32 +00:00
const tootsRegex = /^(?:https?:\/\/(www\.)?.*\..*?)(\/explore|\/public|\/public\/local|\d+)$/ ;
2022-11-22 12:24:26 +00:00
const tootRegex = /^(?:https?:\/\/(www\.)?.*\..*?\/)(?<handle>@\w+(?:@([\w-]+\.)+?\w+)?)\/\d+\/?$/ ;
const handleExtractRegex = /^.*(?<handle>@\w+)@(?<handledomain>([\w-]+\.)+?\w+)\/?$/ ;
2022-11-18 13:20:22 +00:00
const enableConsoleLog = true ;
const logPrepend = "[FediFollow]" ;
const maxElementWaitFactor = 200 ; // x 100ms for total time
2022-11-21 22:47:32 +00:00
const instanceApi = "/api/v1/instance" ;
const statusApi = "/api/v1/statuses" ;
const searchApi = "/api/v2/search"
const fediParamName = "fedifollow" ;
2022-11-22 19:12:23 +00:00
const fediParamActionName = "fediaction" ;
2022-11-18 13:20:22 +00:00
var lastUrl = window . location . href ;
// settings keys with defauls
2022-11-21 22:47:32 +00:00
var settingsDefaults = {
2022-11-18 13:20:22 +00:00
fedifollow _homeinstance : null ,
fedifollow _alert : false ,
fedifollow _mode : "blacklist" ,
fedifollow _whitelist : null ,
fedifollow _blacklist : null ,
2022-11-23 16:39:39 +00:00
fedifollow _target : "_self" ,
2022-11-22 19:30:39 +00:00
fedifollow _autoaction : true
2022-11-18 13:20:22 +00:00
}
2022-11-21 22:47:32 +00:00
// fix for cross-browser storage api compatibility and other public vars
2022-11-22 19:12:23 +00:00
var browser , chrome , instanceUri , fediParamValue , fediParamActionValue , settings ;
2022-11-18 14:13:31 +00:00
2022-11-18 13:20:22 +00:00
// wrappers to prepend to log messages
function log ( text ) {
if ( enableConsoleLog ) {
console . log ( logPrepend + ' ' + text )
}
}
// function to wait for given elements to appear - first found element gets returned (but as of now the selectors are for different layouts anyways)
2022-11-21 22:47:32 +00:00
function waitForEl ( counter , selectors , callback ) {
2022-11-18 13:20:22 +00:00
// check all of the selectors
for ( const selector of selectors ) {
// if found
if ( $ ( selector ) . length ) {
2022-11-21 22:47:32 +00:00
return callback ( selector ) ;
2022-11-18 13:20:22 +00:00
}
}
// repeat if no match was found and we did not exceed the wait factor yet
2022-11-21 22:47:32 +00:00
if ( counter < maxElementWaitFactor ) {
2022-11-18 13:20:22 +00:00
setTimeout ( function ( ) {
// increase counter
waitForEl ( counter + 1 , selectors , callback ) ;
} , 100 ) ;
2022-11-21 22:47:32 +00:00
} else {
return callback ( false ) ;
2022-11-18 13:20:22 +00:00
}
} ;
2022-11-21 22:47:32 +00:00
// promisified xhr for api calls
function makeRequest ( method , url , headers ) {
return new Promise ( function ( resolve , reject ) {
let xhr = new XMLHttpRequest ( ) ;
xhr . open ( method , url ) ;
xhr . timeout = 6000 ;
if ( headers ) {
for ( var key in headers ) {
xhr . setRequestHeader ( key , headers [ key ] )
}
}
xhr . onload = function ( ) {
if ( this . status >= 200 && this . status < 300 ) {
resolve ( xhr . responseText ) ;
} else {
resolve ( false ) ;
}
} ;
xhr . onerror = function ( ) {
reject ( {
status : this . status ,
statusText : xhr . statusText
} ) ;
} ;
xhr . send ( ) ;
} ) ;
}
2022-11-18 15:46:52 +00:00
// extract handle from elements
function extractHandle ( selectors ) {
// check all of the selectors
for ( const selector of selectors ) {
// if found
if ( $ ( selector ) . length ) {
return $ ( selector ) . text ( ) . trim ( ) ;
}
2022-11-18 13:20:22 +00:00
}
2022-11-18 15:46:52 +00:00
return false ;
2022-11-18 13:20:22 +00:00
}
2022-11-21 22:47:32 +00:00
// process white/blacklist from ext settings
2022-11-18 13:20:22 +00:00
function processDomainList ( newLineList ) {
// split by new line
var arrayFromList = newLineList . split ( /\r?\n/ ) ;
// array to put checked domains into
var cleanedArray = [ ] ;
for ( var domain of arrayFromList ) {
// remove whitespace
domain = domain . trim ( ) ;
if ( domainRegex . test ( domain ) ) {
cleanedArray . push ( domain )
} else {
log ( "Removed invalid domain " + domain + " from blacklist/whitelist." )
}
}
// return newly created set (remvoes duplicates)
return [ ... new Set ( cleanedArray ) ] ; ;
}
2022-11-21 22:47:32 +00:00
// extract given url parameter value
var getUrlParameter = function getUrlParameter ( sParam ) {
var sPageURL = window . location . search . substring ( 1 ) ,
sURLVariables = sPageURL . split ( '&' ) , sParameterName , i ;
for ( i = 0 ; i < sURLVariables . length ; i ++ ) {
sParameterName = sURLVariables [ i ] . split ( '=' ) ;
if ( sParameterName [ 0 ] === sParam ) {
return sParameterName [ 1 ] === undefined ? true : decodeURIComponent ( sParameterName [ 1 ] ) ;
}
}
return false ;
} ;
2022-11-18 13:20:22 +00:00
2022-11-22 19:12:23 +00:00
function redirectToHomeInstance ( searchString , action ) {
2022-11-22 21:14:43 +00:00
// build url
2022-11-21 22:47:32 +00:00
var url = "https://" + settings . fedifollow _homeinstance + "/?" + fediParamName + "=" + encodeURIComponent ( searchString ) ;
2022-11-22 19:12:23 +00:00
if ( action ) {
2022-11-22 21:14:43 +00:00
// add action parameter if set
2022-11-22 19:12:23 +00:00
url = url + "&" + fediParamActionName + "=" + action ;
}
2022-11-21 22:47:32 +00:00
log ( "Redirecting to " + url ) ;
2022-11-22 21:14:43 +00:00
// alert if set
2022-11-21 22:47:32 +00:00
if ( settings . fedifollow _alert ) {
alert ( "Redirecting to " + url ) ;
}
2022-11-22 21:14:43 +00:00
// open window according to settings
2022-11-21 22:47:32 +00:00
var win = window . open ( url , settings . fedifollow _target ) ;
// focus the new tab if open was successfull
if ( win ) {
win . focus ( ) ;
} else {
// otherwise notify user...
log ( 'Could not open new window. Please allow popups for this website.' ) ;
}
}
// process fedifollow redirects to the home instance
async function processHomeInstance ( ) {
// first we need the api token
waitForEl ( 0 , tokenPaths , function ( found ) {
if ( found ) {
// then we wait for the appHolder
waitForEl ( 0 , appHolderPaths , async function ( holder ) {
if ( holder ) {
// hide the app holder
$ ( holder ) . hide ( ) ;
// append our notification div
$ ( 'body' ) . append ( '<div style="position: absolute; display: block; margin-left: 30px; margin-top: 30px; font-size: 18px; font-weight: bold" id="fedifollow"></div>' ) ;
// extract the token
var token = JSON . parse ( $ ( found ) . text ( ) ) . meta . access _token ;
if ( token ) {
// update notification div
2022-11-22 19:12:23 +00:00
$ ( 'div#fedifollow' ) . html ( "<p>Resolving search...</p>" ) ;
2022-11-22 18:33:49 +00:00
var requestUrl = location . protocol + '//' + location . hostname + searchApi + "/?q=" + fediParamValue + "&resolve=true&limit=10" ;
2022-11-21 22:47:32 +00:00
var headers = { "Authorization" : "Bearer " + token , } ;
// api request: search endpoint, resolve search string locally (best support for edge cases (for ex. where subdomain does not equal the handle domain) and prevents uncached profile issue)
var response = await makeRequest ( "GET" , requestUrl , headers ) ;
if ( response ) {
response = JSON . parse ( response ) ;
2022-11-22 21:22:42 +00:00
// decode for additional checks
2022-11-22 00:32:49 +00:00
var decodedParam = decodeURIComponent ( fediParamValue ) ;
2022-11-22 00:34:56 +00:00
// if we got no data (failed resolve) we can at least try to resolve a user by swapping the domain in case we got a domain in the handle
2022-11-22 21:22:42 +00:00
// this does not work for resolving post IDs so we check against the handle regex
2022-11-22 10:00:02 +00:00
if ( ! response . accounts . length && ! response . statuses . length && handleExtractRegex . test ( decodedParam ) ) {
2022-11-22 21:22:42 +00:00
// get matches
2022-11-22 10:00:02 +00:00
var matches = decodedParam . match ( handleExtractRegex ) ;
2022-11-22 00:32:49 +00:00
if ( matches . groups . handle && matches . groups . handledomain ) {
2022-11-22 21:22:42 +00:00
// we got handle + handledomain, so try to put the handle domain as host for this fallback (not guaranteed to resolve)
2022-11-22 19:12:23 +00:00
$ ( 'div#fedifollow' ) . append ( "<p>Failed, trying domain swap...</p>" ) ;
2022-11-22 00:32:49 +00:00
var searchstring = encodeURIComponent ( "https://" + matches . groups . handledomain + "/" + matches . groups . handle ) ;
2022-11-22 18:33:49 +00:00
var requestUrl = location . protocol + '//' + location . hostname + searchApi + "/?q=" + searchstring + "&resolve=true&limit=10" ;
2022-11-22 21:22:42 +00:00
// update response var
2022-11-22 00:32:49 +00:00
response = await makeRequest ( "GET" , requestUrl , headers ) ;
response = JSON . parse ( response ) ;
}
}
2022-11-21 22:47:32 +00:00
// set to false initially
var redirect = false ;
// if we got an account but no statuses, redirect to profile (first result)
if ( response . accounts . length && ! response . statuses . length ) {
2022-11-22 21:22:42 +00:00
// build redirect url
2022-11-22 18:33:49 +00:00
var redirect = location . protocol + "//" + location . hostname + "/@" + response . accounts [ 0 ] . acct ;
2022-11-22 19:12:23 +00:00
$ ( 'div#fedifollow' ) . append ( "<p>Success!</p>" ) ;
2022-11-22 21:22:42 +00:00
// if auto actions are enbaled...
2022-11-22 19:30:39 +00:00
if ( settings . fedifollow _autoaction ) {
$ ( 'div#fedifollow' ) . append ( "<p>Attempting auto-follow...</p>" ) ;
2022-11-22 21:22:42 +00:00
// build follow post request
2022-11-22 19:30:39 +00:00
var requestUrl = location . protocol + "//" + location . hostname + "/api/v1/accounts/" + response . accounts [ 0 ] . id + "/follow" ;
var responseFollow = await makeRequest ( "POST" , requestUrl , headers ) ;
2022-11-22 21:22:42 +00:00
// check if it worked (it is ignored if the user was already followed)
2022-11-22 19:30:39 +00:00
if ( responseFollow ) {
responseFollow = JSON . parse ( responseFollow ) ;
if ( responseFollow . following || responseFollow . requested ) {
$ ( 'div#fedifollow' ) . append ( "<p>Success!</p>" ) ;
} else {
$ ( 'div#fedifollow' ) . append ( "<p>Failed.</p>" ) ;
}
2022-11-22 18:33:49 +00:00
}
}
2022-11-21 22:47:32 +00:00
} else if ( ! response . accounts . length && response . statuses . length ) {
2022-11-22 19:12:23 +00:00
$ ( 'div#fedifollow' ) . append ( "<p>Success!</p>" ) ;
2022-11-21 22:47:32 +00:00
// if statuses but no accounts, redirect to status (first result)
2022-11-22 19:12:23 +00:00
var status = response . statuses [ 0 ] ;
2022-11-21 22:47:32 +00:00
var statusData = {
"id" : status . id ,
"account" : status . account . acct
2022-11-18 15:46:52 +00:00
}
2022-11-22 21:22:42 +00:00
// build redirect url
2022-11-22 18:33:49 +00:00
var redirect = location . protocol + "//" + location . hostname + "/@" + statusData . account + "/" + statusData . id ;
2022-11-22 21:22:42 +00:00
// if autoactions enabled and fediParamValue is okay...
2022-11-22 19:30:39 +00:00
if ( settings . fedifollow _autoaction && ( fediParamActionValue == "boost" || fediParamActionValue == "favourite" ) ) {
2022-11-22 19:16:47 +00:00
$ ( 'div#fedifollow' ) . append ( "<p>Attempting auto-" + fediParamActionValue + "...</p>" ) ;
2022-11-22 21:22:42 +00:00
// build favourite/boost post request
2022-11-22 19:12:23 +00:00
var actionRequest = location . protocol + "//" + location . hostname + "/api/v1/statuses/" + statusData . id + "/" ;
if ( fediParamActionValue == "boost" ) {
actionRequest = actionRequest + "reblog" ;
} else {
actionRequest = actionRequest + "favourite" ;
}
var actionResponse = await makeRequest ( "POST" , actionRequest , headers ) ;
2022-11-22 21:22:42 +00:00
// check if it worked (it is ignored if the post was already fav'ed / boosted)
2022-11-22 19:12:23 +00:00
if ( actionResponse ) {
actionResponse = JSON . parse ( actionResponse ) ;
if ( actionResponse . reblogged || actionResponse . favourited ) {
$ ( 'div#fedifollow' ) . append ( "<p>Success!<p>" ) ;
2022-11-22 19:16:47 +00:00
} else {
$ ( 'div#fedifollow' ) . append ( "<p>Failed.<p>" ) ;
2022-11-22 19:12:23 +00:00
}
}
}
2022-11-21 22:47:32 +00:00
}
// if we got a redirect url...
if ( redirect ) {
2022-11-22 19:12:23 +00:00
$ ( 'div#fedifollow' ) . append ( "<p>Redirecting...</p>" ) ;
2022-11-21 22:47:32 +00:00
// open the url in current tab
var win = window . open ( redirect , "_self" ) ;
log ( "Redirected to " + redirect )
2022-11-18 15:46:52 +00:00
// focus the new tab if open was successfull
if ( win ) {
win . focus ( ) ;
2022-11-21 22:47:32 +00:00
return true ;
2022-11-18 15:46:52 +00:00
} else {
// otherwise notify user...
log ( 'Could not open new window. Please allow popups for this website.' ) ;
2022-11-21 22:47:32 +00:00
$ ( 'div#fedifollow' ) . text ( 'Could not open new window. Please allow popups for this website.' ) ;
2022-11-18 15:46:52 +00:00
}
2022-11-21 22:47:32 +00:00
} else {
log ( "Could not resolve a match for this search..." ) ;
$ ( 'div#fedifollow' ) . text ( "Could not resolve a match for this search...." )
}
} else {
log ( "API call failed..." )
$ ( 'div#fedifollow' ) . text ( "API call failed..." )
}
2022-11-18 15:46:52 +00:00
} else {
2022-11-21 22:47:32 +00:00
log ( "Could not extract API token." )
$ ( 'div#fedifollow' ) . text ( "Could not get API token..." ) ;
2022-11-18 15:46:52 +00:00
}
2022-11-21 22:47:32 +00:00
// show app holder after 1.5s (in case we did not redirect)
setTimeout ( function ( ) {
$ ( holder ) . show ( )
} , 1500 ) ;
2022-11-18 13:20:22 +00:00
} else {
2022-11-21 22:47:32 +00:00
log ( "Could not find app holder element." )
2022-11-18 13:20:22 +00:00
}
} ) ;
} else {
2022-11-21 22:47:32 +00:00
log ( "Could not find API token." )
2022-11-18 13:20:22 +00:00
}
2022-11-21 22:47:32 +00:00
} ) ;
}
// process any toots found on supported sites
async function processToots ( ) {
// if the url matches our pattern for supported sites OR is a profile url
if ( tootsRegex . test ( window . location . href . split ( "?" ) [ 0 ] ) || profileRegex . test ( window . location . href . split ( "?" ) [ 0 ] ) ) {
// wait for follow button to appear
waitForEl ( 0 , tootButtonsPaths , function ( found ) {
if ( found ) {
// convert array to comma separated list for jquery select
var allElements = tootButtonsPaths . join ( ", " ) ;
// disable handlers for those elements
$ ( allElements ) . off ( ) ;
$ ( "body" ) . on ( "click" , allElements , async function ( e ) {
// prevent default and immediate propagation
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
2022-11-22 19:12:23 +00:00
// determine action
var action ;
2022-11-22 21:14:43 +00:00
// determine if we have an action (mastodon 4)
2022-11-22 19:12:23 +00:00
if ( $ ( this ) . children ( "i.fa-retweet" ) . length ) {
action = "boost" ;
} else if ( $ ( this ) . children ( "i.fa-star" ) . length ) {
action = "favourite" ;
}
2022-11-21 22:47:32 +00:00
// extract the toot id from the closest article element
2022-11-21 23:44:51 +00:00
var closestTootId ;
2022-11-22 13:52:23 +00:00
// first check if there is an <a> sibling with the actual post URL (easiest and fastest)
if ( $ ( this ) . siblings ( "a.status__relative-time" ) . attr ( "href" ) ) {
var redirected = true ;
2022-11-22 21:14:43 +00:00
redirectToHomeInstance ( ( this ) . siblings ( "a.status__relative-time" ) . attr ( "href" ) , action ) ;
2022-11-22 13:52:23 +00:00
} else if ( $ ( e . target ) . closest ( "div.status" ) . attr ( "data-id" ) ) {
// no? then check if there is a closest div.status with the ID in data-id attribute
2022-11-21 23:44:51 +00:00
closestTootId = $ ( e . target ) . closest ( "div.status" ) . attr ( "data-id" ) . replace ( /[^0-9]/gi , '' ) ;
2022-11-22 13:07:39 +00:00
} else if ( $ ( e . target ) . closest ( "article" ) . attr ( "data-id" ) ) {
2022-11-22 13:52:23 +00:00
// no? then check if there is a closest <article> element with the ID in data-id attribute
2022-11-22 13:07:39 +00:00
closestTootId = $ ( e . target ) . closest ( "article" ) . attr ( "data-id" ) . replace ( /[^0-9]/gi , '' ) ;
2022-11-22 11:18:20 +00:00
} else if ( this . href ) {
2022-11-22 13:52:23 +00:00
// no? then this is probably mastodon 3 and we have the ID in the href of the clicked link
2022-11-22 11:18:20 +00:00
closestTootId = this . href . split ( "?" ) [ 0 ] . split ( "/" ) [ 4 ] ;
2022-11-22 21:14:43 +00:00
// double check action because we have a fallback here
if ( ! action ) {
// if the link contains...
if ( ~ this . href . indexOf ( "type=reblog" ) ) {
action = "boost" ;
} else if ( ~ this . href . indexOf ( "type=favourite" ) ) {
action = "favourite" ;
}
}
2022-11-22 13:52:23 +00:00
} else if ( tootRegex . test ( window . location . href . split ( "?" ) [ 0 ] ) ) {
// no? then this is probably the detailed view of a post, so we can extract the ID from the URL
closestTootId = window . location . href . split ( "/" ) [ 4 ] ;
2022-11-21 23:44:51 +00:00
}
2022-11-22 13:52:23 +00:00
// if we have a toot id and NOT already redirected (see first check above)
2022-11-22 14:08:43 +00:00
if ( ! redirected ) {
if ( closestTootId ) {
2022-11-22 18:33:49 +00:00
var requestUrl = location . protocol + '//' + location . hostname + statusApi + "/" + closestTootId ;
2022-11-22 14:08:43 +00:00
// call status API to get correct author handle
var response = await makeRequest ( "GET" , requestUrl , null ) ;
if ( response ) {
2022-11-22 21:14:43 +00:00
// if succesfull, get the url and clean it (fix for some instances)
2022-11-22 14:08:43 +00:00
var postUri = JSON . parse ( response ) . url . replace ( "/activity/" , "" ) . replace ( "/activity" , "" ) ;
if ( postUri ) {
// redirect to home instance
2022-11-22 19:12:23 +00:00
redirectToHomeInstance ( postUri , action ) ;
2022-11-22 14:08:43 +00:00
} else {
log ( "Could not find post url." )
}
2022-11-21 22:47:32 +00:00
}
2022-11-22 14:08:43 +00:00
} else {
2022-11-22 14:12:28 +00:00
log ( "Could not find toot ID." ) ;
2022-11-21 22:47:32 +00:00
}
}
} ) ;
} else {
log ( "Could not find any toots" ) ;
}
} ) ;
} else {
log ( "There are no toots on this site" ) ;
2022-11-18 13:20:22 +00:00
}
2022-11-21 22:47:32 +00:00
}
2022-11-18 13:20:22 +00:00
2022-11-21 22:47:32 +00:00
// main function to listen for the follow button pressed and open a new tab with the home instance
function processFollow ( ) {
// check if this is a profile url
if ( profileRegex . test ( window . location . href . split ( "?" ) [ 0 ] ) ) {
// wait until follow button appears (document is already ready, but most content is loaded afterwards)
waitForEl ( 0 , followButtonPaths , function ( found ) {
if ( found ) {
// setup the button click listener
2022-11-22 10:00:02 +00:00
$ ( found ) . click ( async function ( e ) {
2022-11-21 22:47:32 +00:00
// prevent default action and other handlers
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
// backup the button text
2022-11-22 14:23:07 +00:00
var originaltext = $ ( found ) . html ( ) ;
2022-11-23 14:30:05 +00:00
var handleEl ;
2022-11-22 10:00:02 +00:00
var handleDomain ;
var handle ;
2022-11-23 14:30:05 +00:00
// dirty fix for some v3 instance views
if ( ~ window . location . href . indexOf ( "/explore" ) ) {
var temp = $ ( e . target ) . closest ( "div.account-card" ) . find ( "div.display-name > span" ) ;
if ( temp . length ) {
handleEl = temp ;
}
} else {
// check all defined selectors for the username element
for ( const selector of profileNamePaths ) {
if ( $ ( selector ) . length ) {
handleEl = $ ( selector )
break ;
}
2022-11-22 10:00:02 +00:00
}
}
2022-11-23 14:30:05 +00:00
if ( handleEl ) {
// match content of first found element against handle regex (with match grups)
var handleDomainMatches = handleEl . text ( ) . trim ( ) . match ( handleExtractRegex ) ;
handleDomain = handleDomainMatches . groups . handledomain ;
handle = handleDomainMatches . groups . handle ;
}
2022-11-22 21:14:43 +00:00
// if extraction worked...
2022-11-22 10:01:10 +00:00
if ( handleDomain && handle ) {
2022-11-22 21:14:43 +00:00
// make request to the external instances search endpoint to make sure we get the correct url for the searchstring
// (for ex. another external instance, also instance domain can differ from handle domain)
2022-11-22 18:33:49 +00:00
var requestUrl = location . protocol + "//" + location . hostname + searchApi + "/?q=" + encodeURIComponent ( handle + "@" + handleDomain ) + "&resolve=false&limit=10" ;
2022-11-22 10:00:02 +00:00
var response = await makeRequest ( "GET" , requestUrl , null ) ;
var result ;
if ( response ) {
response = JSON . parse ( response ) ;
2022-11-22 21:14:43 +00:00
// if there are any accounts in the response
2022-11-22 10:00:02 +00:00
if ( response . accounts . length ) {
2022-11-22 21:14:43 +00:00
// get url of first account (which will be the one we need since we searched user+domain)
2022-11-22 10:00:02 +00:00
result = response . accounts [ 0 ] . url ;
2022-11-22 21:14:43 +00:00
// set result for searchstring
2022-11-22 10:00:02 +00:00
var url = document . createElement ( 'a' ) ;
url . setAttribute ( 'href' , response . accounts [ 0 ] . url ) ;
result = url . protocol + "//" + url . hostname ;
}
}
2022-11-22 21:14:43 +00:00
// replace the button text to indicate redirection
$ ( found ) . text ( "Redirecting..." ) ;
// if we could resolve the user domain...
2022-11-22 10:00:02 +00:00
if ( result ) {
2022-11-22 21:14:43 +00:00
// add the handle
2022-11-22 10:00:02 +00:00
var redirectUrl = result + "/" + handle ;
// timeout 1000ms to make it possible to notice the redirection indication
setTimeout ( function ( ) {
2022-11-22 19:12:23 +00:00
redirectToHomeInstance ( redirectUrl , null ) ;
2022-11-22 10:00:02 +00:00
// restore original button text
2022-11-22 14:48:06 +00:00
$ ( found ) . html ( originaltext ) ;
2022-11-22 10:00:02 +00:00
} , 1000 ) ;
} else {
2022-11-22 21:14:43 +00:00
log ( "Could not get instance URL from API search, attempting raw redirect." ) ;
2022-11-23 14:30:05 +00:00
var rawRedirect = window . location . href ;
// dirty fix for some v3 views
if ( ~ rawRedirect . indexOf ( "/explore" ) ) {
rawRedirect = "https://" + handleDomain + "/" + handle ;
}
2022-11-22 10:00:02 +00:00
// timeout 1000ms to make it possible to notice the redirection indication
setTimeout ( function ( ) {
2022-11-23 14:30:05 +00:00
redirectToHomeInstance ( rawRedirect , null ) ;
2022-11-22 10:00:02 +00:00
// restore original button text
2022-11-22 14:23:07 +00:00
$ ( found ) . html ( originaltext ) ;
2022-11-22 10:00:02 +00:00
} , 1000 ) ;
}
} else {
log ( "Could not extract user handle." )
}
2022-11-21 22:47:32 +00:00
} ) ;
} else {
log ( "Could not find any follow button." ) ;
}
} ) ;
} else {
log ( "Not a profile URL." ) ;
}
}
2022-11-22 21:14:43 +00:00
// for some reason, locationchange event did not work for me so lets use this ugly thing...
2022-11-21 22:47:32 +00:00
async function urlChangeLoop ( ) {
// run every 100ms, can probably be reduced
setTimeout ( function ( ) {
// compare last to current url
if ( ! ( lastUrl == window . location . href ) ) {
// update lastUrl and run main script
lastUrl = window . location . href ;
processFollow ( ) ;
processToots ( ) ;
}
// repeat
urlChangeLoop ( ) ;
} , 300 ) ;
}
function checkSettings ( ) {
// if the home instance is undefined/null/empty
if ( settings . fedifollow _homeinstance == null || ! settings . fedifollow _homeinstance ) {
log ( "Mastodon home instance is not set." ) ;
return false ;
}
// if the value looks like a domain...
if ( ! ( domainRegex . test ( settings . fedifollow _homeinstance ) ) ) {
log ( "Instance setting is not a valid domain name." ) ;
return false ;
}
// set default if wrong value
if ( $ . inArray ( settings . fedifollow _mode , [ "blacklist" , "whitelist" ] ) < 0 ) {
settings . fedifollow _mode = "blacklist" ;
}
if ( $ . inArray ( settings . fedifollow _target , [ "_blank" , "_self" ] ) < 0 ) {
settings . fedifollow _target = "_blank" ;
}
if ( settings . fedifollow _mode == "whitelist" ) {
// if in whitelist mode and the cleaned whitelist is empty, return false
settings . fedifollow _whitelist = processDomainList ( settings . fedifollow _whitelist ) ;
if ( settings . fedifollow _whitelist . length < 1 ) {
log ( "Whitelist is empty or invalid." )
2022-11-18 13:20:22 +00:00
return false ;
}
2022-11-21 22:47:32 +00:00
} else {
// also process the blacklist if in blacklist mode, but an empty blacklist is OK so we do not return false
settings . fedifollow _blacklist = processDomainList ( settings . fedifollow _blacklist ) ;
}
return true ;
}
// test if the current site should be processed or not
// this will also be the function for whitelist/blacklist feature
async function checkSite ( callback ) {
// is this site on our home instance?
2022-11-22 18:33:49 +00:00
if ( location . hostname == settings . fedifollow _homeinstance ) {
2022-11-21 22:47:32 +00:00
// do we have a fedifollow param?
fediParamValue = getUrlParameter ( fediParamName ) ;
2022-11-22 19:12:23 +00:00
fediParamActionValue = getUrlParameter ( fediParamActionName ) ;
2022-11-21 22:47:32 +00:00
if ( fediParamValue ) {
// if so, run home mode
return "home" ;
2022-11-18 13:20:22 +00:00
} else {
2022-11-21 22:47:32 +00:00
log ( "Current site is your home instance." ) ;
return false ;
2022-11-18 13:20:22 +00:00
}
2022-11-21 22:47:32 +00:00
}
// are we in whitelist mode?
if ( settings . fedifollow _mode == "whitelist" ) {
// if so, check if site is NOT in whitelist
2022-11-22 18:33:49 +00:00
if ( $ . inArray ( location . hostname , settings . fedifollow _whitelist ) < 0 ) {
2022-11-21 22:47:32 +00:00
log ( "Current site is not in whitelist." ) ;
return false ;
}
} else {
// otherwise we are in blacklist mode, so check if site is on blacklist
2022-11-22 18:33:49 +00:00
if ( $ . inArray ( location . hostname , settings . fedifollow _blacklist ) > - 1 ) {
2022-11-21 22:47:32 +00:00
log ( "Current site is in blacklist." ) ;
return false ;
}
}
// last check - and probably the most accurate to determine if it actually is mastadon
2022-11-22 18:33:49 +00:00
var requestUrl = location . protocol + '//' + location . hostname + instanceApi ;
2022-11-21 22:47:32 +00:00
// call instance api to confirm its mastodon and get normalized handle uri
var response = await makeRequest ( "GET" , requestUrl , null ) ;
if ( response ) {
var uri = JSON . parse ( response ) . uri ;
if ( uri ) {
// update global var
instanceUri = uri ;
// run external mode
return "external" ;
2022-11-18 13:20:22 +00:00
}
}
2022-11-22 21:14:43 +00:00
log ( "Does not look like a Mastodon instance." ) ;
2022-11-21 22:47:32 +00:00
return false ;
}
2022-11-18 13:20:22 +00:00
2022-11-21 22:47:32 +00:00
// run wrapper
async function run ( ) {
// get settings
settings = await ( browser || chrome ) . storage . local . get ( settingsDefaults ) ;
if ( settings ) {
// validate settings
if ( checkSettings ( ) ) {
// check site (if and which scripts should run)
var mode = await checkSite ( ) ;
// run or exit
if ( mode == "external" ) {
processFollow ( ) ;
processToots ( ) ;
urlChangeLoop ( ) ;
} else if ( mode == "home" ) {
processHomeInstance ( ) ;
} else {
log ( "Will not process this site." )
}
}
} else {
log ( "Could not load settings." )
}
2022-11-18 13:20:22 +00:00
}
2022-11-21 22:47:32 +00:00
run ( )