Channel subscription type streaming
This commit is contained in:
		| @@ -130,17 +130,11 @@ function playSound() { | |||||||
| 	volumeControl.gain.value = vol | 	volumeControl.gain.value = vol | ||||||
| 	source.start(0) | 	source.start(0) | ||||||
| 	soundFile = source | 	soundFile = source | ||||||
|  |  | ||||||
| 	function newFunction() { |  | ||||||
| 		var source |  | ||||||
| 		return source |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| function nano() { | function nano() { | ||||||
| 	postMessage(['nano', null], '*') | 	postMessage(['nano', null], '*') | ||||||
| } | } | ||||||
| onmessage = function(e) { | onmessage = function(e) { | ||||||
| 	console.log(e) |  | ||||||
| 	if (e.data[0] == 'details') { | 	if (e.data[0] == 'details') { | ||||||
| 		details(e.data[1][0], e.data[1][1]) | 		details(e.data[1][0], e.data[1][1]) | ||||||
| 	} else if (e.data[0] == 'udg') { | 	} else if (e.data[0] == 'udg') { | ||||||
|   | |||||||
| @@ -19,7 +19,22 @@ async function mixtl(acct_id, tlid, type, delc, voice) { | |||||||
| 	additional(acct_id, tlid) | 	additional(acct_id, tlid) | ||||||
| 	jQuery('time.timeago').timeago() | 	jQuery('time.timeago').timeago() | ||||||
| 	todc() | 	todc() | ||||||
| 	mixre(acct_id, tlid, 'mix', mute, voice, '') | 	if(mastodonBaseWsStatus[domain] == 'cannnotopen') { | ||||||
|  | 		mixre(acct_id, tlid, 'mix', mute, voice, '') | ||||||
|  | 	} else if(mastodonBaseWs[domain] == 'undetected') { | ||||||
|  | 		const mbws = setInterval(function () { | ||||||
|  | 			if(mastodonBaseWsStatus[domain] == 'cannnotopen') { | ||||||
|  | 				mixre(acct_id, tlid, 'mix', mute, voice, '') | ||||||
|  | 				clearInterval(mbws) | ||||||
|  | 			} else if(mastodonBaseWsStatus[domain] == 'available') { | ||||||
|  | 				mastodonBaseWs[domain].send(`{"type":"subscribe","stream":"public:local"}`) | ||||||
|  | 				clearInterval(mbws) | ||||||
|  | 			} | ||||||
|  | 		}, 1000) | ||||||
|  | 	} else if(mastodonBaseWsStatus[domain] == 'available') { | ||||||
|  | 		mastodonBaseWs[domain].send(`{"type":"subscribe","stream":"public:local"}`) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	$(window).scrollTop(0) | 	$(window).scrollTop(0) | ||||||
| 	lastId = integrated[0].id | 	lastId = integrated[0].id | ||||||
| 	beforeLastId = integrated[1].id | 	beforeLastId = integrated[1].id | ||||||
|   | |||||||
| @@ -1565,79 +1565,98 @@ function pollParse(poll, acct_id, emojis) { | |||||||
| var mastodonBaseWs = {} | var mastodonBaseWs = {} | ||||||
| var mastodonBaseWsStatus = {} | var mastodonBaseWsStatus = {} | ||||||
| function mastodonBaseStreaming(acct_id) { | function mastodonBaseStreaming(acct_id) { | ||||||
| 	const mute = getFilterTypeByAcct(acct_id, type) |  | ||||||
| 	mastodonBaseWsStatus[domain] = 'undetected' |  | ||||||
| 	const domain = localStorage.getItem(`domain_${acct_id}`) | 	const domain = localStorage.getItem(`domain_${acct_id}`) | ||||||
|  | 	if(mastodonBaseWsStatus[domain]) return | ||||||
|  | 	mastodonBaseWsStatus[domain] = 'undetected' | ||||||
| 	const at = localStorage.getItem(`acct_${acct_id}_at`) | 	const at = localStorage.getItem(`acct_${acct_id}_at`) | ||||||
| 	const start = `wss://cutls.com/api/v1/streaming/?at=${at}` | 	const start = `wss://${domain}/api/v1/streaming/?access_token=${at}` | ||||||
| 	mastodonBaseWs[domain] = new WebSocket(start) | 	mastodonBaseWs[domain] = new WebSocket(start) | ||||||
| 	mastodonBaseWs[domain].onopen = function () { | 	mastodonBaseWs[domain].onopen = function () { | ||||||
| 		mastodonBaseWsStatus[domain] = 'available' | 		mastodonBaseWsStatus[domain] = 'available' | ||||||
|  | 		mastodonBaseWs[domain].send(`{"type":"subscribe","stream":"user"}`) | ||||||
|  | 		$('.notice_icon_acct_' + acct_id).removeClass('red-text') | ||||||
| 	} | 	} | ||||||
| 	mastodonBaseWs[domain].onmessage = function () { | 	mastodonBaseWs[domain].onmessage = function (mess) { | ||||||
| 		const typeA = JSON.parse(mess.data).event | 		const typeA = JSON.parse(mess.data).event | ||||||
| 		if (typeA == 'delete') { | 		if (typeA == 'delete') { | ||||||
| 			$(`[unique-id=${JSON.parse(mess.data).payload}]`).hide() | 			$(`[unique-id=${JSON.parse(mess.data).payload}]`).hide() | ||||||
| 			$(`[unique-id=${JSON.parse(mess.data).payload}]`).remove() | 			$(`[unique-id=${JSON.parse(mess.data).payload}]`).remove() | ||||||
| 		} else if (typeA == 'update' || typeA == 'conversation') { | 		} else if (typeA == 'update' || typeA == 'conversation') { | ||||||
| 			if ( | 			//markers show中はダメ | ||||||
| 				!$('#unread_' + tlid + ' .material-icons').hasClass('teal-text') | 			const tl = JSON.parse(mess.data).stream | ||||||
| 			) { | 			const obj = JSON.parse(JSON.parse(mess.data).payload) | ||||||
| 				//markers show中はダメ | 			const tls = getTlMeta(tl[0], tl, acct_id, obj) | ||||||
| 				const tl = JSON.parse(mess.data).stream | 			insertTl(obj, tls) | ||||||
| 				const obj = JSON.parse(JSON.parse(mess.data).payload) | 		} else if (typeA == 'filters_changed') { | ||||||
| 				const tls = getTlMeta(tl[0], tl, acct_id) | 			filterUpdate(acct_id) | ||||||
| 				insertTl(obj, tls) | 		} else if (~typeA.indexOf('announcement')) { | ||||||
| 			} else if (typeA == 'filters_changed') { | 			announ(acct_id, tlid) | ||||||
| 				filterUpdate(acct_id) | 		} else if (type == 'notification') { | ||||||
| 			} else if (~typeA.indexOf('announcement')) { | 			let template = '' | ||||||
| 				announ(acct_id, tlid) | 			localStorage.setItem('lastnotf_' + acct_id, obj.id) | ||||||
|  | 			if (obj.type != 'follow' && obj.type != 'follow_request') { | ||||||
|  | 				template = parse([obj], 'notf', acct_id, 'notf', popup) | ||||||
|  | 			} else if (obj.type == 'follow_request') { | ||||||
|  | 				template = userparse([obj.account], 'request', acct_id, 'notf', -1) | ||||||
|  | 			} else { | ||||||
|  | 				template = userparse([obj], obj.type, acct_id, 'notf', popup) | ||||||
| 			} | 			} | ||||||
|  | 			if (!$('div[data-notfIndv=' + acct_id + '_' + obj.id + ']').length) { | ||||||
|  | 				$('div[data-notf=' + acct_id + ']').prepend(template) | ||||||
|  | 				$('div[data-const=notf_' + acct_id + ']').prepend(template) | ||||||
|  | 			} | ||||||
|  | 			jQuery('time.timeago').timeago() | ||||||
|  | 		} else { | ||||||
|  | 			console.error('unknown type ' + typeA) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	mastodonBaseWs[domain].onerror = function (error) { | 	mastodonBaseWs[domain].onerror = function (error) { | ||||||
| 		console.error("Error closing " + tlid) | 		notf(acct_id, 0) //fallback | ||||||
|  | 		console.error("Error closing " + domain) | ||||||
| 		console.error(error) | 		console.error(error) | ||||||
| 		mastodonBaseWsStatus[domain] = 'cannotuse' | 		mastodonBaseWsStatus[domain] = 'cannotuse' | ||||||
| 		mastodonBaseWs[domain] = false | 		mastodonBaseWs[domain] = false | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 	mastodonBaseWs[domain].onclose = function () { | 	mastodonBaseWs[domain].onclose = function () { | ||||||
| 		console.warn("Closing " + tlid) | 		notf(acct_id, 0) //fallback | ||||||
|  | 		console.warn("Closing " + domain) | ||||||
| 		mastodonBaseWs[domain] = false | 		mastodonBaseWs[domain] = false | ||||||
| 		mastodonBaseWsStatus[domain] = 'cannotuse' | 		mastodonBaseWsStatus[domain] = 'cannotuse' | ||||||
| 		connectMisskey(acct_id, true) |  | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| function insertTl(obj, tls) { | function insertTl(obj, tls) { | ||||||
| 	for (const timeline of tls) { | 	for (const timeline of tls) { | ||||||
| 		const { id, voice } = timeline | 		const { id, voice, type, acct_id } = timeline | ||||||
| 		if ($(`#timeline_${id} [toot-id=${obj.id}]`).length) { | 		const mute = getFilterTypeByAcct(acct_id, type) | ||||||
|  | 		if ($(`#unread_${id} .material-icons`).hasClass('teal-text')) continue | ||||||
|  | 		if (!$(`#timeline_${id} [toot-id=${obj.id}]`).length) { | ||||||
| 			if (voice) { | 			if (voice) { | ||||||
| 				say(obj.content) | 				say(obj.content) | ||||||
| 			} | 			} | ||||||
| 			const template = parse([obj], type, acct_id, tlid, '', mute, type) | 			const template = parse([obj], type, acct_id, id, '', mute, type) | ||||||
|  | 			console.log($(`#timeline_box_${id}_box .tl-box`).scrollTop(), `timeline_box_${id}_box .tl-box`) | ||||||
| 			if ( | 			if ( | ||||||
| 				$(`timeline_box_${tlid}_box .tl-box`).scrollTop() === 0 | 				$(`#timeline_box_${id}_box .tl-box`).scrollTop() === 0 | ||||||
| 			) { | 			) { | ||||||
| 				$(`#timeline_${tlid}`).prepend(template) | 				$(`#timeline_${id}`).prepend(template) | ||||||
| 			} else { | 			} else { | ||||||
| 				const pool = localStorage.getItem('pool_' + tlid) | 				let pool = localStorage.getItem('pool_' + id) | ||||||
| 				if (pool) { | 				if (pool) { | ||||||
| 					pool = template + pool | 					pool = template + pool | ||||||
| 				} else { | 				} else { | ||||||
| 					pool = template | 					pool = template | ||||||
| 				} | 				} | ||||||
| 				localStorage.setItem('pool_' + tlid, pool) | 				localStorage.setItem('pool_' + id, pool) | ||||||
| 			} | 			} | ||||||
| 			scrollck() | 			scrollck() | ||||||
| 			additional(acct_id, tlid) | 			additional(acct_id, id) | ||||||
| 			jQuery('time.timeago').timeago() | 			jQuery('time.timeago').timeago() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| function getTlMeta(type, data, num) { | function getTlMeta(type, data, num, status) { | ||||||
| 	const acct_id = num.toString() | 	const acct_id = num.toString() | ||||||
| 	const columns = localStorage.getItem('column') | 	const columns = localStorage.getItem('column') | ||||||
| 	const obj = JSON.parse(columns) | 	const obj = JSON.parse(columns) | ||||||
| @@ -1652,7 +1671,9 @@ function getTlMeta(type, data, num) { | |||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| @@ -1666,7 +1687,9 @@ function getTlMeta(type, data, num) { | |||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| @@ -1680,7 +1703,9 @@ function getTlMeta(type, data, num) { | |||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| @@ -1694,7 +1719,9 @@ function getTlMeta(type, data, num) { | |||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| @@ -1708,7 +1735,9 @@ function getTlMeta(type, data, num) { | |||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| @@ -1722,7 +1751,25 @@ function getTlMeta(type, data, num) { | |||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
|  | 					}) | ||||||
|  | 				} | ||||||
|  | 				i++ | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  | 		case 'direct': | ||||||
|  | 			for (const tl of obj) { | ||||||
|  | 				if (tl.domain != acct_id) continue | ||||||
|  | 				if (tl.type == 'dm') { | ||||||
|  | 					let voice = false | ||||||
|  | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
|  | 					ret.push({ | ||||||
|  | 						id: i, | ||||||
|  | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| @@ -1742,19 +1789,38 @@ function getTlMeta(type, data, num) { | |||||||
| 					let voice = false | 					let voice = false | ||||||
| 					let can = false | 					let can = false | ||||||
| 					if (columnData.name == data[1]) can = true | 					if (columnData.name == data[1]) can = true | ||||||
|  | 					//any | ||||||
| 					if (columnData.any.split(',').includes(data[1])) can = true | 					if (columnData.any.split(',').includes(data[1])) can = true | ||||||
|  | 					//all | ||||||
|  | 					const { tags } = status | ||||||
|  | 					if (columnData.all) can = true | ||||||
|  | 					for (const { name } of tags) { | ||||||
|  | 						if (!columnData.all.split(',').includes(name)) { | ||||||
|  | 							can = false | ||||||
|  | 							break | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					//none | ||||||
|  | 					if (columnData.none) can = true | ||||||
|  | 					for (const { name } of tags) { | ||||||
|  | 						if (columnData.none.split(',').includes(name)) { | ||||||
|  | 							can = false | ||||||
|  | 							break | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
| 					if (localStorage.getItem('voice_' + i)) voice = true | 					if (localStorage.getItem('voice_' + i)) voice = true | ||||||
| 					ret.push({ | 					ret.push({ | ||||||
| 						id: i, | 						id: i, | ||||||
| 						voice: voice | 						voice: voice, | ||||||
|  | 						type: tl.type, | ||||||
|  | 						acct_id: tl.domain | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				i++ | 				i++ | ||||||
| 			} | 			} | ||||||
| 			break; | 			break; | ||||||
| 		default: | 		default: | ||||||
| 			console.log(`Sorry, we are out of ${expr}.`); | 			console.error(`Cannot catch`); | ||||||
| 	} | 	} | ||||||
| 	return ret | 	return ret | ||||||
| } | } | ||||||
| @@ -248,25 +248,43 @@ function reload(type, cc, acct_id, tlid, data, mute, delc, voice, mode) { | |||||||
| 		}, 100) | 		}, 100) | ||||||
| 	} else { | 	} else { | ||||||
| 		var domain = localStorage.getItem('domain_' + acct_id) | 		var domain = localStorage.getItem('domain_' + acct_id) | ||||||
| 		if(mastodonBaseWs[domain] == 'cannnotopen') { | 		if(mastodonBaseWsStatus[domain] == 'cannnotopen') { | ||||||
| 			oldStreaming(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | 			oldStreaming(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | ||||||
| 		} else if(mastodonBaseWs[domain] == 'undetected') { | 		} else if(mastodonBaseWs[domain] == 'undetected') { | ||||||
| 			var mbws = setInterval(function () { | 			const mbws = setInterval(function () { | ||||||
| 				if(mastodonBaseWs[domain] == 'cannnotopen') { | 				if(mastodonBaseWsStatus[domain] == 'cannnotopen') { | ||||||
| 					oldStreaming(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | 					oldStreaming(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | ||||||
| 					clearInterval(mbws) | 					clearInterval(mbws) | ||||||
| 				} else if(mastodonBaseWs[domain] == 'available') { | 				} else if(mastodonBaseWsStatus[domain] == 'available') { | ||||||
| 					stremaingSubscribe(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | 					stremaingSubscribe(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | ||||||
| 					clearInterval(mbws) | 					clearInterval(mbws) | ||||||
| 				} | 				} | ||||||
| 			}, 1000) | 			}, 1000) | ||||||
| 		} else if(mastodonBaseWs[domain] == 'available') { | 		} else if(mastodonBaseWsStatus[domain] == 'available') { | ||||||
| 			stremaingSubscribe(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | 			stremaingSubscribe(type, cc, acct_id, tlid, data, mute, delc, voice, mode) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| function stremaingSubscribe(type, cc, acct_id, tlid, data, mute, delc, voice, mode) { | function stremaingSubscribe(type, cc, acct_id, tlid, data, mute, delc, voice, mode) { | ||||||
| 	 | 	let stream | ||||||
|  | 	const domain = localStorage.getItem('domain_' + acct_id) | ||||||
|  | 	if(type === 'local' || type === 'mix' ) { stream = 'public:local' } | ||||||
|  | 	else if(type === 'local-media' ) { stream = 'public:local:media' } | ||||||
|  | 	else if(type === 'pub' ) { stream = 'public' } | ||||||
|  | 	else if(type === 'pub-media' ) { stream = 'public:media' } | ||||||
|  | 	else if(type === 'list' ) {  | ||||||
|  | 		mastodonBaseWs[domain].send(`{"type":"subscribe","stream":"list","list":"${data}"}`) | ||||||
|  | 	}else if(type === 'tag' ) { | ||||||
|  | 		let arr = [] | ||||||
|  | 		let name = data | ||||||
|  | 		if(data.name) name = data.name | ||||||
|  | 		arr.push(name) | ||||||
|  | 		if(data.any) arr = arr.concat(data.any.split()) | ||||||
|  | 		if(data.all) arr = arr.concat(data.all.split()) | ||||||
|  | 		for(const tag of arr) { | ||||||
|  | 			mastodonBaseWs[domain].send(`{"type":"subscribe","stream":"hashtag","tag":"${tag}"}`) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| function oldStreaming(type, cc, acct_id, tlid, data, mute, delc, voice, mode) { | function oldStreaming(type, cc, acct_id, tlid, data, mute, delc, voice, mode) { | ||||||
| 	var misskey = false | 	var misskey = false | ||||||
|   | |||||||
| @@ -53,7 +53,7 @@ function parseColumn(target, dontclose) { | |||||||
| 			localStorage.setItem('prof_' + key, acct.prof) | 			localStorage.setItem('prof_' + key, acct.prof) | ||||||
| 			localStorage.setItem('domain_' + key, acct.domain) | 			localStorage.setItem('domain_' + key, acct.domain) | ||||||
| 			localStorage.setItem('acct_' + key + '_at', acct.at) | 			localStorage.setItem('acct_' + key + '_at', acct.at) | ||||||
| 			notf(key, 0) | 			mastodonBaseStreaming(key) | ||||||
| 			ckdb(key) | 			ckdb(key) | ||||||
| 			//フィルターデータ読もう | 			//フィルターデータ読もう | ||||||
| 			getFilter(key) | 			getFilter(key) | ||||||
| @@ -370,7 +370,7 @@ function parseColumn(target, dontclose) { | |||||||
| 				<div class="boxIn" id="timeline_box_${key}_box" tlid="${key}" data-acct="${acct.domain}" style="${addHeight}"> | 				<div class="boxIn" id="timeline_box_${key}_box" tlid="${key}" data-acct="${acct.domain}" style="${addHeight}"> | ||||||
| 					<div class="notice-box z-depth-2" id="menu_${key}" style="${insert}"> | 					<div class="notice-box z-depth-2" id="menu_${key}" style="${insert}"> | ||||||
| 						<div class="area-notice"> | 						<div class="area-notice"> | ||||||
| 							<i class="material-icons waves-effect ${isMisRed}" id="notice_icon_${key}" ${notf_attr} | 							<i class="material-icons waves-effect ${isMisRed} notice_icon_acct_${acct.domain}" id="notice_icon_${key}" ${notf_attr} | ||||||
| 								 style="font-size:40px; padding-top:25%;"  | 								 style="font-size:40px; padding-top:25%;"  | ||||||
| 								 onclick="checkStr('${acct.type}','${data}','${acct.domain}', '${key}', '${delc}','${voice}',null)" | 								 onclick="checkStr('${acct.type}','${data}','${acct.domain}', '${key}', '${delc}','${voice}',null)" | ||||||
| 							 	 title="${lang.lang_layout_gotop}" aria-hidden="true"> | 							 	 title="${lang.lang_layout_gotop}" aria-hidden="true"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	