Added relay actions and relay via menu. Removed retransmit and alert actions as they are superseded by relay/reply dialog
This commit is contained in:
parent
3a9750015d
commit
e0c3592868
128
mainwindow.cpp
128
mainwindow.cpp
@ -328,6 +328,16 @@ namespace
|
|||||||
tempCursor.setCharFormat(charFormat);
|
tempCursor.setCharFormat(charFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
QList<T> listCopyReverse(QList<T> const &list){
|
||||||
|
QList<T> newList = QList<T>();
|
||||||
|
auto iter = list.end();
|
||||||
|
while(iter != list.begin()){
|
||||||
|
newList.append(*(--iter));
|
||||||
|
}
|
||||||
|
return newList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------- MainWindow constructor
|
//--------------------------------------------------- MainWindow constructor
|
||||||
@ -1214,6 +1224,11 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
|||||||
directedMenu->setDisabled(missingCallsign);
|
directedMenu->setDisabled(missingCallsign);
|
||||||
buildQueryMenu(directedMenu, selectedCall);
|
buildQueryMenu(directedMenu, selectedCall);
|
||||||
|
|
||||||
|
auto relayAction = buildRelayAction(selectedCall);
|
||||||
|
relayAction->setText(QString("Relay via %1...").arg(selectedCall));
|
||||||
|
relayAction->setDisabled(missingCallsign);
|
||||||
|
menu->addActions({ relayAction });
|
||||||
|
|
||||||
auto deselect = menu->addAction("Deselect");
|
auto deselect = menu->addAction("Deselect");
|
||||||
deselect->setDisabled(missingCallsign);
|
deselect->setDisabled(missingCallsign);
|
||||||
connect(deselect, &QAction::triggered, this, [this](){
|
connect(deselect, &QAction::triggered, this, [this](){
|
||||||
@ -1275,6 +1290,11 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
|||||||
directedMenu->setDisabled(missingCallsign);
|
directedMenu->setDisabled(missingCallsign);
|
||||||
buildQueryMenu(directedMenu, selectedCall);
|
buildQueryMenu(directedMenu, selectedCall);
|
||||||
|
|
||||||
|
auto relayAction = buildRelayAction(selectedCall);
|
||||||
|
relayAction->setText(QString("Relay via %1...").arg(selectedCall));
|
||||||
|
relayAction->setDisabled(missingCallsign || isAllCall);
|
||||||
|
menu->addActions({ relayAction });
|
||||||
|
|
||||||
auto deselect = menu->addAction("Deselect");
|
auto deselect = menu->addAction("Deselect");
|
||||||
deselect->setDisabled(missingCallsign);
|
deselect->setDisabled(missingCallsign);
|
||||||
connect(deselect, &QAction::triggered, this, [this](){
|
connect(deselect, &QAction::triggered, this, [this](){
|
||||||
@ -1356,16 +1376,30 @@ void MainWindow::initializeDummyData(){
|
|||||||
if(!QApplication::applicationName().contains("dummy")){
|
if(!QApplication::applicationName().contains("dummy")){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto dt = QDateTime::currentDateTimeUtc().addSecs(-300);
|
|
||||||
CallDetail cd1 = {};
|
|
||||||
cd1.call = "KN4CRD";
|
|
||||||
cd1.utcTimestamp = dt;
|
|
||||||
logCallActivity(cd1, false);
|
|
||||||
|
|
||||||
CallDetail cd2 = {};
|
QList<QString> calls = {
|
||||||
cd2.call = "KN4CRD/P";
|
"KN4CRD",
|
||||||
cd2.utcTimestamp = dt;
|
"KN4CRD/P",
|
||||||
logCallActivity(cd2, false);
|
"KC9QNE",
|
||||||
|
"KI6SSI",
|
||||||
|
"LB9YH",
|
||||||
|
"VE7/LB9YH",
|
||||||
|
"M0IAX",
|
||||||
|
"N0JDS",
|
||||||
|
"OH8STN",
|
||||||
|
"VA3OSO",
|
||||||
|
"VK1MIC",
|
||||||
|
"W0FW"
|
||||||
|
};
|
||||||
|
|
||||||
|
auto dt = QDateTime::currentDateTimeUtc().addSecs(-300);
|
||||||
|
|
||||||
|
foreach(auto call, calls){
|
||||||
|
CallDetail cd = {};
|
||||||
|
cd.call = call;
|
||||||
|
cd.utcTimestamp = dt;
|
||||||
|
logCallActivity(cd, false);
|
||||||
|
}
|
||||||
|
|
||||||
displayActivity(true);
|
displayActivity(true);
|
||||||
}
|
}
|
||||||
@ -5961,6 +5995,17 @@ bool MainWindow::isMessageQueuedForTransmit(){
|
|||||||
return m_transmitting || m_txFrameCount > 0;
|
return m_transmitting || m_txFrameCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::prependMessageText(QString text){
|
||||||
|
// don't add message text if we already have a transmission queued...
|
||||||
|
if(isMessageQueuedForTransmit()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c = QTextCursor(ui->extFreeTextMsgEdit->textCursor());
|
||||||
|
c.movePosition(QTextCursor::Start);
|
||||||
|
c.insertText(text);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::addMessageText(QString text, bool clear, bool selectFirstPlaceholder){
|
void MainWindow::addMessageText(QString text, bool clear, bool selectFirstPlaceholder){
|
||||||
// don't add message text if we already have a transmission queued...
|
// don't add message text if we already have a transmission queued...
|
||||||
if(isMessageQueuedForTransmit()){
|
if(isMessageQueuedForTransmit()){
|
||||||
@ -5971,7 +6016,6 @@ void MainWindow::addMessageText(QString text, bool clear, bool selectFirstPlaceh
|
|||||||
ui->extFreeTextMsgEdit->clear();
|
ui->extFreeTextMsgEdit->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: jsherer - check to make sure we're not transmitting currently
|
|
||||||
QTextCursor c = ui->extFreeTextMsgEdit->textCursor();
|
QTextCursor c = ui->extFreeTextMsgEdit->textCursor();
|
||||||
if(c.hasSelection()){
|
if(c.hasSelection()){
|
||||||
c.removeSelectedText();
|
c.removeSelectedText();
|
||||||
@ -7569,6 +7613,7 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
|
|||||||
addMessageText(QString("%1#[MESSAGE]").arg(selectedCall), true, true);
|
addMessageText(QString("%1#[MESSAGE]").arg(selectedCall), true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#if 0
|
||||||
auto retransmitAction = menu->addAction(QString("%1|[MESSAGE] - Please ACK and retransmit the following message").arg(call).trimmed());
|
auto retransmitAction = menu->addAction(QString("%1|[MESSAGE] - Please ACK and retransmit the following message").arg(call).trimmed());
|
||||||
retransmitAction->setDisabled(isAllCall);
|
retransmitAction->setDisabled(isAllCall);
|
||||||
connect(retransmitAction, &QAction::triggered, this, [this](){
|
connect(retransmitAction, &QAction::triggered, this, [this](){
|
||||||
@ -7580,8 +7625,9 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
|
|||||||
|
|
||||||
addMessageText(QString("%1|[MESSAGE]").arg(selectedCall), true, true);
|
addMessageText(QString("%1|[MESSAGE]").arg(selectedCall), true, true);
|
||||||
});
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
auto alertAction = menu->addAction(QString("%1![MESSAGE] - Please display this message in an alert dialog and ACK if acknowledged").arg(call).trimmed());
|
auto alertAction = menu->addAction(QString("%1>[MESSAGE] - Please (optionally relay) and display this message in an reply dialog").arg(call).trimmed());
|
||||||
alertAction->setDisabled(isAllCall);
|
alertAction->setDisabled(isAllCall);
|
||||||
connect(alertAction, &QAction::triggered, this, [this](){
|
connect(alertAction, &QAction::triggered, this, [this](){
|
||||||
|
|
||||||
@ -7590,7 +7636,7 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessageText(QString("%1![MESSAGE]").arg(selectedCall), true, true);
|
addMessageText(QString("%1>[MESSAGE]").arg(selectedCall), true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
@ -7770,16 +7816,18 @@ void MainWindow::buildRelayMenu(QMenu *menu){
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto call = cd.call;
|
menu->addAction(buildRelayAction(cd.call));
|
||||||
auto a = menu->addAction(call);
|
|
||||||
connect(a, &QAction::triggered, this, [this, call](){
|
|
||||||
auto c = ui->extFreeTextMsgEdit->textCursor();
|
|
||||||
c.movePosition(QTextCursor::Start);
|
|
||||||
c.insertText(QString("%1>").arg(call));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QAction* MainWindow::buildRelayAction(QString call){
|
||||||
|
QAction *a = new QAction(call);
|
||||||
|
connect(a, &QAction::triggered, this, [this, call](){
|
||||||
|
prependMessageText(QString("%1>").arg(call));
|
||||||
|
});
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::buildEditMenu(QMenu *menu, QTextEdit *edit){
|
void MainWindow::buildEditMenu(QMenu *menu, QTextEdit *edit){
|
||||||
bool hasSelection = !edit->textCursor().selectedText().isEmpty();
|
bool hasSelection = !edit->textCursor().selectedText().isEmpty();
|
||||||
|
|
||||||
@ -9431,21 +9479,23 @@ void MainWindow::processCommandActivity() {
|
|||||||
reply = lines.join('\n');
|
reply = lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
// PROCESS RETRANSMIT
|
// PROCESS RETRANSMIT
|
||||||
else if (d.cmd == "|" && !isAllCall) {
|
else if (d.cmd == "|" && !isAllCall) {
|
||||||
// TODO: jsherer - perhaps parse d.text and ensure it is a valid message as well as prefix it with our call...
|
// TODO: jsherer - perhaps parse d.text and ensure it is a valid message as well as prefix it with our call...
|
||||||
|
|
||||||
reply = QString("%1 ACK\n%2 DE %1").arg(d.from).arg(d.text);
|
reply = QString("%1 ACK\n%2 DE %1").arg(d.from).arg(d.text);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// PROCESS RELAY
|
// PROCESS RELAY
|
||||||
else if (d.cmd == ">" && !isAllCall) {
|
else if (d.cmd == ">" && !isAllCall) {
|
||||||
|
|
||||||
// 1. see if there are any more hops to process
|
// 1. see if there are any more hops to process
|
||||||
// 2. if so, forward
|
// 2. if so, forward
|
||||||
// 3. otherwise, display alert
|
// 3. otherwise, display alert & reply dialog
|
||||||
|
|
||||||
QString callToPattern = {R"((?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?(?<type>[> ]))\b)"};
|
QString callToPattern = {R"(^(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?(?<type>[> ]))\b)"};
|
||||||
QRegularExpression re(callToPattern);
|
QRegularExpression re(callToPattern);
|
||||||
auto text = d.text;
|
auto text = d.text;
|
||||||
auto match = re.match(text);
|
auto match = re.match(text);
|
||||||
@ -9568,6 +9618,7 @@ void MainWindow::processCommandActivity() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
// PROCESS ALERT
|
// PROCESS ALERT
|
||||||
else if (d.cmd == "!" && !isAllCall) {
|
else if (d.cmd == "!" && !isAllCall) {
|
||||||
|
|
||||||
@ -9577,6 +9628,7 @@ void MainWindow::processCommandActivity() {
|
|||||||
// make sure this is explicit
|
// make sure this is explicit
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (reply.isEmpty()) {
|
if (reply.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
@ -9599,18 +9651,34 @@ void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QStr
|
|||||||
QMessageBox * msgBox = new QMessageBox(this);
|
QMessageBox * msgBox = new QMessageBox(this);
|
||||||
msgBox->setIcon(QMessageBox::Information);
|
msgBox->setIcon(QMessageBox::Information);
|
||||||
|
|
||||||
|
QList<QString> calls = listCopyReverse<QString>(from.split(">"));
|
||||||
|
auto fromLabel = calls.join(" via ");
|
||||||
|
|
||||||
|
calls.removeLast();
|
||||||
|
|
||||||
|
QString fromReplace = QString{};
|
||||||
|
foreach(auto call, calls){
|
||||||
|
fromReplace.append(" DE ");
|
||||||
|
fromReplace.append(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto text = d.text;
|
||||||
|
if(!fromReplace.isEmpty()){
|
||||||
|
text = text.replace(fromReplace, "");
|
||||||
|
}
|
||||||
|
|
||||||
auto header = QString("Message from %3 at %1 (%2):");
|
auto header = QString("Message from %3 at %1 (%2):");
|
||||||
header = header.arg(d.utcTimestamp.time().toString());
|
header = header.arg(d.utcTimestamp.time().toString());
|
||||||
header = header.arg(d.freq);
|
header = header.arg(d.freq);
|
||||||
header = header.arg(d.from);
|
header = header.arg(fromLabel);
|
||||||
msgBox->setText(header);
|
msgBox->setText(header);
|
||||||
msgBox->setInformativeText(d.text);
|
msgBox->setInformativeText(text);
|
||||||
|
|
||||||
auto ab = msgBox->addButton("ACK", QMessageBox::AcceptRole);
|
auto ab = msgBox->addButton("ACK", QMessageBox::AcceptRole);
|
||||||
auto rb = msgBox->addButton("Reply", QMessageBox::AcceptRole);
|
auto rb = msgBox->addButton("Reply", QMessageBox::AcceptRole);
|
||||||
auto db = msgBox->addButton("Discard", QMessageBox::NoRole);
|
auto db = msgBox->addButton("Discard", QMessageBox::NoRole);
|
||||||
|
|
||||||
connect(msgBox, &QMessageBox::buttonClicked, this, [this, cmd, from, d, db, rb, ab](QAbstractButton * btn) {
|
connect(msgBox, &QMessageBox::buttonClicked, this, [this, cmd, from, fromLabel, d, db, rb, ab](QAbstractButton * btn) {
|
||||||
if (btn == db) {
|
if (btn == db) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -9622,7 +9690,7 @@ void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QStr
|
|||||||
if(btn == rb){
|
if(btn == rb){
|
||||||
auto diag = new MessageReplyDialog(this);
|
auto diag = new MessageReplyDialog(this);
|
||||||
diag->setWindowTitle("Message Reply");
|
diag->setWindowTitle("Message Reply");
|
||||||
diag->setLabel(QString("Message to send to %1:").arg(from));
|
diag->setLabel(QString("Message to send to %1:").arg(fromLabel));
|
||||||
|
|
||||||
connect(diag, &MessageReplyDialog::accepted, this, [this, diag, from, cmd, d](){
|
connect(diag, &MessageReplyDialog::accepted, this, [this, diag, from, cmd, d](){
|
||||||
enqueueMessage(PriorityHigh, QString("%1%2%3").arg(from).arg(cmd).arg(diag->textValue()), d.freq, nullptr);
|
enqueueMessage(PriorityHigh, QString("%1%2%3").arg(from).arg(cmd).arg(diag->textValue()), d.freq, nullptr);
|
||||||
@ -9747,16 +9815,6 @@ void MainWindow::displayActivity(bool force) {
|
|||||||
m_rxDisplayDirty = false;
|
m_rxDisplayDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
QList<T> listCopyReverse(QList<T> const &list){
|
|
||||||
QList<T> newList = QList<T>();
|
|
||||||
auto iter = list.end();
|
|
||||||
while(iter != list.begin()){
|
|
||||||
newList.append(*(--iter));
|
|
||||||
}
|
|
||||||
return newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::displayBandActivity() {
|
void MainWindow::displayBandActivity() {
|
||||||
auto now = QDateTime::currentDateTimeUtc();
|
auto now = QDateTime::currentDateTimeUtc();
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ public slots:
|
|||||||
void writeNoticeTextToUI(QDateTime date, QString text);
|
void writeNoticeTextToUI(QDateTime date, QString text);
|
||||||
int writeMessageTextToUI(QDateTime date, QString text, int freq, bool bold, int block=-1);
|
int writeMessageTextToUI(QDateTime date, QString text, int freq, bool bold, int block=-1);
|
||||||
bool isMessageQueuedForTransmit();
|
bool isMessageQueuedForTransmit();
|
||||||
|
void prependMessageText(QString text);
|
||||||
void addMessageText(QString text, bool clear=false, bool selectFirstPlaceholder=false);
|
void addMessageText(QString text, bool clear=false, bool selectFirstPlaceholder=false);
|
||||||
void enqueueMessage(int priority, QString message, int freq, Callback c);
|
void enqueueMessage(int priority, QString message, int freq, Callback c);
|
||||||
void resetMessage();
|
void resetMessage();
|
||||||
@ -269,6 +270,7 @@ private slots:
|
|||||||
void buildCallActivitySortByMenu(QMenu * menu);
|
void buildCallActivitySortByMenu(QMenu * menu);
|
||||||
void buildQueryMenu(QMenu *, QString callsign);
|
void buildQueryMenu(QMenu *, QString callsign);
|
||||||
void buildRelayMenu(QMenu *menu);
|
void buildRelayMenu(QMenu *menu);
|
||||||
|
QAction* buildRelayAction(QString call);
|
||||||
void buildEditMenu(QMenu *, QTextEdit *);
|
void buildEditMenu(QMenu *, QTextEdit *);
|
||||||
void on_queryButton_pressed();
|
void on_queryButton_pressed();
|
||||||
void on_macrosMacroButton_pressed();
|
void on_macrosMacroButton_pressed();
|
||||||
|
14
varicode.cpp
14
varicode.cpp
@ -46,8 +46,8 @@ QMap<QString, int> directed_cmds = {
|
|||||||
{"$", 3 }, // query station(s) heard
|
{"$", 3 }, // query station(s) heard
|
||||||
{"^", 4 }, // query grid
|
{"^", 4 }, // query grid
|
||||||
{">", 5 }, // relay message
|
{">", 5 }, // relay message
|
||||||
{"|", 6 }, // retransmit message
|
//{"|", 6 }, // retransmit message
|
||||||
{"!", 7 }, // alert message
|
//{"!", 7 }, // alert message
|
||||||
{"#", 8 }, // all or nothing message
|
{"#", 8 }, // all or nothing message
|
||||||
|
|
||||||
// {"=", 9 }, // unused
|
// {"=", 9 }, // unused
|
||||||
@ -78,14 +78,14 @@ QMap<QString, int> directed_cmds = {
|
|||||||
{" ", 31 }, // send freetext
|
{" ", 31 }, // send freetext
|
||||||
};
|
};
|
||||||
|
|
||||||
QSet<int> allowed_cmds = {0, 1, 2, 3, 4, 5, 6, 7, 8, /*...*/ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, /*24,*/ 25, 26, 27, 28, 29, 30, 31};
|
QSet<int> allowed_cmds = {0, 1, 2, 3, 4, 5, /*6,*/ /*7,*/ 8, /*...*/ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, /*24,*/ 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
|
||||||
QSet<int> buffered_cmds = {5, 6, 7, 8, 13, 14, 15};
|
QSet<int> buffered_cmds = {5, /*6,*/ /*7,*/ 8, 13, 14, 15};
|
||||||
|
|
||||||
QMap<int, int> checksum_cmds = {
|
QMap<int, int> checksum_cmds = {
|
||||||
{ 5, 16 },
|
{ 5, 16 },
|
||||||
{ 6, 16 },
|
/*{ 6, 16 },*/
|
||||||
{ 7, 16 },
|
/*{ 7, 16 },*/
|
||||||
{ 8, 32 },
|
{ 8, 32 },
|
||||||
{ 13, 16 },
|
{ 13, 16 },
|
||||||
{ 14, 16 },
|
{ 14, 16 },
|
||||||
@ -93,7 +93,7 @@ QMap<int, int> checksum_cmds = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QString callsign_pattern = QString("(?<callsign>[A-Z0-9/]+)");
|
QString callsign_pattern = QString("(?<callsign>[A-Z0-9/]+)");
|
||||||
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|GRID|APRS[:]|QSO|[?@&$%|!#^> ]))?");
|
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|GRID|APRS[:]|QSO|[?@&$%#^> ]))?");
|
||||||
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
|
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
|
||||||
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
|
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
|
||||||
QString optional_num_pattern = QString("(?<num>(?<=SNR|HEARING)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
|
QString optional_num_pattern = QString("(?<num>(?<=SNR|HEARING)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
|
||||||
|
Loading…
Reference in New Issue
Block a user