1 /* 2 SPDX-License-Identifier: GPL-2.0-or-later 3 4 SPDX-FileCopyrightText: 2002 Dario Abatianni <eisfuchs@tigress.com> 5 SPDX-FileCopyrightText: 2005 Ismail Donmez <ismail@kde.org> 6 SPDX-FileCopyrightText: 2005 Peter Simonsson <psn@linux.se> 7 SPDX-FileCopyrightText: 2005 John Tapsell <johnflux@gmail.com> 8 SPDX-FileCopyrightText: 2005-2009 Eike Hein <hein@kde.org> 9 */ 10 11 #include "outputfilter.h" 12 13 #include "ircview.h" 14 #include "application.h" 15 #include "mainwindow.h" 16 #include "channel.h" 17 #include "awaymanager.h" 18 #include "ignore.h" 19 #include "server.h" 20 #include "scriptlauncher.h" 21 #include "irccharsets.h" 22 #include "query.h" 23 #include "viewcontainer.h" 24 #include "outputfilterresolvejob.h" 25 #include "konversation_log.h" 26 #ifdef HAVE_QCA2 27 #include "cipher.h" 28 #endif 29 30 #include <KPasswordDialog> 31 #include <KMessageBox> 32 #include <kxmlgui_version.h> 33 34 #include <QStringList> 35 #include <QFile> 36 #include <QMetaMethod> 37 #include <QRegularExpression> 38 #include <QTextCodec> 39 #include <QByteArray> 40 #include <QTextStream> 41 #include <QTextDocument> 42 #include <QDebug> 43 44 QDebug operator<<(QDebug d, QTextDocument* document); 45 46 namespace Konversation 47 { 48 QSet<QString> OutputFilter::m_commands; 49 fillCommandList()50 void OutputFilter::fillCommandList() 51 { 52 if (!m_commands.isEmpty()) 53 return; 54 55 QString methodSignature; 56 57 for (int i = OutputFilter::staticMetaObject.methodOffset(); 58 i < OutputFilter::staticMetaObject.methodCount(); ++i) 59 { 60 methodSignature = QString::fromLatin1(OutputFilter::staticMetaObject.method(i).methodSignature()); 61 62 if (methodSignature.startsWith(QLatin1String("command_"))) 63 m_commands << methodSignature.mid(8).section(QLatin1Char('('), 0, 0).toLower(); 64 } 65 } 66 OutputFilter(Server * server)67 OutputFilter::OutputFilter(Server* server) 68 : QObject(server) 69 { 70 m_server = server; 71 72 fillCommandList(); 73 } 74 ~OutputFilter()75 OutputFilter::~OutputFilter() 76 { 77 } 78 79 // replace all aliases in the string and return true if anything got replaced at all replaceAliases(QString & line,ChatWindow * context)80 bool OutputFilter::replaceAliases(QString& line, ChatWindow* context) 81 { 82 const QStringList aliasList = Preferences::self()->aliasList(); 83 84 for(const QString& alias : aliasList) { 85 // cut alias pattern from definition 86 QString aliasPattern(alias.section(QLatin1Char(' '), 0, 0)); 87 88 // cut first word from command line, so we do not wrongly find an alias 89 // that starts with the same letters, like /m would override /me 90 QString lineStart = line.section(QLatin1Char(' '), 0, 0); 91 92 // pattern found? 93 if (lineStart == Preferences::self()->commandChar() + aliasPattern) 94 { 95 QString aliasReplace = alias.section(QLatin1Char(' '),1); 96 97 if (context) 98 aliasReplace = context->getServer()->parseWildcards(aliasReplace, context); 99 100 if (!alias.contains(QLatin1String("%p"))) 101 aliasReplace.append(QLatin1Char(' ') + line.section(QLatin1Char(' '), 1)); 102 103 // protect "%%" 104 aliasReplace.replace(QStringLiteral("%%"),QStringLiteral("%\x01")); 105 // replace %p placeholder with rest of line 106 aliasReplace.replace(QStringLiteral("%p"), line.section(QLatin1Char(' '), 1)); 107 // restore "%<1>" as "%%" 108 aliasReplace.replace(QStringLiteral("%\x01"),QStringLiteral("%%")); 109 // modify line 110 line=aliasReplace; 111 // return "replaced" 112 return true; 113 } 114 } 115 116 return false; 117 } 118 splitForEncoding(const QString & destination,const QString & inputLine,int max,int segments)119 QStringList OutputFilter::splitForEncoding(const QString& destination, const QString& inputLine, 120 int max, int segments) 121 { 122 int sublen = 0; //The encoded length since the last split 123 int charLength = 0; //the length of this char 124 int lastBreakPoint = 0; 125 126 //FIXME should we run this through the encoder first, checking with "canEncode"? 127 QString text = inputLine; // the text we'll send, currently in Unicode 128 QStringList finals; // The strings we're going to output 129 130 QString channelCodecName = Preferences::channelEncoding(m_server->getDisplayName(), destination); 131 //Get the codec we're supposed to use. This must not fail. (not verified) 132 QTextCodec* codec; 133 134 // I copied this bit straight out of Server::send 135 if (channelCodecName.isEmpty()) 136 { 137 codec = m_server->getIdentity()->getCodec(); 138 } 139 else 140 { 141 codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName); 142 } 143 144 Q_ASSERT(codec); 145 int index = 0; 146 147 while (index < text.length() && (segments == -1 || finals.size() < segments-1)) 148 { 149 // The most important bit - turn the current char into a QCString so we can measure it 150 QByteArray ch = codec->fromUnicode(QString(text[index])); 151 charLength = ch.length(); 152 153 // If adding this char puts us over the limit: 154 if (charLength + sublen > max) 155 { 156 if (lastBreakPoint != 0) 157 { 158 finals.append(text.left(lastBreakPoint + 1)); 159 text = text.mid(lastBreakPoint + 1); 160 } 161 else 162 { 163 finals.append(text.left(index)); 164 text = text.mid(index); 165 } 166 167 lastBreakPoint = 0; 168 sublen = 0; 169 index = 0; 170 } 171 else if (text[index].isSpace() || text[index].isPunct()) 172 { 173 lastBreakPoint = index; 174 } 175 176 ++index; 177 sublen += charLength; 178 } 179 180 if (!text.isEmpty()) 181 { 182 finals.append(text); 183 } 184 185 return finals; 186 } 187 checkForEncodingConflict(QString * line,const QString & target)188 bool OutputFilter::checkForEncodingConflict(QString *line, const QString& target) 189 { 190 QTextCodec* codec = nullptr; 191 QString encoding; 192 QString oldLine(*line); 193 if(m_server->getServerGroup()) 194 encoding = Preferences::channelEncoding(m_server->getServerGroup()->id(), target); 195 else 196 encoding = Preferences::channelEncoding(m_server->getDisplayName(), target); 197 198 if(encoding.isEmpty()) 199 codec = m_server->getIdentity()->getCodec(); 200 else 201 codec = Konversation::IRCCharsets::self()->codecForName(encoding); 202 203 QTextCodec::ConverterState state; 204 205 QString newLine; 206 if(codec) 207 newLine = codec->toUnicode(codec->fromUnicode(oldLine.constData(),oldLine.length(),&state)); 208 209 if(!newLine.isEmpty() && state.invalidChars) 210 { 211 int ret = KMessageBox::Continue; 212 213 ret = KMessageBox::warningContinueCancel(m_server->getViewContainer()->getWindow(), 214 i18n("The message you are sending includes characters " 215 "that do not exist in your current encoding. If " 216 "you choose to continue anyway those characters " 217 "will be replaced by a '?'."), 218 i18n("Encoding Conflict Warning"), 219 KStandardGuiItem::cont(), 220 KStandardGuiItem::cancel(), 221 QStringLiteral("WarnEncodingConflict")); 222 223 *line = newLine; //set so we show it as it's sent 224 if (ret != KMessageBox::Continue) return true; 225 } 226 227 return false; 228 } 229 parse(const QString & myNick,const QString & originalLine,const QString & destination,ChatWindow * inputContext)230 OutputFilterResult OutputFilter::parse(const QString& myNick, const QString& originalLine, 231 const QString& destination, ChatWindow* inputContext) 232 { 233 OutputFilterInput input; 234 input.myNick = myNick; 235 input.destination = destination; 236 input.context = inputContext; 237 238 OutputFilterResult result; 239 240 QString inputLine(originalLine); 241 242 if (inputLine.isEmpty() || inputLine == QLatin1Char('\n') || checkForEncodingConflict(&inputLine, destination)) 243 return result; 244 245 //Protect against nickserv auth being sent as a message on the off chance 246 // someone didn't notice leading spaces 247 { 248 QString testNickServ(inputLine.trimmed()); 249 if(testNickServ.startsWith(Preferences::self()->commandChar() + QLatin1String("nickserv"), Qt::CaseInsensitive) 250 || testNickServ.startsWith(Preferences::self()->commandChar() + QLatin1String("ns"), Qt::CaseInsensitive)) 251 { 252 inputLine = testNickServ; 253 } 254 } 255 256 //perform variable expansion according to prefs 257 inputLine = Konversation::doVarExpansion(inputLine); 258 259 QString line = inputLine.toLower(); 260 261 // Convert double command chars at the beginning to single ones 262 if(line.startsWith(Preferences::self()->commandChar() + Preferences::self()->commandChar()) && !destination.isEmpty()) 263 { 264 inputLine.remove(0, 1); 265 goto BYPASS_COMMAND_PARSING; 266 } 267 // Server command? 268 else if (line.startsWith(Preferences::self()->commandChar())) 269 { 270 const QString command = inputLine.section(QLatin1Char(' '), 0, 0).mid(1).toLower(); 271 input.parameter = inputLine.section(QLatin1Char(' '), 1); 272 273 if (command != QLatin1String("topic")) 274 input.parameter = input.parameter.trimmed(); 275 276 if (supportedCommands().contains(command)) 277 { 278 QString methodSignature(QLatin1String("command_") + command); 279 280 QMetaObject::invokeMethod(this, 281 methodSignature.toLatin1().constData(), 282 Qt::DirectConnection, 283 Q_RETURN_ARG(OutputFilterResult, result), 284 Q_ARG(OutputFilterInput, input)); 285 } 286 // Forward unknown commands to server 287 else 288 { 289 result.toServer = inputLine.mid(1); 290 result.type = Message; 291 } 292 } 293 // Ordinary message to channel/query? 294 else if (!destination.isEmpty()) 295 { 296 BYPASS_COMMAND_PARSING: 297 298 const QStringList outputList = splitForEncoding(destination, inputLine, 299 m_server->getPreLength(QStringLiteral("PRIVMSG"), destination)); 300 301 if (outputList.count() > 1) 302 { 303 result.output.clear(); 304 result.outputList = outputList; 305 result.toServerList.reserve(outputList.size()); 306 for (const QString& out : outputList) { 307 result.toServerList += QLatin1String("PRIVMSG ") + destination + QLatin1String(" :") + out; 308 } 309 } 310 else 311 { 312 result.output = inputLine; 313 result.toServer = QLatin1String("PRIVMSG ") + destination + QLatin1String(" :") + inputLine; 314 } 315 316 result.type = Message; 317 } 318 // Eveything else goes to the server unchanged 319 else 320 { 321 result.toServer = inputLine; 322 result.output = inputLine; 323 result.typeString = i18n("Raw"); 324 result.type = Program; 325 } 326 327 return result; 328 } 329 command_op(const OutputFilterInput & input)330 OutputFilterResult OutputFilter::command_op(const OutputFilterInput& input) 331 { 332 return changeMode(input.parameter, input.destination, 'o', '+'); 333 } 334 command_deop(const OutputFilterInput & input)335 OutputFilterResult OutputFilter::command_deop(const OutputFilterInput& input) 336 { 337 return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), 338 input.destination, 'o', '-'); 339 } 340 command_hop(const OutputFilterInput & input)341 OutputFilterResult OutputFilter::command_hop(const OutputFilterInput& input) 342 { 343 return changeMode(input.parameter, input.destination, 'h', '+'); 344 } 345 command_dehop(const OutputFilterInput & input)346 OutputFilterResult OutputFilter::command_dehop(const OutputFilterInput& input) 347 { 348 return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination, 349 'h', '-'); 350 } 351 command_voice(const OutputFilterInput & input)352 OutputFilterResult OutputFilter::command_voice(const OutputFilterInput& input) 353 { 354 return changeMode(input.parameter, input.destination, 'v', '+'); 355 } 356 command_unvoice(const OutputFilterInput & input)357 OutputFilterResult OutputFilter::command_unvoice(const OutputFilterInput& input) 358 { 359 return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination, 360 'v', '-'); 361 } 362 command_devoice(const OutputFilterInput & input)363 OutputFilterResult OutputFilter::command_devoice(const OutputFilterInput& input) 364 { 365 return command_unvoice(input); 366 } 367 command_join(const OutputFilterInput & _input)368 OutputFilterResult OutputFilter::command_join(const OutputFilterInput& _input) 369 { 370 OutputFilterInput input(_input); 371 OutputFilterResult result; 372 373 if (input.parameter.contains(QLatin1Char(','))) // Protect against #foo,0 tricks 374 input.parameter.remove(QStringLiteral(",0")); 375 //else if(channelName == "0") // FIXME IRC RFC 2812 section 3.2.1 376 377 if (input.parameter.isEmpty()) 378 { 379 if (input.destination.isEmpty() || !isAChannel(input.destination)) 380 return usage(i18n("Usage: %1JOIN <channel> [password]", 381 Preferences::self()->commandChar())); 382 383 input.parameter = input.destination; 384 } 385 else if (!isAChannel(input.parameter)) 386 input.parameter = QLatin1Char('#') + input.parameter.trimmed(); 387 388 Channel* channel = m_server->getChannelByName(input.parameter); 389 390 if (channel) 391 { 392 // Note that this relies on the channels-flush-nicklists-on-disconnect behavior. 393 if (!channel->numberOfNicks()) 394 result.toServer = QLatin1String("JOIN ") + input.parameter; 395 396 if (channel->isJoined()) 397 Q_EMIT showView(channel); 398 } 399 else 400 result.toServer = QLatin1String("JOIN ") + input.parameter; 401 402 return result; 403 } 404 command_j(const OutputFilterInput & input)405 OutputFilterResult OutputFilter::command_j(const OutputFilterInput& input) 406 { 407 return command_join(input); 408 } 409 command_kick(const OutputFilterInput & input)410 OutputFilterResult OutputFilter::command_kick(const OutputFilterInput& input) 411 { 412 OutputFilterResult result; 413 414 if (isAChannel(input.destination)) 415 { 416 // get nick to kick 417 const QString victim = input.parameter.left(input.parameter.indexOf(QLatin1Char(' '))); 418 419 if (victim.isEmpty()) 420 result = usage(i18n("Usage: %1KICK <nick> [reason]", Preferences::self()->commandChar())); 421 else 422 { 423 // get kick reason (if any) 424 QString reason = input.parameter.mid(victim.length() + 1); 425 426 // if no reason given, take default reason 427 if (reason.isEmpty()) 428 reason = m_server->getIdentity()->getKickReason(); 429 430 result.toServer = QLatin1String("KICK ") + input.destination + QLatin1Char(' ') + victim + QLatin1String(" :") + reason; 431 } 432 } 433 else 434 result = error(i18n("%1KICK only works from within channels.", 435 Preferences::self()->commandChar())); 436 437 return result; 438 } 439 command_part(const OutputFilterInput & input)440 OutputFilterResult OutputFilter::command_part(const OutputFilterInput& input) 441 { 442 OutputFilterResult result; 443 444 // No parameter, try default part message 445 if (input.parameter.isEmpty()) 446 { 447 // But only if we actually are in a channel 448 if (isAChannel(input.destination)) 449 result.toServer = QLatin1String("PART ") + input.destination + QLatin1String(" :") + 450 m_server->getIdentity()->getPartReason(); 451 else if (m_server->getQueryByName(input.destination)) 452 m_server->closeQuery(input.destination); 453 else 454 result = error(i18n("%1PART and %1LEAVE without parameters only work from within a " 455 "channel or a query.", Preferences::self()->commandChar())); 456 } 457 else 458 { 459 // part a given channel 460 if (isAChannel(input.parameter)) 461 { 462 // get channel name 463 const QString channel = input.parameter.left(input.parameter.indexOf(QLatin1Char(' '))); 464 // get part reason (if any) 465 QString reason = input.parameter.mid(channel.length() + 1); 466 467 // if no reason given, take default reason 468 if (reason.isEmpty()) 469 reason = m_server->getIdentity()->getPartReason(); 470 471 result.toServer = QLatin1String("PART ") + channel + QLatin1String(" :") + reason; 472 } 473 // part this channel with a given reason 474 else 475 { 476 if (isAChannel(input.destination)) 477 result.toServer = QLatin1String("PART ") + input.destination + QLatin1String(" :") + input.parameter; 478 else 479 result = error(i18n("%1PART without channel name only works from within a channel.", 480 Preferences::self()->commandChar())); 481 } 482 } 483 484 return result; 485 } 486 command_leave(const OutputFilterInput & input)487 OutputFilterResult OutputFilter::command_leave(const OutputFilterInput& input) 488 { 489 return command_part(input); 490 } 491 command_topic(const OutputFilterInput & input)492 OutputFilterResult OutputFilter::command_topic(const OutputFilterInput& input) 493 { 494 OutputFilterResult result; 495 496 // No parameter, try to get current topic 497 if (input.parameter.isEmpty()) 498 { 499 // But only if we actually are in a channel 500 if (isAChannel(input.destination)) 501 result.toServer = QLatin1String("TOPIC ") + input.destination; 502 else 503 result = error(i18n("%1TOPIC without parameters only works from within a channel.", 504 Preferences::self()->commandChar())); 505 } 506 else 507 { 508 // retrieve or set topic of a given channel 509 if (isAChannel(input.parameter)) 510 { 511 // get channel name 512 const QString channel = input.parameter.left(input.parameter.indexOf(QLatin1Char(' '))); 513 // get topic (if any) 514 QString topic = input.parameter.mid(channel.length()+1); 515 // if no topic given, retrieve topic 516 if (topic.isEmpty()) 517 m_server->requestTopic(channel); 518 519 // otherwise set topic there 520 else 521 { 522 result.toServer = QLatin1String("TOPIC ") + channel + QLatin1String(" :"); 523 //If we get passed a ^A as a topic its a sign we should clear the topic. 524 //Used to be a \n, but those get smashed by QStringList::split and readded later 525 //Now is not the time to fight with that. FIXME 526 //If anyone out there *can* actually set the topic to a single ^A, now they have to 527 //specify it twice to get one. 528 if (topic == QLatin1String("\x01\x01")) 529 result.toServer += QLatin1Char('\x01'); 530 else if (topic != QLatin1String("\x01")) 531 result.toServer += topic; 532 } 533 } 534 // set this channel's topic 535 else 536 { 537 if (isAChannel(input.destination)) 538 result.toServer = QLatin1String("TOPIC ") + input.destination + QLatin1String(" :") + input.parameter; 539 else 540 result = error(i18n("%1TOPIC without channel name only works from within a channel.", 541 Preferences::self()->commandChar())); 542 } 543 } 544 545 return result; 546 } 547 command_away(const OutputFilterInput & input)548 OutputFilterResult OutputFilter::command_away(const OutputFilterInput& input) 549 { 550 if (input.parameter.isEmpty() && m_server->isAway()) 551 m_server->requestUnaway(); 552 else 553 m_server->requestAway(input.parameter); 554 555 return OutputFilterResult(); 556 } 557 command_unaway(const OutputFilterInput &)558 OutputFilterResult OutputFilter::command_unaway(const OutputFilterInput& /* input */) 559 { 560 m_server->requestUnaway(); 561 562 return OutputFilterResult(); 563 } 564 command_back(const OutputFilterInput & input)565 OutputFilterResult OutputFilter::command_back(const OutputFilterInput& input) 566 { 567 return command_unaway(input); 568 } 569 command_aaway(const OutputFilterInput & input)570 OutputFilterResult OutputFilter::command_aaway(const OutputFilterInput& input) 571 { 572 Application::instance()->getAwayManager()->requestAllAway(input.parameter); 573 574 return OutputFilterResult(); 575 } 576 command_aunaway(const OutputFilterInput &)577 OutputFilterResult OutputFilter::command_aunaway(const OutputFilterInput& /* input */) 578 { 579 Application::instance()->getAwayManager()->requestAllUnaway(); 580 581 return OutputFilterResult(); 582 } 583 command_aback(const OutputFilterInput & input)584 OutputFilterResult OutputFilter::command_aback(const OutputFilterInput& input) 585 { 586 return command_aunaway(input); 587 } 588 command_names(const OutputFilterInput & input)589 OutputFilterResult OutputFilter::command_names(const OutputFilterInput& input) 590 { 591 OutputFilterResult result; 592 593 result.toServer = QStringLiteral("NAMES "); 594 595 if (input.parameter.isNull()) 596 result = error(i18n("%1NAMES with no target may disconnect you from the server. Specify " 597 "'*' if you really want this.", Preferences::self()->commandChar())); 598 else if (input.parameter != QLatin1Char('*')) 599 result.toServer.append(input.parameter); 600 601 return result; 602 } 603 command_close(const OutputFilterInput & _input)604 OutputFilterResult OutputFilter::command_close(const OutputFilterInput& _input) 605 { 606 OutputFilterInput input(_input); 607 OutputFilterResult result; 608 609 if (input.parameter.isEmpty()) 610 input.parameter = input.destination; 611 612 if (isAChannel(input.parameter) && m_server->getChannelByName(input.parameter)) 613 m_server->getChannelByName(input.parameter)->closeYourself(false); 614 else if (m_server->getQueryByName(input.parameter)) 615 m_server->getQueryByName(input.parameter)->closeYourself(false); 616 else if (input.parameter.isEmpty()) // this can only mean one thing.. we're in the Server tab 617 m_server->closeYourself(false); 618 else 619 result = usage(i18n("Usage: %1close [window] closes the named channel or query tab, " 620 "or the current tab if none specified.", 621 Preferences::self()->commandChar())); 622 623 return result; 624 } 625 command_reconnect(const OutputFilterInput & input)626 OutputFilterResult OutputFilter::command_reconnect(const OutputFilterInput& input) 627 { 628 Q_EMIT reconnectServer(input.parameter); 629 630 return OutputFilterResult(); 631 } 632 command_disconnect(const OutputFilterInput & input)633 OutputFilterResult OutputFilter::command_disconnect(const OutputFilterInput& input) 634 { 635 Q_EMIT disconnectServer(input.parameter); 636 637 return OutputFilterResult(); 638 } 639 command_quit(const OutputFilterInput & input)640 OutputFilterResult OutputFilter::command_quit(const OutputFilterInput& input) 641 { 642 Q_EMIT disconnectServer(input.parameter); 643 644 return OutputFilterResult(); 645 } 646 command_notice(const OutputFilterInput & input)647 OutputFilterResult OutputFilter::command_notice(const OutputFilterInput& input) 648 { 649 OutputFilterResult result; 650 651 const QString recipient = input.parameter.left(input.parameter.indexOf(QLatin1Char(' '))); 652 QString message = input.parameter.mid(recipient.length()+1); 653 654 if (input.parameter.isEmpty() || message.isEmpty()) 655 result = usage(i18n("Usage: %1NOTICE <recipient> <message>", 656 Preferences::self()->commandChar())); 657 else 658 { 659 result.typeString = i18n("Notice"); 660 result.toServer = QLatin1String("NOTICE ") + recipient + QLatin1String(" :") + message; 661 result.output=i18nc("%1 is the message, %2 the recipient nickname", 662 "Sending notice \"%1\" to %2.", message, recipient); 663 result.type = Program; 664 } 665 666 return result; 667 } 668 command_me(const OutputFilterInput & input)669 OutputFilterResult OutputFilter::command_me(const OutputFilterInput& input) 670 { 671 OutputFilterResult result; 672 673 if (input.destination.isEmpty()) 674 return error(i18n("%1ME only works from a channel, query or DCC chat tab.", Preferences::self()->commandChar())); 675 else if (input.parameter.isEmpty()) 676 return usage(i18n("Usage: %1ME text", Preferences::self()->commandChar())); 677 678 QString command = QStringLiteral("PRIVMSGACTION \x01\x01"); 679 680 QStringList outputList = splitForEncoding(input.destination, input.parameter, 681 m_server->getPreLength(command, input.destination), 2); 682 683 if (outputList.count() > 1) 684 { 685 command = QStringLiteral("PRIVMSG"); 686 687 outputList += splitForEncoding(input.destination, outputList.at(1), 688 m_server->getPreLength(command, input.destination)); 689 690 outputList.removeAt(1); 691 692 result.output.clear(); 693 result.outputList = outputList; 694 695 for (int i = 0; i < outputList.count(); ++i) 696 { 697 if (i == 0) 698 result.toServerList += QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :\x01""ACTION ") + outputList.at(i) + QLatin1Char('\x01'); 699 else 700 result.toServerList += QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :") + outputList.at(i); 701 } 702 } 703 else 704 { 705 result.output = input.parameter; 706 result.toServer = QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :\x01""ACTION ") + 707 input.parameter + QLatin1Char('\x01'); 708 } 709 710 result.type = Action; 711 712 return result; 713 } 714 command_msg(const OutputFilterInput & input)715 OutputFilterResult OutputFilter::command_msg(const OutputFilterInput& input) 716 { 717 return handleMsg(input.parameter, false); 718 } 719 command_m(const OutputFilterInput & input)720 OutputFilterResult OutputFilter::command_m(const OutputFilterInput& input) 721 { 722 return command_msg(input); 723 } 724 command_query(const OutputFilterInput & input)725 OutputFilterResult OutputFilter::command_query(const OutputFilterInput& input) 726 { 727 return handleMsg(input.parameter, true); 728 } 729 handleMsg(const QString & parameter,bool commandIsQuery)730 OutputFilterResult OutputFilter::handleMsg(const QString& parameter, bool commandIsQuery) 731 { 732 OutputFilterResult result; 733 734 const QString recipient = parameter.section(QLatin1Char(' '), 0, 0, QString::SectionSkipEmpty); 735 const QString message = parameter.section(QLatin1Char(' '), 1); 736 QString output; 737 738 bool recipientIsAChannel = false; 739 740 if (recipient.isEmpty()) 741 return error(i18n("You need to specify a recipient.")); 742 else 743 recipientIsAChannel = m_server->isAChannel(recipient); 744 745 if (commandIsQuery && recipientIsAChannel) 746 return error(i18n("You cannot open queries to channels.")); 747 748 if (message.trimmed().isEmpty()) 749 { 750 // Empty result - we don't want to send any message to the server. 751 if (!commandIsQuery) 752 return error(i18n("You need to specify a message.")); 753 } 754 else 755 { 756 output = message; 757 758 if (message.startsWith(Preferences::self()->commandChar() + QLatin1String("me"))) 759 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""ACTION ") + 760 message.mid(4) + QLatin1Char('\x01'); 761 else 762 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :") + message; 763 } 764 765 // If this is a /query, always open a query window. 766 // Treat "/msg nick" as "/query nick". 767 if (commandIsQuery || output.isEmpty()) 768 { 769 // Note we have to be a bit careful here. 770 // We only want to create ('obtain') a new nickinfo if we have done /query 771 // or "/msg nick". Not "/msg nick message". 772 NickInfoPtr nickInfo = m_server->obtainNickInfo(recipient); 773 ::Query* query = m_server->addQuery(nickInfo, true /*we initiated*/); 774 775 // Force focus if the user did not specify any message. 776 if (output.isEmpty() && Preferences::self()->focusNewQueries()) Q_EMIT showView(query); 777 } 778 779 // Result should be completely empty; 780 if (output.isEmpty()) return result; 781 782 //FIXME: Don't do below line if query is focused. 783 result.output = output; 784 result.typeString = recipient; 785 result.type = PrivateMessage; 786 787 return result; 788 } 789 command_smsg(const OutputFilterInput & input)790 OutputFilterResult OutputFilter::command_smsg(const OutputFilterInput& input) 791 { 792 OutputFilterResult result; 793 794 const QString recipient = input.parameter.left(input.parameter.indexOf(QLatin1Char(' '))); 795 QString message = input.parameter.mid(recipient.length() + 1); 796 797 if (message.startsWith(Preferences::self()->commandChar() + QLatin1String("me"))) 798 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""ACTION ") + 799 message.mid(4) + QLatin1Char('\x01'); 800 else 801 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :") + message; 802 803 return result; 804 } 805 command_ping(const OutputFilterInput & input)806 OutputFilterResult OutputFilter::command_ping(const OutputFilterInput& input) 807 { 808 return handleCtcp(input.parameter.section(QLatin1Char(' '), 0, 0) + QLatin1String(" ping")); 809 } 810 command_ctcp(const OutputFilterInput & input)811 OutputFilterResult OutputFilter::command_ctcp(const OutputFilterInput& input) 812 { 813 return handleCtcp(input.parameter); 814 } 815 handleCtcp(const QString & parameter)816 OutputFilterResult OutputFilter::handleCtcp(const QString& parameter) 817 { 818 OutputFilterResult result; 819 // who is the recipient? 820 const QString recipient = parameter.section(QLatin1Char(' '), 0, 0); 821 // what is the first word of the ctcp? 822 const QString request = parameter.section(QLatin1Char(' '), 1, 1, QString::SectionSkipEmpty).toUpper(); 823 // what is the complete ctcp command? 824 const QString message = parameter.section(QLatin1Char(' '), 2, 0xffffff, QString::SectionSkipEmpty); 825 826 QString out = request; 827 828 if (!message.isEmpty()) 829 out+= QLatin1Char(' ') + message; 830 831 if (request == QLatin1String("PING")) 832 { 833 unsigned int timeT = QDateTime::currentDateTime().toSecsSinceEpoch(); 834 result.toServer = QStringLiteral("PRIVMSG %1 :\x01PING %2\x01").arg(recipient).arg(timeT); 835 result.output = i18n("Sending CTCP-%1 request to %2.", QStringLiteral("PING"), recipient); 836 } 837 else 838 { 839 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01") + out + QLatin1Char('\x01'); 840 result.output = i18n("Sending CTCP-%1 request to %2.", out, recipient); 841 } 842 843 result.typeString = i18n("CTCP"); 844 result.type = Program; 845 846 return result; 847 } 848 command_ame(const OutputFilterInput & input)849 OutputFilterResult OutputFilter::command_ame(const OutputFilterInput& input) 850 { 851 if (input.parameter.isEmpty()) 852 return usage(i18n("Usage: %1AME [-LOCAL] text", Preferences::self()->commandChar())); 853 854 if (isParameter(QStringLiteral("local"), input.parameter.section(QLatin1Char(' '), 0, 0))) 855 m_server->sendToAllChannelsAndQueries(Preferences::self()->commandChar() + QLatin1String("me ") + input.parameter.section(QLatin1Char(' '), 1)); 856 else 857 Q_EMIT multiServerCommand(QStringLiteral("me"), input.parameter); 858 859 return OutputFilterResult(); 860 } 861 command_amsg(const OutputFilterInput & input)862 OutputFilterResult OutputFilter::command_amsg(const OutputFilterInput& input) 863 { 864 if (input.parameter.isEmpty()) 865 return usage(i18n("Usage: %1AMSG [-LOCAL] text", Preferences::self()->commandChar())); 866 867 if (isParameter(QStringLiteral("local"), input.parameter.section(QLatin1Char(' '), 0, 0))) 868 m_server->sendToAllChannelsAndQueries(input.parameter.section(QLatin1Char(' '), 1)); 869 else 870 Q_EMIT multiServerCommand(QStringLiteral("msg"), input.parameter); 871 872 return OutputFilterResult(); 873 } 874 command_omsg(const OutputFilterInput & input)875 OutputFilterResult OutputFilter::command_omsg(const OutputFilterInput& input) 876 { 877 if (input.parameter.isEmpty()) 878 return usage(i18n("Usage: %1OMSG text", Preferences::self()->commandChar())); 879 880 OutputFilterResult result; 881 882 result.toServer = QLatin1String("PRIVMSG @") + input.destination + QLatin1String(" :") + input.parameter; 883 884 return result; 885 } 886 command_onotice(const OutputFilterInput & input)887 OutputFilterResult OutputFilter::command_onotice(const OutputFilterInput& input) 888 { 889 OutputFilterResult result; 890 891 if (!input.parameter.isEmpty()) 892 { 893 result.toServer = QLatin1String("NOTICE @") + input.destination + QLatin1String(" :") + input.parameter; 894 result.typeString = i18n("Notice"); 895 result.type = Program; 896 result.output = i18n("Sending notice \"%1\" to %2.", input.parameter, input.destination); 897 } 898 else 899 result = usage(i18n("Usage: %1ONOTICE text", Preferences::self()->commandChar())); 900 901 return result; 902 } 903 command_quote(const OutputFilterInput & input)904 OutputFilterResult OutputFilter::command_quote(const OutputFilterInput& input) 905 { 906 OutputFilterResult result; 907 908 if (input.parameter.isEmpty()) 909 result = usage(i18n("Usage: %1QUOTE command list", Preferences::self()->commandChar())); 910 else 911 result.toServer = input.parameter; 912 913 return result; 914 } 915 command_say(const OutputFilterInput & input)916 OutputFilterResult OutputFilter::command_say(const OutputFilterInput& input) 917 { 918 OutputFilterResult result; 919 920 if (input.parameter.isEmpty()) 921 result = usage(i18n("Usage: %1SAY text", Preferences::self()->commandChar())); 922 else 923 { 924 result.toServer = QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :") + input.parameter; 925 result.output = input.parameter; 926 } 927 928 return result; 929 } 930 command_dcc(const OutputFilterInput & _input)931 OutputFilterResult OutputFilter::command_dcc(const OutputFilterInput& _input) 932 { 933 OutputFilterInput input(_input); 934 OutputFilterResult result; 935 936 qCDebug(KONVERSATION_LOG) << input.parameter; 937 // No parameter, just open DCC panel 938 if (input.parameter.isEmpty()) 939 { 940 Q_EMIT addDccPanel(); 941 } 942 else 943 { 944 const QStringList parameterList = input.parameter.replace(QLatin1String("\\ "), QLatin1String("%20")).split(QLatin1Char(' ')); 945 946 QString dccType = parameterList[0].toLower(); 947 948 //TODO close should not just refer to the gui-panel, let it close connections 949 if (dccType == QLatin1String("close")) 950 { 951 Q_EMIT closeDccPanel(); 952 } 953 else if (dccType == QLatin1String("send")) 954 { 955 if (parameterList.count() == 1) // DCC SEND 956 { 957 Q_EMIT requestDccSend(); 958 } 959 else if (parameterList.count() == 2) // DCC SEND <nickname> 960 { 961 Q_EMIT requestDccSend(parameterList[1]); 962 } 963 else if (parameterList.count() > 2) // DCC SEND <nickname> <file> [file] ... 964 { 965 // TODO: make sure this will work: 966 //output=i18n("Usage: %1DCC SEND nickname [filename] [filename] ...").arg(commandChar); 967 QUrl fileURL(parameterList[2]); 968 969 //We could easily check if the remote file exists, but then we might 970 //end up asking for creditionals twice, so settle for only checking locally 971 if (!fileURL.isLocalFile() || QFile::exists(fileURL.path())) 972 { 973 Q_EMIT openDccSend(parameterList[1],fileURL); 974 } 975 else 976 { 977 result = error(i18n("File \"%1\" does not exist.", parameterList[2])); 978 } 979 } 980 else // Don't know how this should happen, but ... 981 result = usage(i18n("Usage: %1DCC [SEND [nickname [filename]]]", 982 Preferences::self()->commandChar())); 983 } 984 else if (dccType == QLatin1String("get")) 985 { 986 //dcc get [nick [file]] 987 switch (parameterList.count()) 988 { 989 case 1: 990 Q_EMIT acceptDccGet(QString(),QString()); 991 break; 992 case 2: 993 Q_EMIT acceptDccGet(parameterList.at(1),QString()); 994 break; 995 case 3: 996 Q_EMIT acceptDccGet(parameterList.at(1),parameterList.at(2)); 997 break; 998 default: 999 result = usage(i18n("Usage: %1DCC [GET [nickname [filename]]]", 1000 Preferences::self()->commandChar())); 1001 } 1002 } 1003 else if (dccType == QLatin1String("chat")) 1004 { 1005 //dcc chat nick 1006 switch (parameterList.count()) 1007 { 1008 case 1: 1009 Q_EMIT openDccChat(QString()); 1010 break; 1011 case 2: 1012 Q_EMIT openDccChat(parameterList[1]); 1013 break; 1014 default: 1015 result = usage(i18n("Usage: %1DCC [CHAT [nickname]]", 1016 Preferences::self()->commandChar())); 1017 } 1018 } 1019 else if (dccType == QLatin1String("whiteboard")) 1020 { 1021 //dcc whiteboard nick 1022 switch (parameterList.count()) 1023 { 1024 case 1: 1025 Q_EMIT openDccWBoard(QString()); 1026 break; 1027 case 2: 1028 Q_EMIT openDccWBoard(parameterList[1]); 1029 break; 1030 default: 1031 result = usage(i18n("Usage: %1DCC [WHITEBOARD [nickname]]", 1032 Preferences::self()->commandChar())); 1033 } 1034 } 1035 else 1036 result = error(i18n("Unrecognized command %1DCC %2. Possible commands are SEND, " 1037 "CHAT, CLOSE, GET, WHITEBOARD.", 1038 Preferences::self()->commandChar(), parameterList[0])); 1039 } 1040 1041 return result; 1042 } 1043 sendRequest(const QString & recipient,const QString & fileName,const QString & address,quint16 port,quint64 size)1044 OutputFilterResult OutputFilter::sendRequest(const QString &recipient, const QString &fileName, 1045 const QString &address, quint16 port, quint64 size) 1046 { 1047 OutputFilterResult result; 1048 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC SEND ") 1049 + fileName 1050 + QLatin1Char(' ') + address + QLatin1Char(' ') + QString::number(port) + QLatin1Char(' ') 1051 + QString::number(size) + QLatin1Char('\x01'); 1052 1053 return result; 1054 } 1055 passiveSendRequest(const QString & recipient,const QString & fileName,const QString & address,quint64 size,const QString & token)1056 OutputFilterResult OutputFilter::passiveSendRequest(const QString& recipient, 1057 const QString &fileName, 1058 const QString &address, 1059 quint64 size, const QString &token) 1060 { 1061 OutputFilterResult result; 1062 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC SEND ") 1063 + fileName 1064 + QLatin1Char(' ') + address + QLatin1String(" 0 ") + QString::number(size) + QLatin1Char(' ') 1065 + token + QLatin1Char('\x01'); 1066 1067 return result; 1068 } 1069 1070 // Accepting Resume Request acceptResumeRequest(const QString & recipient,const QString & fileName,quint16 port,quint64 startAt)1071 OutputFilterResult OutputFilter::acceptResumeRequest(const QString &recipient, 1072 const QString &fileName, 1073 quint16 port, quint64 startAt) 1074 { 1075 OutputFilterResult result; 1076 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC ACCEPT ") + fileName + QLatin1Char(' ') + 1077 QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char('\x01'); 1078 1079 return result; 1080 } 1081 1082 // Accepting Passive Resume Request acceptPassiveResumeRequest(const QString & recipient,const QString & fileName,quint16 port,quint64 startAt,const QString & token)1083 OutputFilterResult OutputFilter::acceptPassiveResumeRequest(const QString &recipient, 1084 const QString &fileName, 1085 quint16 port, quint64 startAt, 1086 const QString &token) 1087 { 1088 OutputFilterResult result; 1089 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC ACCEPT ") + fileName + QLatin1Char(' ') + 1090 QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char(' ') + 1091 token + QLatin1Char('\x01'); 1092 1093 return result; 1094 } 1095 1096 // Requesting Resume Request resumeRequest(const QString & sender,const QString & fileName,quint16 port,KIO::filesize_t startAt)1097 OutputFilterResult OutputFilter::resumeRequest(const QString &sender, const QString &fileName, 1098 quint16 port, KIO::filesize_t startAt) 1099 { 1100 OutputFilterResult result; 1101 result.toServer = QLatin1String("PRIVMSG ") + sender + QLatin1String(" :\x01""DCC RESUME ") + fileName + QLatin1Char(' ') + 1102 QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char('\x01'); 1103 1104 return result; 1105 } 1106 1107 // Requesting Passive Resume Request resumePassiveRequest(const QString & sender,const QString & fileName,quint16 port,KIO::filesize_t startAt,const QString & token)1108 OutputFilterResult OutputFilter::resumePassiveRequest(const QString &sender, 1109 const QString &fileName, 1110 quint16 port, KIO::filesize_t startAt, 1111 const QString &token) 1112 { 1113 OutputFilterResult result; 1114 result.toServer = QLatin1String("PRIVMSG ") + sender + QLatin1String(" :\x01""DCC RESUME ") + fileName + QLatin1Char(' ') 1115 + QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char(' ') 1116 + token + QLatin1Char('\x01'); 1117 1118 return result; 1119 } 1120 1121 // Accept Passive Send Request, there active doesn't need that acceptPassiveSendRequest(const QString & recipient,const QString & fileName,const QString & address,quint16 port,quint64 size,const QString & token)1122 OutputFilterResult OutputFilter::acceptPassiveSendRequest(const QString& recipient, 1123 const QString &fileName, 1124 const QString &address, 1125 quint16 port, quint64 size, 1126 const QString &token) 1127 { 1128 OutputFilterResult result; 1129 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC SEND ") 1130 + fileName 1131 + QLatin1Char(' ') + address + QLatin1Char(' ') + QString::number(port) 1132 + QLatin1Char(' ') + QString::number(size) + QLatin1Char(' ') + token + QLatin1Char('\x01'); 1133 1134 return result; 1135 } 1136 1137 // Rejecting quened dcc receive, is not send when abort an active send rejectDccSend(const QString & partnerNick,const QString & fileName)1138 OutputFilterResult OutputFilter::rejectDccSend(const QString& partnerNick, const QString & fileName) 1139 { 1140 OutputFilterResult result; 1141 result.toServer = QLatin1String("NOTICE ") + partnerNick + QLatin1String(" :\x01""DCC REJECT SEND ") 1142 + fileName + QLatin1Char('\x01'); 1143 1144 return result; 1145 } 1146 rejectDccChat(const QString & partnerNick,const QString & extension)1147 OutputFilterResult OutputFilter::rejectDccChat(const QString & partnerNick, const QString& extension) 1148 { 1149 OutputFilterResult result; 1150 result.toServer = QLatin1String("NOTICE ") + partnerNick + QLatin1String(" :\x01""DCC REJECT CHAT ") + extension.toUpper() + QLatin1Char('\x01'); 1151 1152 return result; 1153 } 1154 requestDccChat(const QString & partnerNick,const QString & extension,const QString & numericalOwnIp,quint16 ownPort)1155 OutputFilterResult OutputFilter::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort) 1156 { 1157 OutputFilterResult result; 1158 result.toServer = QLatin1String("PRIVMSG ") + partnerNick + QLatin1String(" :\x01""DCC CHAT ") + 1159 extension.toUpper() + QLatin1Char(' ') + numericalOwnIp + QLatin1Char(' ') + 1160 QString::number(ownPort) + QLatin1Char('\x01'); 1161 return result; 1162 } 1163 passiveChatRequest(const QString & recipient,const QString & extension,const QString & address,const QString & token)1164 OutputFilterResult OutputFilter::passiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token) 1165 { 1166 OutputFilterResult result; 1167 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC CHAT ") + 1168 extension.toUpper() + QLatin1Char(' ') + address + QLatin1String(" 0 ") + 1169 token + QLatin1Char('\x01'); 1170 return result; 1171 } 1172 acceptPassiveChatRequest(const QString & recipient,const QString & extension,const QString & numericalOwnIp,quint16 ownPort,const QString & token)1173 OutputFilterResult OutputFilter::acceptPassiveChatRequest(const QString& recipient, const QString& extension, const QString& numericalOwnIp, quint16 ownPort, const QString& token) 1174 { 1175 OutputFilterResult result; 1176 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC CHAT ") + 1177 extension.toUpper() + QLatin1Char(' ') + numericalOwnIp + QLatin1Char(' ') + QString::number(ownPort) + QLatin1Char(' ') + token + QLatin1Char('\x01'); 1178 return result; 1179 } 1180 command_invite(const OutputFilterInput & input)1181 OutputFilterResult OutputFilter::command_invite(const OutputFilterInput& input) 1182 { 1183 OutputFilterResult result; 1184 1185 if (input.parameter.isEmpty()) 1186 result = usage(i18n("Usage: %1INVITE <nick> [channel]", Preferences::self()->commandChar())); 1187 else 1188 { 1189 const QString nick = input.parameter.section(QLatin1Char(' '), 0, 0, QString::SectionSkipEmpty); 1190 QString channel = input.parameter.section(QLatin1Char(' '), 1, 1, QString::SectionSkipEmpty); 1191 1192 if (channel.isEmpty()) 1193 { 1194 if (isAChannel(input.destination)) 1195 channel = input.destination; 1196 else 1197 result = error(i18n("%1INVITE without channel name works only from within channels.", 1198 Preferences::self()->commandChar())); 1199 } 1200 1201 if (!channel.isEmpty()) 1202 { 1203 if (isAChannel(channel)) 1204 result.toServer = QLatin1String("INVITE ") + nick + QLatin1Char(' ') + channel; 1205 else 1206 result = error(i18n("%1 is not a channel.", channel)); 1207 } 1208 } 1209 1210 return result; 1211 } 1212 command_exec(const OutputFilterInput & input)1213 OutputFilterResult OutputFilter::command_exec(const OutputFilterInput& input) 1214 { 1215 OutputFilterResult result; 1216 1217 if (input.parameter.isEmpty()) 1218 result = usage(i18n("Usage: %1EXEC [-SHOWPATH] <script> [parameter list]", 1219 Preferences::self()->commandChar())); 1220 else 1221 { 1222 QStringList parameterList = input.parameter.split(QLatin1Char(' ')); 1223 1224 if (parameterList.length() >= 2 && isParameter(QStringLiteral("showpath"), parameterList[0]) && !parameterList[1].isEmpty()) 1225 { 1226 result = info(i18nc("%2 is a filesystem path to the script file", 1227 "The script file '%1' was found at: %2", 1228 parameterList[1], 1229 ScriptLauncher::scriptPath(parameterList[1]))); 1230 } 1231 else if (!parameterList[0].contains(QLatin1String("../"))) 1232 Q_EMIT launchScript(m_server->connectionId(), input.destination, input.parameter); 1233 else 1234 result = error(i18n("The script name may not contain \"../\".")); 1235 } 1236 1237 return result; 1238 } 1239 command_raw(const OutputFilterInput & input)1240 OutputFilterResult OutputFilter::command_raw(const OutputFilterInput& input) 1241 { 1242 OutputFilterResult result; 1243 1244 if (input.parameter.isEmpty() || input.parameter == QLatin1String("open")) 1245 Q_EMIT openRawLog(true); 1246 else if (input.parameter == QLatin1String("close")) 1247 Q_EMIT closeRawLog(); 1248 else 1249 result = usage(i18n("Usage: %1RAW [OPEN | CLOSE]", Preferences::self()->commandChar())); 1250 1251 return result; 1252 } 1253 command_notify(const OutputFilterInput & input)1254 OutputFilterResult OutputFilter::command_notify(const OutputFilterInput& input) 1255 { 1256 OutputFilterResult result; 1257 1258 int serverGroupId = -1; 1259 1260 if (m_server->getServerGroup()) 1261 serverGroupId = m_server->getServerGroup()->id(); 1262 1263 QStringList added; 1264 QStringList removed; 1265 1266 if (!input.parameter.isEmpty() && serverGroupId != -1) 1267 { 1268 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 1269 const QStringList list = input.parameter.split(QLatin1Char(' '), QString::SkipEmptyParts); 1270 #else 1271 const QStringList list = input.parameter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 1272 #endif 1273 1274 for (const QString& parameter : list) { 1275 // Try to remove current pattern. 1276 if (!Preferences::removeNotify(serverGroupId, parameter)) 1277 { 1278 // If remove failed, try to add it instead. 1279 if (Preferences::addNotify(serverGroupId, parameter)) 1280 added << parameter; 1281 } 1282 else 1283 removed << parameter; 1284 } 1285 } 1286 1287 QString list = Preferences::notifyListByGroupId(serverGroupId).join(QLatin1String(", ")); 1288 1289 if (list.isEmpty()) 1290 { 1291 if (!removed.isEmpty()) 1292 result.output = i18nc("%1 = Comma-separated list of nicknames", 1293 "The notify list has been emptied (removed %1).", 1294 removed.join(QLatin1String(", "))); 1295 else 1296 result.output = i18n("The notify list is currently empty."); 1297 } 1298 else 1299 { 1300 if (!added.isEmpty() && removed.isEmpty()) 1301 result.output = i18nc("%1 and %2 = Comma-separated lists of nicknames", 1302 "Current notify list: %1 (added %2).", 1303 list, added.join(QLatin1String(", "))); 1304 else if (!removed.isEmpty() && added.isEmpty()) 1305 result.output = i18nc("%1 and %2 = Comma-separated lists of nicknames", 1306 "Current notify list: %1 (removed %2).", 1307 list, removed.join(QLatin1String(", "))); 1308 else if (!added.isEmpty() && !removed.isEmpty()) 1309 result.output = i18nc("%1, %2 and %3 = Comma-separated lists of nicknames", 1310 "Current notify list: %1 (added %2, removed %3).", 1311 list, added.join(QLatin1String(", ")), removed.join(QLatin1String(", "))); 1312 else 1313 result.output = i18nc("%1 = Comma-separated list of nicknames", 1314 "Current notify list: %1.", 1315 list); 1316 } 1317 1318 result.type = Program; 1319 result.typeString = i18nc("Message type", "Notify"); 1320 1321 return result; 1322 } 1323 command_oper(const OutputFilterInput & input)1324 OutputFilterResult OutputFilter::command_oper(const OutputFilterInput& input) 1325 { 1326 OutputFilterResult result; 1327 1328 QStringList parameterList = input.parameter.split(QLatin1Char(' ')); 1329 1330 if (input.parameter.isEmpty() || parameterList.count() == 1) 1331 { 1332 QString nick((parameterList.count() == 1) ? parameterList[0] : input.myNick); 1333 1334 QPointer<KPasswordDialog> dialog = new KPasswordDialog(nullptr, KPasswordDialog::ShowUsernameLine); 1335 dialog->setPrompt(i18n("Enter username and password for IRC operator privileges:")); 1336 dialog->setUsername(nick); 1337 dialog->setWindowTitle(i18n("IRC Operator Password")); 1338 if (dialog->exec()) { 1339 result.toServer = QLatin1String("OPER ") + dialog->username() + QLatin1Char(' ') + dialog->password(); 1340 } 1341 1342 delete dialog; 1343 } 1344 else 1345 result.toServer = QLatin1String("OPER ") + input.parameter; 1346 1347 return result; 1348 } 1349 command_ban(const OutputFilterInput & input)1350 OutputFilterResult OutputFilter::command_ban(const OutputFilterInput& input) 1351 { 1352 return handleBan(input, false); 1353 } 1354 command_kickban(const OutputFilterInput & input)1355 OutputFilterResult OutputFilter::command_kickban(const OutputFilterInput& input) 1356 { 1357 return handleBan(input, true); 1358 } 1359 handleBan(const OutputFilterInput & input,bool kick)1360 OutputFilterResult OutputFilter::handleBan(const OutputFilterInput& input, bool kick) 1361 { 1362 OutputFilterResult result; 1363 1364 // assume incorrect syntax first 1365 bool showUsage = true; 1366 1367 if (!input.parameter.isEmpty()) 1368 { 1369 QStringList parameterList = input.parameter.split(QLatin1Char(' ')); 1370 QString channel; 1371 QString option; 1372 // check for option 1373 bool host = isParameter(QStringLiteral("host"), parameterList[0]); 1374 bool domain = isParameter(QStringLiteral("domain"), parameterList[0]); 1375 bool uhost = isParameter(QStringLiteral("userhost"), parameterList[0]); 1376 bool udomain = isParameter(QStringLiteral("userdomain"), parameterList[0]); 1377 1378 // remove possible option 1379 if (host || domain || uhost || udomain) 1380 { 1381 option = parameterList[0].mid(1); 1382 parameterList.pop_front(); 1383 } 1384 1385 // look for channel / ban mask 1386 if (!parameterList.isEmpty()) { 1387 // user specified channel 1388 if (isAChannel(parameterList[0])) 1389 { 1390 channel = parameterList[0]; 1391 parameterList.pop_front(); 1392 } 1393 // no channel, so assume current destination as channel 1394 else if (isAChannel(input.destination)) 1395 channel = input.destination; 1396 else 1397 { 1398 // destination is no channel => error 1399 if (!kick) 1400 result = error(i18n("%1BAN without channel name works only from inside a channel.", 1401 Preferences::self()->commandChar())); 1402 else 1403 result = error(i18n("%1KICKBAN without channel name works only from inside a channel.", 1404 Preferences::self()->commandChar())); 1405 1406 // no usage information after error 1407 showUsage = false; 1408 } 1409 // signal server to ban this user if all went fine 1410 if (!channel.isEmpty() && !parameterList.isEmpty()) 1411 { 1412 if (kick) 1413 { 1414 QString victim = parameterList[0]; 1415 parameterList.pop_front(); 1416 1417 QString reason = parameterList.join(QLatin1Char(' ')); 1418 1419 if (reason.isEmpty()) 1420 reason = m_server->getIdentity()->getKickReason(); 1421 1422 result.toServer = QLatin1String("KICK ") + channel + QLatin1Char(' ') + victim + QLatin1String(" :") + reason; 1423 1424 Q_EMIT banUsers(QStringList(victim), channel, option); 1425 } 1426 else 1427 Q_EMIT banUsers(parameterList, channel, option); 1428 1429 // syntax was correct, so reset flag 1430 showUsage = false; 1431 } 1432 } 1433 } 1434 1435 if (showUsage) 1436 { 1437 if (!kick) 1438 result = usage(i18n("Usage: %1BAN [-HOST | -DOMAIN | -USERHOST | -USERDOMAIN] " 1439 "[channel] <nickname | mask>", Preferences::self()->commandChar())); 1440 else 1441 result = usage(i18n("Usage: %1KICKBAN [-HOST | -DOMAIN | -USERHOST | -USERDOMAIN] " 1442 "[channel] <nickname | mask> [reason]", 1443 Preferences::self()->commandChar())); 1444 } 1445 1446 return result; 1447 } 1448 1449 // finally set the ban execBan(const QString & mask,const QString & channel)1450 OutputFilterResult OutputFilter::execBan(const QString& mask, const QString& channel) 1451 { 1452 OutputFilterResult result; 1453 result.toServer = QLatin1String("MODE ") + channel + QLatin1String(" +b ") + mask; 1454 return result; 1455 } 1456 command_unban(const OutputFilterInput & input)1457 OutputFilterResult OutputFilter::command_unban(const OutputFilterInput& input) 1458 { 1459 OutputFilterResult result; 1460 1461 // assume incorrect syntax first 1462 bool showUsage = true; 1463 1464 if (!input.parameter.isEmpty()) 1465 { 1466 QStringList parameterList = input.parameter.split(QLatin1Char(' ')); 1467 QString channel; 1468 1469 // if the user specified a channel 1470 if (isAChannel(parameterList[0])) 1471 { 1472 // get channel 1473 channel = parameterList[0]; 1474 // remove channel from parameter list 1475 parameterList.pop_front(); 1476 } 1477 // otherwise the current destination must be a channel 1478 else if (isAChannel(input.destination)) 1479 channel = input.destination; 1480 else 1481 { 1482 // destination is no channel => error 1483 result = error(i18n("%1UNBAN without channel name works only from inside a channel.", 1484 Preferences::self()->commandChar())); 1485 // no usage information after error 1486 showUsage = false; 1487 } 1488 // if all went good, signal server to unban this mask 1489 if (!channel.isEmpty() && !parameterList.isEmpty()) { 1490 Q_EMIT unbanUsers(parameterList[0], channel); 1491 // syntax was correct, so reset flag 1492 showUsage = false; 1493 } 1494 } 1495 1496 if (showUsage) 1497 result = usage(i18n("Usage: %1UNBAN [channel] pattern", Preferences::self()->commandChar())); 1498 1499 return result; 1500 } 1501 execUnban(const QString & mask,const QString & channel)1502 OutputFilterResult OutputFilter::execUnban(const QString& mask, const QString& channel) 1503 { 1504 OutputFilterResult result; 1505 result.toServer = QLatin1String("MODE ") + channel + QLatin1String(" -b ") + mask; 1506 return result; 1507 } 1508 command_ignore(const OutputFilterInput & input)1509 OutputFilterResult OutputFilter::command_ignore(const OutputFilterInput& input) 1510 { 1511 OutputFilterResult result; 1512 1513 // assume incorrect syntax first 1514 bool showUsage = true; 1515 1516 // did the user give parameters at all? 1517 if (!input.parameter.isEmpty()) 1518 { 1519 QStringList parameterList = input.parameter.split(QLatin1Char(' ')); 1520 1521 // if nothing else said, only ignore channels and queries 1522 int value = Ignore::Channel | Ignore::Query; 1523 1524 // user specified -all option 1525 if (isParameter(QStringLiteral("all"), parameterList[0])) 1526 { 1527 // ignore everything 1528 value = Ignore::All; 1529 parameterList.pop_front(); 1530 } 1531 1532 // were there enough parameters? 1533 if (parameterList.count() >= 1) 1534 { 1535 for (QString parameter : qAsConst(parameterList)) { 1536 if (!parameter.contains(QLatin1Char('!'))) 1537 parameter += QLatin1String("!*"); 1538 1539 Preferences::removeIgnore(parameter); 1540 Preferences::addIgnore(parameter + QLatin1Char(',') + QString::number(value)); 1541 } 1542 1543 result.output = i18n("Added %1 to your ignore list.", parameterList.join(QLatin1String(", "))); 1544 result.typeString = i18n("Ignore"); 1545 result.type = Program; 1546 1547 // all went fine, so show no error message 1548 showUsage = false; 1549 } 1550 } 1551 1552 if (showUsage) 1553 result = usage(i18n("Usage: %1IGNORE [ -ALL ] <user 1> <user 2> ... <user n>", 1554 Preferences::self()->commandChar())); 1555 1556 return result; 1557 } 1558 command_unignore(const OutputFilterInput & input)1559 OutputFilterResult OutputFilter::command_unignore(const OutputFilterInput& input) 1560 { 1561 OutputFilterResult result; 1562 1563 if (input.parameter.isEmpty()) 1564 result = usage(i18n("Usage: %1UNIGNORE <user 1> <user 2> ... <user n>", 1565 Preferences::self()->commandChar())); 1566 else 1567 { 1568 QString unignore = input.parameter.simplified(); 1569 const QStringList unignoreList = unignore.split(QLatin1Char(' ')); 1570 1571 QStringList succeeded; 1572 QStringList failed; 1573 1574 // Iterate over potential unignores 1575 for (const QString &uign : unignoreList) { 1576 // If pattern looks incomplete, try to complete it 1577 if (!uign.contains(QLatin1Char('!'))) { 1578 QString fixedPattern = uign; 1579 fixedPattern += QLatin1String("!*"); 1580 1581 bool success = false; 1582 1583 // Try to remove completed pattern 1584 if (Preferences::removeIgnore(fixedPattern)) 1585 { 1586 succeeded.append(fixedPattern); 1587 success = true; 1588 } 1589 1590 // Try to remove the incomplete version too, in case it was added via the GUI ... 1591 // FIXME: Validate patterns in GUI? 1592 if (Preferences::removeIgnore(uign)) 1593 { 1594 succeeded.append(uign); 1595 success = true; 1596 } 1597 1598 if (!success) 1599 failed.append(uign + QLatin1String("[!*]")); 1600 } 1601 // Try to remove seemingly complete pattern 1602 else if (Preferences::removeIgnore(uign)) 1603 succeeded.append(uign); 1604 // Failed to remove given complete pattern 1605 else 1606 failed.append(uign); 1607 } 1608 1609 // Print all successful unignores, in case there were any 1610 if (succeeded.count() >= 1) 1611 { 1612 result.output = i18n("Removed %1 from your ignore list.", succeeded.join(QLatin1String(", "))); 1613 result.typeString = i18n("Ignore"); 1614 result.type = Program; 1615 } 1616 1617 // Any failed unignores 1618 if (failed.count() >= 1) 1619 result = error(i18np("No such ignore: %2", "No such ignores: %2", failed.count(), failed.join(QLatin1String(", ")))); 1620 } 1621 1622 return result; 1623 } 1624 command_server(const OutputFilterInput & input)1625 OutputFilterResult OutputFilter::command_server(const OutputFilterInput& input) 1626 { 1627 if (input.parameter.isEmpty() && !m_server->isConnected() && !m_server->isConnecting()) 1628 Q_EMIT reconnectServer(QString()); 1629 else 1630 { 1631 const QStringList parameterList = input.parameter.split(QLatin1Char(' ')); 1632 1633 if (parameterList.count() == 3) 1634 Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0], parameterList[1], parameterList[2]); 1635 else if (parameterList.count() == 2) 1636 { 1637 if (parameterList[0].contains(QRegularExpression(QStringLiteral(":[0-9]+$")))) 1638 Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0], QString(), parameterList[1]); 1639 else 1640 Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0], parameterList[1]); 1641 } 1642 else 1643 Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0]); 1644 } 1645 1646 return OutputFilterResult(); 1647 } 1648 command_charset(const OutputFilterInput & input)1649 OutputFilterResult OutputFilter::command_charset(const OutputFilterInput& input) 1650 { 1651 if (input.parameter.isEmpty()) 1652 return info(i18n("Current encoding is: %1", 1653 QString::fromUtf8(m_server->getIdentity()->getCodec()->name()))); 1654 1655 OutputFilterResult result; 1656 1657 QString shortName = Konversation::IRCCharsets::self()->ambiguousNameToShortName(input.parameter); 1658 1659 if (!shortName.isEmpty()) 1660 { 1661 m_server->getIdentity()->setCodecName(shortName); 1662 Q_EMIT encodingChanged(); 1663 result = info (i18n("Switched to %1 encoding.", shortName)); 1664 } 1665 else 1666 result = error(i18n("%1 is not a valid encoding.", input.parameter)); 1667 1668 return result; 1669 } 1670 command_encoding(const OutputFilterInput & input)1671 OutputFilterResult OutputFilter::command_encoding(const OutputFilterInput& input) 1672 { 1673 return command_charset(input); 1674 } 1675 command_setkey(const OutputFilterInput & input)1676 OutputFilterResult OutputFilter::command_setkey(const OutputFilterInput& input) 1677 { 1678 #ifdef HAVE_QCA2 1679 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 1680 QStringList parms = input.parameter.split(QLatin1Char(' '), QString::SkipEmptyParts); 1681 #else 1682 QStringList parms = input.parameter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 1683 #endif 1684 1685 if (parms.count() == 1 && !input.destination.isEmpty()) 1686 parms.prepend(input.destination); 1687 else if (parms.count() < 2) 1688 return usage(i18n("Usage: %1setkey <nick|channel> <key> sets the encryption key " 1689 "for nick or channel. %1setkey <key> when in a channel or query " 1690 "tab sets the key for it. The key field recognizes \"cbc:\" and " 1691 "\"ecb:\" prefixes to set the block cipher mode of operation to " 1692 "either Cipher-Block Chaining or Electronic Codebook. The mode it " 1693 "defaults to when no prefix is given can be changed in the " 1694 "configuration dialog under Behavior -> Connection -> Encryption " 1695 "-> Default Encryption Type, with the default for that setting being " 1696 "Electronic Codebook (ECB).", 1697 Preferences::self()->commandChar())); 1698 1699 if (!Cipher::isFeatureAvailable(Cipher::Blowfish)) 1700 return error(i18n("Unable to set an encryption key for %1.", parms[0]) + QLatin1Char(' ') + Cipher::runtimeError()); 1701 1702 m_server->setKeyForRecipient(parms[0], QStringList(parms.mid(1)).join(QLatin1Char(' ')).toUtf8()); 1703 1704 if (isAChannel(parms[0]) && m_server->getChannelByName(parms[0])) 1705 m_server->getChannelByName(parms[0])->setEncryptedOutput(true); 1706 else if (m_server->getQueryByName(parms[0])) 1707 m_server->getQueryByName(parms[0])->setEncryptedOutput(true); 1708 1709 return info(i18n("The key for %1 has been set.", parms[0])); 1710 #else 1711 Q_UNUSED(input) 1712 return error(i18n("Setting an encryption key requires Konversation to have been built " 1713 "with support for the Qt Cryptographic Architecture (QCA) library. " 1714 "Contact your distributor about a Konversation package with QCA " 1715 "support, or rebuild Konversation with QCA present.")); 1716 #endif 1717 } 1718 command_keyx(const OutputFilterInput & input)1719 OutputFilterResult OutputFilter::command_keyx(const OutputFilterInput& input) 1720 { 1721 #ifdef HAVE_QCA2 1722 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 1723 QStringList parms = input.parameter.split(QLatin1Char(' '), QString::SkipEmptyParts); 1724 #else 1725 QStringList parms = input.parameter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 1726 #endif 1727 1728 if (parms.isEmpty() && !input.destination.isEmpty()) 1729 parms.prepend(input.destination); 1730 else if (parms.count() !=1) 1731 return usage(i18n("Usage: %1keyx <nick|channel> triggers DH1080 key exchange with the target.", 1732 Preferences::self()->commandChar())); 1733 if (!Cipher::isFeatureAvailable(Cipher::DH)) 1734 return error(i18n("Unable to request a key exchange from %1.", parms[0]) + QLatin1Char(' ') + Cipher::runtimeError()); 1735 1736 m_server->initKeyExchange(parms[0]); 1737 1738 return info(i18n("Beginning DH1080 key exchange with %1.", parms[0])); 1739 #else 1740 Q_UNUSED(input) 1741 return error(i18n("Setting an encryption key requires Konversation to have been built " 1742 "with support for the Qt Cryptographic Architecture (QCA) library. " 1743 "Contact your distributor about a Konversation package with QCA " 1744 "support, or rebuild Konversation with QCA present.")); 1745 #endif 1746 } 1747 command_delkey(const OutputFilterInput & _input)1748 OutputFilterResult OutputFilter::command_delkey(const OutputFilterInput& _input) 1749 { 1750 OutputFilterInput input(_input); 1751 OutputFilterResult result; 1752 1753 if (input.parameter.isEmpty()) 1754 input.parameter = input.destination; 1755 1756 if (input.parameter.isEmpty() || input.parameter.contains(QLatin1Char(' '))) 1757 return usage(i18n("Usage: %1delkey <nick> or <channel> deletes the encryption key for " 1758 "nick or channel", Preferences::self()->commandChar())); 1759 1760 if (!m_server->getKeyForRecipient(input.parameter).isEmpty()) 1761 { 1762 m_server->setKeyForRecipient(input.parameter, ""); 1763 1764 if (isAChannel(input.parameter) && m_server->getChannelByName(input.parameter)) 1765 m_server->getChannelByName(input.parameter)->setEncryptedOutput(false); 1766 else if (m_server->getQueryByName(input.parameter)) 1767 m_server->getQueryByName(input.parameter)->setEncryptedOutput(false); 1768 1769 result = info(i18n("The key for %1 has been deleted.", input.parameter)); 1770 } 1771 else 1772 result = error(i18n("No key has been set for %1.", input.parameter)); 1773 1774 return result; 1775 } 1776 command_showkey(const OutputFilterInput & _input)1777 OutputFilterResult OutputFilter::command_showkey(const OutputFilterInput& _input) 1778 { 1779 OutputFilterInput input(_input); 1780 1781 if (input.parameter.isEmpty()) 1782 input.parameter = input.destination; 1783 1784 const QString key = QString::fromUtf8(m_server->getKeyForRecipient(input.parameter)); 1785 1786 QWidget* mw = Application::instance()->getMainWindow(); 1787 1788 if (!key.isEmpty()) 1789 KMessageBox::information(mw, i18n("The key for %1 is \"%2\".", input.parameter, key), 1790 i18n("Blowfish")); 1791 else 1792 KMessageBox::information(mw, i18n("No key has been set for %1.", input.parameter)); 1793 1794 return OutputFilterResult(); 1795 } 1796 command_kill(const OutputFilterInput & input)1797 OutputFilterResult OutputFilter::command_kill(const OutputFilterInput& input) 1798 { 1799 OutputFilterResult result; 1800 1801 if (input.parameter.isEmpty()) 1802 result = usage(i18n("Usage: %1KILL <nick> [comment]", Preferences::self()->commandChar())); 1803 else 1804 { 1805 const QString victim = input.parameter.section(QLatin1Char(' '), 0, 0); 1806 1807 result.toServer = QLatin1String("KILL ") + victim + QLatin1String(" :") + input.parameter.mid(victim.length() + 1); 1808 } 1809 1810 return result; 1811 } 1812 command_dns(const OutputFilterInput & input)1813 OutputFilterResult OutputFilter::command_dns(const OutputFilterInput& input) 1814 { 1815 if (input.parameter.isEmpty()) 1816 return usage(i18n("Usage: %1DNS <nick>", Preferences::self()->commandChar())); 1817 else 1818 new OutputFilterResolveJob(input); 1819 1820 return OutputFilterResult(); 1821 } 1822 command_list(const OutputFilterInput & input)1823 OutputFilterResult OutputFilter::command_list(const OutputFilterInput& input) 1824 { 1825 Q_EMIT openChannelList(input.parameter); 1826 1827 return OutputFilterResult(); 1828 } 1829 command_konsole(const OutputFilterInput &)1830 OutputFilterResult OutputFilter::command_konsole(const OutputFilterInput& /* input */) 1831 { 1832 Q_EMIT openKonsolePanel(); 1833 1834 return OutputFilterResult(); 1835 } 1836 command_queuetuner(const OutputFilterInput & input)1837 OutputFilterResult OutputFilter::command_queuetuner(const OutputFilterInput& input) 1838 { 1839 Application *konvApp = Application::instance(); 1840 1841 if (input.parameter.isEmpty() || input.parameter == QLatin1String("on")) 1842 konvApp->showQueueTuner(true); 1843 else if (input.parameter == QLatin1String("off")) 1844 konvApp->showQueueTuner(false); 1845 else 1846 return usage(i18n("Usage: %1queuetuner [on | off]", Preferences::self()->commandChar())); 1847 1848 return OutputFilterResult(); 1849 } 1850 command_sayversion(const OutputFilterInput & input)1851 OutputFilterResult OutputFilter::command_sayversion(const OutputFilterInput& input) 1852 { 1853 OutputFilterResult result; 1854 result.output = input.parameter; 1855 1856 QTextStream serverOut(&result.toServer); 1857 QTextStream myOut(&result.output); //<--because seek is unimplemented in QTextStreamPrivate::write(const QString &data) 1858 myOut 1859 << "Konversation: " << qApp->applicationVersion() 1860 << ", Qt " << QString::fromLatin1(qVersion()) 1861 << ", KDE Frameworks " << QStringLiteral(KXMLGUI_VERSION_STRING); 1862 ; 1863 if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION") && !qEnvironmentVariableIsEmpty("KDE_SESSION_VERSION")) 1864 myOut << ", Plasma " << QString::fromLatin1(qgetenv("KDE_SESSION_VERSION")); 1865 else 1866 myOut << ", no Plasma"; 1867 1868 serverOut << "PRIVMSG " << input.destination << " :" << result.output; 1869 return result; 1870 } 1871 command_cycle(const OutputFilterInput & input)1872 OutputFilterResult OutputFilter::command_cycle(const OutputFilterInput& input) 1873 { 1874 if (input.parameter.isEmpty()) 1875 { 1876 if (input.context) 1877 input.context->cycle(); 1878 else 1879 qCDebug(KONVERSATION_LOG) << "Parameter-less /cycle without an input context can't work."; 1880 } 1881 else 1882 { 1883 if (isParameter(QStringLiteral("app"), input.parameter)) 1884 { 1885 Application *konvApp = Application::instance(); 1886 1887 konvApp->restart(); 1888 } 1889 else if (isParameter(QStringLiteral("server"), input.parameter)) 1890 { 1891 if (m_server) 1892 m_server->cycle(); 1893 else 1894 qCDebug(KONVERSATION_LOG) << "Told to cycle the server, but current context doesn't have one."; 1895 } 1896 else if (m_server) 1897 { 1898 if (isAChannel(input.parameter)) 1899 { 1900 Channel* channel = m_server->getChannelByName(input.parameter); 1901 1902 if (channel) channel->cycle(); 1903 } 1904 else if (m_server->getQueryByName(input.parameter)) 1905 m_server->getQueryByName(input.parameter)->cycle(); 1906 else 1907 return usage(i18n("%1CYCLE [-APP | -SERVER] [channel | nickname]", Preferences::self()->commandChar())); 1908 } 1909 } 1910 1911 return OutputFilterResult(); 1912 } 1913 command_clear(const OutputFilterInput & input)1914 OutputFilterResult OutputFilter::command_clear(const OutputFilterInput& input) 1915 { 1916 if (input.parameter.isEmpty()) 1917 { 1918 if (input.context) 1919 input.context->clear(); 1920 else 1921 qCDebug(KONVERSATION_LOG) << "Parameter-less /clear without an input context can't work."; 1922 } 1923 else if (m_server) 1924 { 1925 if (isParameter(QStringLiteral("all"), input.parameter)) 1926 m_server->getViewContainer()->clearAllViews(); 1927 else 1928 { 1929 if (isAChannel(input.parameter)) 1930 { 1931 Channel* channel = m_server->getChannelByName(input.parameter); 1932 1933 if (channel) channel->clear(); 1934 } 1935 else if (m_server->getQueryByName(input.parameter)) 1936 m_server->getQueryByName(input.parameter)->clear(); 1937 else 1938 return usage(i18n("%1CLEAR [-ALL] [channel | nickname]", Preferences::self()->commandChar())); 1939 } 1940 } 1941 1942 return OutputFilterResult(); 1943 } 1944 command_umode(const OutputFilterInput & input)1945 OutputFilterResult OutputFilter::command_umode(const OutputFilterInput& input) 1946 { 1947 OutputFilterResult result; 1948 1949 result.toServer = QLatin1String("MODE ") + input.myNick + QLatin1Char(' ') + input.parameter; 1950 1951 return result; 1952 } 1953 changeMode(const QString & parameter,const QString & destination,char mode,char giveTake)1954 OutputFilterResult OutputFilter::changeMode(const QString ¶meter, const QString& destination, 1955 char mode, char giveTake) 1956 { 1957 OutputFilterResult result; 1958 // TODO: Make sure this works with +l <limit> and +k <password> also! 1959 QString token; 1960 QString tmpToken; 1961 QStringList nickList = parameter.split(QLatin1Char(' ')); 1962 1963 if (!nickList.isEmpty()) { 1964 // Check if the user specified a channel 1965 if(isAChannel(nickList[0])) 1966 { 1967 token = QLatin1String("MODE ") + nickList[0]; 1968 // remove the first element 1969 nickList.removeFirst(); 1970 } 1971 // Add default destination if it is a channel 1972 else if(isAChannel(destination)) 1973 { 1974 token = QLatin1String("MODE ") + destination; 1975 } 1976 1977 // Only continue if there was no error 1978 if (!token.isEmpty()) { 1979 QString modeToken; 1980 QString nickToken; 1981 1982 modeToken = QLatin1Char(' ') + QLatin1Char(giveTake); 1983 1984 tmpToken = token; 1985 1986 for(int index = 0; index < nickList.count(); index++) 1987 { 1988 if(index != 0 && (index % 3) == 0) 1989 { 1990 token += modeToken + nickToken; 1991 result.toServerList.append(token); 1992 token = tmpToken; 1993 nickToken.clear(); 1994 modeToken = QLatin1Char(' ') + QLatin1Char(giveTake); 1995 } 1996 nickToken += QLatin1Char(' ') + nickList[index]; 1997 modeToken += QLatin1Char(mode); 1998 } 1999 2000 if(!nickToken.isEmpty()) 2001 { 2002 token += modeToken + nickToken; 2003 result.toServerList.append(token); 2004 } 2005 } 2006 } 2007 2008 return result; 2009 } 2010 usage(const QString & string)2011 OutputFilterResult OutputFilter::usage(const QString& string) 2012 { 2013 OutputFilterResult result; 2014 result.typeString = i18n("Usage"); 2015 result.output = string; 2016 result.type = Program; 2017 return result; 2018 } 2019 info(const QString & string)2020 OutputFilterResult OutputFilter::info(const QString& string) 2021 { 2022 OutputFilterResult result; 2023 result.typeString = i18n("Info"); 2024 result.output = string; 2025 result.type = Program; 2026 return result; 2027 } 2028 error(const QString & string)2029 OutputFilterResult OutputFilter::error(const QString& string) 2030 { 2031 OutputFilterResult result; 2032 result.typeString = i18n("Error"); 2033 result.output = string; 2034 result.type = Program; 2035 return result; 2036 } 2037 addNickToEmptyNickList(const QString & nick,const QString & parameter)2038 QString OutputFilter::addNickToEmptyNickList(const QString& nick, const QString& parameter) 2039 { 2040 const QStringList nickList = parameter.split(QLatin1Char(' ')); 2041 QString newNickList; 2042 2043 if (nickList.isEmpty()) 2044 { 2045 newNickList = nick; 2046 } 2047 // check if list contains only target channel 2048 else if (nickList.count() == 1 && isAChannel(nickList[0])) 2049 { 2050 newNickList = nickList[0] + QLatin1Char(' ') + nick; 2051 } 2052 // list contains at least one nick 2053 else 2054 { 2055 newNickList = parameter; 2056 } 2057 2058 return newNickList; 2059 } 2060 2061 // # & + and ! are *often*, but not necessarily, channel identifiers. + and ! are non-RFC, 2062 // so if a server doesn't offer 005 and supports + and ! channels, I think thats broken 2063 // behaviour on their part - not ours. isAChannel(const QString & check) const2064 bool OutputFilter::isAChannel(const QString &check) const 2065 { 2066 if (check.isEmpty()) 2067 return false; 2068 Q_ASSERT(m_server); 2069 // XXX if we ever see the assert, we need the ternary 2070 return m_server? m_server->isAChannel(check) : bool(QStringLiteral("#&").contains(check.at(0))); 2071 } 2072 isParameter(const QString & parameter,const QString & string) const2073 bool OutputFilter::isParameter(const QString& parameter, const QString& string) const 2074 { 2075 const QString pattern = QRegularExpression::anchoredPattern(QStringLiteral("^[\\-]{1,2}%1$").arg(parameter)); 2076 const QRegularExpression rx(pattern, QRegularExpression::CaseInsensitiveOption); 2077 2078 return rx.match(string).hasMatch(); 2079 } 2080 } 2081 2082 2083 // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; 2084 // vim: set et sw=4 ts=4 cino=l1,cs,U1: 2085