558
procentric/application/lib/hoteltv.js
Executable file
558
procentric/application/lib/hoteltv.js
Executable file
@@ -0,0 +1,558 @@
|
||||
/**
|
||||
* Copyright (c) 2020
|
||||
*
|
||||
* CENTIRM HotelTV Core javascript.
|
||||
*
|
||||
* @summary short description for the file
|
||||
* @author Joel <joel.kim@centirm.com>
|
||||
*
|
||||
* Created at : 2020-11-26 02:21:56
|
||||
* Last modified : 2020-11-26 15:31:40
|
||||
*/
|
||||
|
||||
|
||||
var HotelTV = HotelTV || {
|
||||
'devinfo': {},
|
||||
'svrinfo': {},
|
||||
'services': {},
|
||||
'settings': {},
|
||||
'hotelinfo': {},
|
||||
'guestinfo': {},
|
||||
'opening': {},
|
||||
'flight': {},
|
||||
'weather': {},
|
||||
'epg': {},
|
||||
'news': {},
|
||||
'tvguide': {},
|
||||
'message': {},
|
||||
'translation': {},
|
||||
'state': {
|
||||
'lang': 'kr',
|
||||
'menu': {
|
||||
'stage': null,
|
||||
'main': {
|
||||
'cnt': 0,
|
||||
'cur': null,
|
||||
'prev': null,
|
||||
},
|
||||
},
|
||||
'media': {
|
||||
'playing': false,
|
||||
},
|
||||
'hotkey': {
|
||||
"mm": {}
|
||||
},
|
||||
'token': null
|
||||
},
|
||||
'carts': {
|
||||
'amenity': {
|
||||
|
||||
},
|
||||
'roomservice': {
|
||||
|
||||
}
|
||||
},
|
||||
'orders': {
|
||||
'amenity': {
|
||||
|
||||
},
|
||||
'roomservice': {
|
||||
|
||||
}
|
||||
},
|
||||
'dbginfo': {
|
||||
'en': false,
|
||||
'emulator': false, //PC 브라우저를 이용할때, true로 설정, 타겟 셋트에서는 false로 설정할것
|
||||
'hcap_ipc': false,
|
||||
'output': 'osd'
|
||||
},
|
||||
'media_hndl': null,
|
||||
};
|
||||
|
||||
|
||||
|
||||
HotelTV.namespace = function(ns_string) {
|
||||
var parts = ns_string.split('.'),
|
||||
parent = HotelTV,
|
||||
i; // 처음에 중복되는 전역 객체명은 제거한다.
|
||||
if (parts[0] == 'HotelTV') {
|
||||
parts = parts.slice(1);
|
||||
}
|
||||
for (i = 0; i < parts.length; i += 1) {
|
||||
if (typeof parent[parts[i]] == 'undefined') {
|
||||
parent[parts[i]] = {};
|
||||
}
|
||||
parent = parent[parts[i]];
|
||||
}
|
||||
return parent;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//// 비공개 매써드
|
||||
/**
|
||||
* HCAP Wrapper Function::> Get ProCentric Server Info(Realted HCAP API:hcap.system.getHotelMode)
|
||||
* @param {string} sz_key property key name.
|
||||
* @param {callback} callback is callback object
|
||||
*/
|
||||
function __fxn_Set_DebugOpt() {
|
||||
HotelTV.dbginfo['en'] = true;
|
||||
HotelTV.dbginfo['output'] = 'native';
|
||||
|
||||
HotelTV.hcap.Init();
|
||||
|
||||
/** Develop Mode definitions */
|
||||
console.log("OPT" + navigator.appVersion);
|
||||
if (navigator.appVersion.includes("Web0S") == true) {
|
||||
HotelTV.dbginfo['emulator'] = false;
|
||||
} else {
|
||||
HotelTV.dbginfo['emulator'] = true;
|
||||
}
|
||||
|
||||
if (HotelTV.dbginfo['output'] == 'osd') {
|
||||
if (HotelTV.dbginfo['en'] == true) {
|
||||
(function() {
|
||||
var old = console.log;
|
||||
var logger = document.getElementById('debugwin');
|
||||
var dateNow = new Date();
|
||||
var hours = dateNow.getHours();
|
||||
var minutes = dateNow.getMinutes();
|
||||
var seconds = dateNow.getSeconds();
|
||||
if (hours < 10) {
|
||||
hours = "0" + hours;
|
||||
}
|
||||
if (minutes < 10) {
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
if (seconds < 10) {
|
||||
seconds = "0" + seconds;
|
||||
}
|
||||
console.log = function(logmsg) {
|
||||
var _dispMsg = hours + ":" + minutes + ":" + seconds + "-> ";
|
||||
if (typeof logmsg == 'object') {
|
||||
_dispMsg += (JSON && JSON.stringify ? JSON.stringify(logmsg) : logmsg) + '<br />';
|
||||
} else {
|
||||
_dispMsg += logmsg + '<br />';
|
||||
}
|
||||
_dispMsg += logger.innerHTML;
|
||||
|
||||
logger.innerHTML = _dispMsg;
|
||||
}
|
||||
})();
|
||||
}
|
||||
} else {
|
||||
/** Set HCAP Debug Options */
|
||||
if (HotelTV.dbginfo['emulator'] == false) {
|
||||
//Enable/Disable browser debug mode
|
||||
HotelTV.hcap.SetBrowserDebug(HotelTV.dbginfo['hcap_ipc'], HotelTV.dbginfo['en']);
|
||||
} else {
|
||||
//Below for Non WebOS
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
HotelTV.Init = async function() {
|
||||
console.log("Start Initialization:: ");
|
||||
//STEP#01::> Shutdown Channel
|
||||
HotelTV.hcap.ChannelShutDown();
|
||||
|
||||
//SETP#02::> Set Debug Options
|
||||
__fxn_Set_DebugOpt();
|
||||
|
||||
//STEP#03::> Get Device Info
|
||||
HotelTV.ui_utils.SetBusy(true);
|
||||
try {
|
||||
await HotelTV.hcap.GetDevInfo();
|
||||
//await HotelTV.hcap.ShowDevInfo();
|
||||
} catch (_error) {
|
||||
console.log("Fail to get device information");
|
||||
}
|
||||
|
||||
//STEP#04::> API Module INIT
|
||||
await HotelTV.api.Init(HotelTV.svrinfo.ipaddr, HotelTV.svrinfo.port, HotelTV.devinfo.model_name.substring(2, HotelTV.devinfo.model_name.length - 1), HotelTV.devinfo.serial_number);
|
||||
|
||||
try {
|
||||
let __apiRet = await HotelTV.api.CheckRegistration();
|
||||
HotelTV.state['registration'] = await HotelTV.api.CheckRegistration();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for check registration");
|
||||
}
|
||||
|
||||
if (HotelTV.state['registration'].registered != true) {
|
||||
console.log("Show up and unregistered device");
|
||||
}
|
||||
|
||||
//STEP05::> CHECK HOTELTV LICENSE
|
||||
if (HotelTV.state['registration'].license == 'hotel-full') {
|
||||
// FULL LICENSE MODE
|
||||
try {
|
||||
HotelTV.settings = await HotelTV.api.GetSettings();
|
||||
let need_reboot = await HotelTV.hcap.SetDevice(HotelTV.settings);
|
||||
if (need_reboot == true) {
|
||||
console.log("System Need to Reboot");
|
||||
HotelTV.ui_utils.SetBusy(false);
|
||||
HotelTV.ui_utils.ShowErrMsg(true, "", "");
|
||||
setTimeout(HotelTV.hcap.Reboot(), 3000);
|
||||
return;
|
||||
}
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get stttings");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.hotelinfo = await HotelTV.api.GetHotelInfo();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get hotel info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.guestinfo = await HotelTV.api.GetGuestInfo();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get Guest info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.opening = await HotelTV.api.GetOpeningCtz();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get Opening Contents");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.weather = await HotelTV.api.GetWeather();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get Weather info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.flight = await HotelTV.api.GetFlight();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get flight info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.tvguide = await HotelTV.api.GetProgramCtz();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get tvguide info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.epg = await HotelTV.api.GetEpg();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get epg info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.news = await HotelTV.api.GetNews();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get news info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.message = await HotelTV.api.GetMessage();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get news info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.translation = await HotelTV.api.GetTranslation();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get translation info");
|
||||
}
|
||||
|
||||
|
||||
//Save to Session
|
||||
sessionStorage.setItem("devinfo", JSON.stringify(HotelTV.devinfo));
|
||||
sessionStorage.setItem("svrinfo", JSON.stringify(HotelTV.svrinfo));
|
||||
sessionStorage.setItem("settings", JSON.stringify(HotelTV.settings));
|
||||
sessionStorage.setItem("state", JSON.stringify(HotelTV.state));
|
||||
sessionStorage.setItem("hotelinfo", JSON.stringify(HotelTV.hotelinfo));
|
||||
sessionStorage.setItem("guestinfo", JSON.stringify(HotelTV.guestinfo));
|
||||
sessionStorage.setItem("opening", JSON.stringify(HotelTV.opening));
|
||||
sessionStorage.setItem("flight", JSON.stringify(HotelTV.flight));
|
||||
sessionStorage.setItem("weather", JSON.stringify(HotelTV.weather));
|
||||
sessionStorage.setItem("tvguide", JSON.stringify(HotelTV.tvguide));
|
||||
sessionStorage.setItem("news", JSON.stringify(HotelTV.news));
|
||||
sessionStorage.setItem("message", JSON.stringify(HotelTV.message));
|
||||
sessionStorage.setItem("translation", JSON.stringify(HotelTV.translation));
|
||||
|
||||
//Now Switch Over Welcome page
|
||||
if (HotelTV.opening != null) {
|
||||
$(".startup").fadeOut(1000, function() {
|
||||
window.location.replace('./wellcome.html');
|
||||
});
|
||||
} else {
|
||||
if (HotelTV.state['registration'].license == 'hotel-full') {
|
||||
setTimeout(function() {
|
||||
window.location.replace('./app_hoteltv_full.html');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("Display Error page for invalid license type.");
|
||||
}
|
||||
console.log("Initialization:: Done");
|
||||
HotelTV.ui_utils.SetBusy(false);
|
||||
|
||||
SpatialNavigation.init();
|
||||
};
|
||||
|
||||
HotelTV.LoadWelCome = async function() {
|
||||
HotelTV.devinfo = await JSON.parse(sessionStorage.getItem("devinfo"));
|
||||
HotelTV.svrinfo = await JSON.parse(sessionStorage.getItem("svrinfo"));
|
||||
HotelTV.state = await JSON.parse(sessionStorage.getItem("state"));
|
||||
//STEP#03:API Module INIT
|
||||
await HotelTV.api.Init(HotelTV.svrinfo.ipaddr, HotelTV.svrinfo.port, HotelTV.devinfo.model_name.substring(2, HotelTV.devinfo.model_name.length - 1), HotelTV.devinfo.serial_number);
|
||||
HotelTV.settings = await JSON.parse(sessionStorage.getItem("settings"));
|
||||
HotelTV.guestinfo = await JSON.parse(sessionStorage.getItem("guestinfo"));
|
||||
HotelTV.hotelinfo = await JSON.parse(sessionStorage.getItem("hotelinfo"));
|
||||
HotelTV.opening = await JSON.parse(sessionStorage.getItem("opening"));
|
||||
HotelTV.tvguide = await JSON.parse(sessionStorage.getItem("tvguide"));
|
||||
try {
|
||||
HotelTV.services = await HotelTV.api.GetServiceInfo();
|
||||
sessionStorage.setItem("services", JSON.stringify(HotelTV.services));
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get service info");
|
||||
}
|
||||
//HotelTV.translation = await JSON.parse(sessionStorage.getItem("translation"));
|
||||
try {
|
||||
HotelTV.translation = await HotelTV.api.GetTranslation();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get translation info");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Set checkined guest language
|
||||
HotelTV.state.lang = HotelTV.guestinfo.language;
|
||||
|
||||
let _res = HotelTV.devinfo.display_resolution.split("x");
|
||||
HotelTV.ui_welcome.Init(_res[0], _res[1]);
|
||||
|
||||
//await HotelTV.hcap.ShowDevInfo();
|
||||
HotelTV.ui_welcome.Show();
|
||||
}
|
||||
|
||||
HotelTV.UnloadWelCome = async function() {
|
||||
sessionStorage.setItem("state", JSON.stringify(HotelTV.state));
|
||||
HotelTV.ui_welcome.Close();
|
||||
if (HotelTV.state['registration'].license == 'hotel-full') {
|
||||
setTimeout(function() {
|
||||
window.location.replace('./app_hoteltv_full.html');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HotelTV.LoadAppFull = async function() {
|
||||
HotelTV.ui_utils.SetBusy(true);
|
||||
|
||||
//STEP#01: Load session info
|
||||
HotelTV.devinfo = await JSON.parse(sessionStorage.getItem("devinfo"));
|
||||
HotelTV.svrinfo = await JSON.parse(sessionStorage.getItem("svrinfo"));
|
||||
HotelTV.services = await JSON.parse(sessionStorage.getItem("services"));
|
||||
HotelTV.state = await JSON.parse(sessionStorage.getItem("state"));
|
||||
|
||||
//STEP#02: Init HotelTV API
|
||||
await HotelTV.api.Init(HotelTV.svrinfo.ipaddr, HotelTV.svrinfo.port, HotelTV.devinfo.model_name.substring(2, HotelTV.devinfo.model_name.length - 1), HotelTV.devinfo.serial_number);
|
||||
HotelTV.settings = await JSON.parse(sessionStorage.getItem("settings"));
|
||||
HotelTV.guestinfo = await JSON.parse(sessionStorage.getItem("guestinfo"));
|
||||
HotelTV.hotelinfo = await JSON.parse(sessionStorage.getItem("hotelinfo"));
|
||||
HotelTV.weather = await JSON.parse(sessionStorage.getItem("weather"));
|
||||
HotelTV.opening = await JSON.parse(sessionStorage.getItem("opening"));
|
||||
HotelTV.tvguide = await JSON.parse(sessionStorage.getItem("tvguide"));
|
||||
HotelTV.flight = await JSON.parse(sessionStorage.getItem("flight"));
|
||||
HotelTV.news = await JSON.parse(sessionStorage.getItem("news"));
|
||||
HotelTV.message = await JSON.parse(sessionStorage.getItem("message"));
|
||||
//HotelTV.epg = await JSON.parse(sessionStorage.getItem("epg"));
|
||||
|
||||
try {
|
||||
HotelTV.carts.amenity = await HotelTV.api.GetAmenityCarts(null);
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get amenity carts info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.carts.roomservice = await HotelTV.api.GetRoomserviceCarts();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get roomservicec carts info");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
HotelTV.orders.amenity = await HotelTV.api.GetAmenityOrders(null);
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get amenity order info");
|
||||
}
|
||||
|
||||
try {
|
||||
HotelTV.orders.roomservice = await HotelTV.api.GetRoomserviceOrders(null);
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get roomservice order info");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
HotelTV.translation = await HotelTV.api.GetTranslation();
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get translation info");
|
||||
}
|
||||
//HotelTV.translation = await JSON.parse(sessionStorage.getItem("translation"));
|
||||
|
||||
|
||||
//STEP#03: INIT HCAP for HotelTV FullAPP
|
||||
try {
|
||||
let siAppToken = null;
|
||||
let siAppName = null;
|
||||
// siApp Token Authentication
|
||||
let _progam = HotelTV.tvguide.program;
|
||||
for (let _i = 1; _i <= _progam.length; _i++) {
|
||||
if (_progam[_i].type == "application" && _progam[_i].service == "netflix") {
|
||||
siAppToken = await HotelTV.api.DownloadData(_progam[_i].license.file.download);
|
||||
siAppName = _progam[_i].service;
|
||||
//siAppToken = _progam[_i].service;
|
||||
|
||||
if (siAppName && siAppToken) {
|
||||
HotelTV.hcap.siAppAuth(siAppName, siAppToken, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_error) {
|
||||
console.log("Display Error page for get siApp Auth");
|
||||
}
|
||||
|
||||
//STEP#05: Set Display size
|
||||
let _res = HotelTV.devinfo.display_resolution.split("x");
|
||||
HotelTV.ui_appfull.Init(_res[0], _res[1]);
|
||||
HotelTV.hcap.PreAppGetInfo(false);
|
||||
|
||||
//STEP#06; Show UI
|
||||
//await HotelTV.hcap.ShowDevInfo();
|
||||
HotelTV.ui_appfull.Show();
|
||||
|
||||
//STEP#06 Register Service web worker
|
||||
if (window.SharedWorker) {
|
||||
const myWorker = new SharedWorker("./lib/hoteltv.service.js");
|
||||
|
||||
myWorker.port.postMessage({
|
||||
'cmd': "start_service",
|
||||
"param": {
|
||||
"token": HotelTV.state.token,
|
||||
"dev_family": HotelTV.devinfo.model_name.substring(2, HotelTV.devinfo.model_name.length - 1),
|
||||
"dev_snum": HotelTV.devinfo.serial_number,
|
||||
"svrinfo": HotelTV.svrinfo,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
myWorker.port.onmessage = function(e) {
|
||||
let recvMsg = e.data;
|
||||
//console.log(`Recv Msg[${recvMsg.event}]::> ${recvMsg.ret} ::> ${recvMsg.data}`);
|
||||
//console.log(`Recv Msg[${recvMsg.event}]::> ${recvMsg.ret}`);
|
||||
|
||||
// if (recvMsg.event == "update_epg") {
|
||||
// if (recvMsg.ret === "OK") {
|
||||
// HotelTV.epg = recvMsg.data;
|
||||
// HotelTV.ui_appfull.UpdateEpg();
|
||||
// }
|
||||
// } else if (recvMsg.event == "update_news") {
|
||||
// if (recvMsg.ret === "OK") {
|
||||
// HotelTV.news = recvMsg.data;
|
||||
// }
|
||||
// } else if (recvMsg.event == "update_flight") {
|
||||
// if (recvMsg.ret === "OK") {
|
||||
// let x2js = new X2JS();
|
||||
// _rspJson = x2js.xml2json($.parseXML(recvMsg.data));
|
||||
// HotelTV.flight = { "count": Number(_rspJson.response.body.totalCount), "info": _rspJson.response.body.items.item };
|
||||
// }
|
||||
// } else if (recvMsg.event == "update_weather") {
|
||||
|
||||
// } else if (recvMsg.event == "update_report") {
|
||||
// // HotelTV.hcap.GetUptime();
|
||||
// // HotelTV.hcap.GetDeviceUsage().then(_usage => {
|
||||
// // //console.log(`USAGE::> CPU[${_usage.cpu}%] RAM[${_usage.mem}%]`);
|
||||
// // //HotelTV.api.ReportBrief(HotelTV.devinfo, HotelTV.guestinfo, _usage);
|
||||
// // });
|
||||
// } else if (recvMsg.event == "mqtt_events") {
|
||||
// console.log(`MQTTMSG::> ${recvMsg.data}`);
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
console.log('Your browser doesn\'t support web workers.')
|
||||
}
|
||||
|
||||
//STEP#07 MQTT Service
|
||||
if (HotelTV.services) {
|
||||
if (HotelTV.services.mqtt) {
|
||||
let _mqttSvcInfo = HotelTV.services.mqtt;
|
||||
|
||||
if (_mqttSvcInfo.protocol == "ws") {
|
||||
var client = mqtt.connect(
|
||||
//'ws://' + HotelTV.svrinfo.ipaddr + ':9001',
|
||||
'ws://' + _mqttSvcInfo.host + ':' + _mqttSvcInfo.port, {
|
||||
clean: true, // retain session
|
||||
connectTimeout: 4000, // Timeout period
|
||||
username: _mqttSvcInfo.username,
|
||||
password: _mqttSvcInfo.password,
|
||||
clientId: 'mqtt_sid_' + HotelTV.devinfo.serial_number.substring(0, 12)
|
||||
}
|
||||
);
|
||||
|
||||
client.on('connect', function() {
|
||||
// let sz_topic = ['ctlxb0_' + HotelTV.devinfo.serial_number + '/topic', 'ctlxb0_all/topic'];
|
||||
|
||||
// client.subscribe(sz_topic[0], function(err) {
|
||||
// if (err) {
|
||||
// console.log(`Fail to subscribe::TOPIC[${sz_topic[0]}]`);
|
||||
// }
|
||||
// });
|
||||
|
||||
// client.subscribe(sz_topic[1], function(err) {
|
||||
// if (err) {
|
||||
// console.log(`Fail to subscribe::TOPIC[${sz_topic[1]}]`);
|
||||
// }
|
||||
// })
|
||||
|
||||
client.subscribe(_mqttSvcInfo.topic, function(err) {
|
||||
if (err) {
|
||||
console.log(`Fail to subscribe::TOPIC[${_mqttSvcInfo.topic}]`);
|
||||
} else {
|
||||
console.log(`Success to subscribe::TOPIC[${_mqttSvcInfo.topic}]`);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
client.on('message', function(topic, message) {
|
||||
let szMsg = message.toString();
|
||||
HotelTV.ui_appfull.MqttEvent(szMsg);
|
||||
//MQTT메시지 종료 하려면 아래 end호출
|
||||
//client.end()
|
||||
})
|
||||
|
||||
client.on('error', function(error) {
|
||||
// message is Buffer
|
||||
console.log(error.toString());
|
||||
//client.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
HotelTV.hcap.Test();
|
||||
}
|
||||
|
||||
HotelTV.UnloadAppFull = async function() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
HotelTV.Deinit = function() {
|
||||
console.log("Start De-Initialization:: ");
|
||||
//HotelTV.service.Deinit();
|
||||
}
|
||||
Reference in New Issue
Block a user