commit 0381859219123ea34fb89b8745a8ea0dcee06fa0 Author: Correl Roush Date: Sun Aug 31 09:08:24 2008 +0000 Imported the QT4 IRC Client project, along with the mIRC script parser that will eventually be integrated into it git-svn-id: file:///srv/svn/ircclient/trunk@1 a9804ffe-773b-11dd-bd7c-89c3ef1d2733 diff --git a/guiapp/chatwindow.h b/guiapp/chatwindow.h new file mode 100644 index 0000000..a51081d --- /dev/null +++ b/guiapp/chatwindow.h @@ -0,0 +1,51 @@ +#ifndef CHATWINDOW_H +#define CHATWINDOW_H + +#include "ui_chatwindow.h" +#include + +class ChatWindow : public QWidget, public Ui::ChatWindow { + Q_OBJECT + +private: + QString filter; + QString defaultCmd; + +public: + ChatWindow( QWidget *parent = 0 ) : QWidget( parent ) { + }; + + void setupUi( QWidget *ChatWindow = 0 ) { + Ui::ChatWindow::setupUi( ChatWindow ); + connect( input, SIGNAL( returnPressed() ), this, SLOT( processCmd() ) ); + } + +public slots: + void setMessageFilter( QString filter ) { this->filter = filter; } + void setDefaultCmd( QString cmd ) { this->defaultCmd = cmd; } + void message( QString src, QString message ) { + display->append( "<" + src + "> " + message ); + display->verticalScrollBar()->setValue( display->verticalScrollBar()->maximum() ); + } + void echo( QString text ) { + display->append( text ); + display->verticalScrollBar()->setValue( display->verticalScrollBar()->maximum() ); + } + void processCmd() { + QString cmd = defaultCmd; + QStringList args = input->text().trimmed().split( " " ); + if( args.count() < 1 ) { return; } + if( args[0].startsWith( '/' ) ) { + cmd = args.takeFirst(); + cmd = cmd.right( cmd.length() - 1 ); + } + emit doCmd( cmd + " " + args.join( " " ) ); + input->clear(); + } + +signals: + void doCmd( QString cmd ); +}; + +#endif + diff --git a/guiapp/chatwindow.ui b/guiapp/chatwindow.ui new file mode 100644 index 0000000..d45828d --- /dev/null +++ b/guiapp/chatwindow.ui @@ -0,0 +1,36 @@ + + ChatWindow + + + + 0 + 0 + 400 + 300 + + + + + + + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + + + + + + + + + + + diff --git a/guiapp/guiapp.pro b/guiapp/guiapp.pro new file mode 100644 index 0000000..ec07c95 --- /dev/null +++ b/guiapp/guiapp.pro @@ -0,0 +1,16 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Tue Aug 14 00:00:17 2007 +###################################################################### + +TEMPLATE = app +TARGET = guiapp +DESTDIR = bin +DEPENDPATH += . +INCLUDEPATH += . +QT += network + +# Input +HEADERS += chatwindow.h messagehandler.h ../src/ircclient.h ../src/dccserver.h +FORMS += chatwindow.ui +SOURCES += main.cpp messagehandler.cpp ../src/ircclient.cpp ../src/dccserver.cpp +TRANSLATIONS += guiapp_en.ts diff --git a/guiapp/guiapp_en.ts b/guiapp/guiapp_en.ts new file mode 100644 index 0000000..e720edc --- /dev/null +++ b/guiapp/guiapp_en.ts @@ -0,0 +1,34 @@ + + + + + ChatWindow + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + + + + IRCClient + + + Quit + Quit + + + + MessageHandler + + + Changing servers + Changing Servers + + + diff --git a/guiapp/main.cpp b/guiapp/main.cpp new file mode 100644 index 0000000..32df8a7 --- /dev/null +++ b/guiapp/main.cpp @@ -0,0 +1,18 @@ +#include +#include "../src/ircclient.h" +#include "chatwindow.h" +#include "messagehandler.h" + +int main( int argc, char *argv[] ) { + QApplication app( argc, argv ); + IRCClient *irc = new IRCClient(); + MessageHandler *mh = new MessageHandler( irc ); + + QObject::connect( &app, SIGNAL( aboutToQuit() ), irc, SLOT( quit() ) ); + mh->show(); + + irc->connectAndRegister( argc > 1 ? argv[1] : "dev1", 6667, argc > 2 ? argv[2] : "Test", "correlr", "Correl Roush" ); + + return app.exec(); +} + diff --git a/guiapp/messagehandler.cpp b/guiapp/messagehandler.cpp new file mode 100644 index 0000000..93bbfa4 --- /dev/null +++ b/guiapp/messagehandler.cpp @@ -0,0 +1,215 @@ +#include "messagehandler.h" +#include + +MessageHandler::MessageHandler( IRCClient *irc, QWidget *parent ) : QTabWidget( parent ) { + this->irc = irc; + connect( irc, SIGNAL( messageRcvd( QString, QString, QString, QStringList, QString ) ), this, SLOT( messageRcvd( QString, QString, QString, QStringList, QString ) ) ); + connect( irc, SIGNAL( registered() ), this, SLOT( autoRun() ) ); + connect( irc, SIGNAL( sentRAW( QString ) ), this, SLOT( sentRAW( QString ) ) ); + connect( irc, SIGNAL( rcvdRAW( QString ) ), this, SLOT( rcvdRAW( QString ) ) ); + this->setTabPosition( QTabWidget::South ); + _addWindow( "status", "", true ); + + DCCPortMin = 1024; + DCCPortMax = 5000; + + DCCSendAhead = true; + + // Set up variables + variables["address"] = "$ip"; +} + +void MessageHandler::_addWindow( QString name, QString defaultCmd, bool focusOnOpen ) { + if( windows.keys().contains( name ) ) { return; } + int index = this->addTab( new ChatWindow(), name ); + windows[name] = (ChatWindow*)this->widget( index ); + windows[name]->setDefaultCmd( defaultCmd ); + connect( windows[name], SIGNAL( doCmd( QString ) ), this, SLOT( processCmd( QString ) ) ); + windows[name]->setupUi( windows[name] ); + if( focusOnOpen ) { + this->setCurrentIndex( index ); + windows[name]->input->setFocus(); + } +} + +QString MessageHandler::parseVar( QString text ) { + if( !text.startsWith( '$' ) ) { return text; } + QString var = text.right( text.length() - 1 ).toLower(); + QString value; + + if( var == "ip" ) { value = irc->getIPAddress(); } + if( var == "nick" || var == "nickname" ) { value = irc->getNickName(); } + + if( value.isNull() && variables.keys().contains( var ) ) { value = variables[var].toString(); } + return ( value.isNull() || value == text ) ? text : parseVar( value ); // Allows for recursive vars, while avoiding an infinite loop +} + +void MessageHandler::messageRcvd( QString type, QString src, QString dest, QStringList values, QString text ) { + QString window = "status"; + QString source = "server"; + QString message = text; + + if( src.startsWith( ':' ) ) { src = src.right( src.length() - 1 ); } + + // Handle special messages first + if( type == "JOIN" ) { + _addWindow( text, "msg " + text, true ); + } + + if( !dest.isEmpty() && dest.startsWith( '#' ) ) { + window = dest; + } + if( !src.isEmpty() && src.contains( '!' ) && src.contains( '@' ) ) { + QStringList split = src.split( '!' ); + source = split[0]; + if( dest == irc->getNickName() && type == "PRIVMSG" ) { + window = source; + } + } + + if( type == "CTCP" ) { + values = text.split( " " ); + QString ctcpCommand = values.takeFirst().toUpper(); + if( ctcpCommand == "VERSION" ) { + irc->notice( source, tr( "Atma2 IRC Client %1" ).arg( 0.01 ) ); + } + } + + //emit dispatchMessage( window, src, message ); + _addWindow( window, "msg " + window ); + windows[window]->message( source, message ); +} + +void MessageHandler::messageSentDCC( QString nickName, QString text ) { + QString window = nickName.prepend( "=" ); + _addWindow( window, "msg " + window ); + windows[window]->message( irc->getNickName(), text ); +} + +void MessageHandler::messageRcvdDCC( QString nickName, QString text ) { + QString window = "=" + nickName; + _addWindow( window, "msg " + window ); + windows[window]->message( nickName, text ); +} + +void MessageHandler::processCmd( QString cmdText ) { + qDebug() << cmdText; + QStringList args = cmdText.split( ' ' ); + QStringList newargs; + + QString cmd = args.takeFirst().toLower(); + for( int i = 0; i < args.count(); i++ ) { + if( args[i].startsWith( '$' ) ) { + args[i] = parseVar( args[i] ); + //QString var = args[i].right( args[i].length() - 1 ); + //if( variables.contains( var ) ) { args[i] = variables[var].toString(); } + } + } + if( cmd == "server" || cmd == "connect" ) { + if( args.count() < 1 ) { return; } + irc->quit( tr( "Changing servers" ) ); + QString host = args.takeFirst(); + int port = 0; + if( host.contains( ':' ) ) { + QStringList splitHost = host.split( ':' ); + host = splitHost.takeFirst(); + port = splitHost.join( "" ).toInt(); + } + port = port > 0 ? port : 6667; + QString nickName = args.count() > 0 ? args.takeFirst() : irc->getNickName(); + QString userName = args.count() > 0 ? args.takeFirst() : irc->getUserName(); + QString realName = args.count() > 0 ? args.join( " " ) : irc->getRealName(); + irc->connectAndRegister( + host, // Host + port > 0 ? port : 6667, // Port + nickName, + userName, + realName ); + } else if( cmd == "nick" ) { + if( args.count() < 1 ) { return; } + irc->nick( args[0] ); + } else if( cmd == "msg" || cmd == "privmsg" ) { + if( args.count() < 2 ) { return; } + newargs += args.takeFirst(); + if( newargs[0].startsWith( "=") ) { + // DCC Chat + ((DCCChatServer*)DCCServers[DCCNicks[newargs[0].right( newargs[0].length() - 1 )]])->sendText( args.join( " " ) ); + } else { + irc->msg( newargs[0], args.join( " " ) ); + } + // Update existing chat windows with the sent message + QString window; + QStringList windows = newargs[0].split( "," ); + foreach( window, windows ) { + if( !window.startsWith( "=" ) && this->windows.keys().contains( window ) ) { this->windows[window]->message( irc->getNickName(), args.join( " " ) ); } + } + } else if( cmd == "notice" ) { + if( args.count() < 2 ) { return; } + QString dest = args.takeFirst(); + irc->notice( dest, args.join( " " ) ); + } else if( cmd == "ctcp" ) { + if( args.count() < 2 ) { return; } + newargs += args.takeFirst(); + irc->ctcp( newargs[0], args.join( " " ) ); + } else if( cmd == "join" ) { + irc->join( args.join( "," ) ); + } else if( cmd == "set" ) { + QString var = args.takeFirst(); + variables[var] = args.join( " " ); + } else if( cmd == "dcc" ) { + if( args.count() < 2 ) { return; } + QString type = args.takeFirst(); + QString nick = args.takeFirst(); + if( type == "chat" ) { + startDCC( nick ); + } else if( type == "send" ) { + if( args.count() == 0 ) { return; } + startDCC( nick, args.join( " " ) ); + } + } else if( cmd == "quit" || cmd == "q" ) { + if( args.count() > 0 ) { irc->quit( args.join( " " ) ); } + else { irc->quit(); } + } +} +void MessageHandler::autoRun() { + //irc->join( "#pinet" ); +} + +void MessageHandler::startDCC( QString nickName, QString fileName ) { + for( int i = DCCPortMin; i <= DCCPortMax; i++ ) { + if( !DCCServers.keys().contains( i ) ) { + QString message = "DCC %1"; + if( fileName.isEmpty() ) { + // Chat session + DCCServers[i] = new DCCChatServer( nickName ); + connect( ((DCCChatServer*)DCCServers[i]), SIGNAL( connectedChat( QString ) ), this, SLOT( connectedDCCChat( QString ) ) ); + DCCNicks[nickName] = i; + message = message.arg( "CHAT chat %1 %2" ); + } else { + // File Send + QFileInfo fileInfo = QFileInfo( fileName ); + if( !fileInfo.exists() ) { return; } + DCCServers[i] = new DCCFileServer( fileName, DCCSendAhead ); + message = message.arg( "SEND %1 %3 %2" ).arg( fileInfo.fileName() ).arg( fileInfo.size() ).arg( "%1 %2" ); + } + bool success = DCCServers[i]->listen( QHostAddress::Any, i ); + qDebug() << QString( "Listen on port %1: %2" ).arg( i ).arg( success ); + message = message.arg( irc->localHost.toIPv4Address() ).arg( i ); + qDebug() << message; + irc->ctcp( nickName, message ); + break; + } + } +} + +void MessageHandler::connectedDCCChat( QString nickName ) { + DCCChatServer *dcc = ((DCCChatServer*)DCCServers[DCCNicks[nickName]]); + connect( dcc, SIGNAL( rcvdText( QString, QString ) ), this, SLOT( messageRcvdDCC( QString, QString ) ) ); + connect( dcc, SIGNAL( sentText( QString, QString ) ), this, SLOT( messageSentDCC( QString, QString ) ) ); + QString window = nickName.prepend( "=" ); + _addWindow( window, "msg " + window ); + windows[window]->echo( "DCC Chat Connected" ); +} + +void MessageHandler::sentRAW( QString text ) { qDebug() << ">>> " << text.trimmed(); } +void MessageHandler::rcvdRAW( QString text ) { qDebug() << "<<< " << text.trimmed(); } diff --git a/guiapp/messagehandler.h b/guiapp/messagehandler.h new file mode 100644 index 0000000..30650dc --- /dev/null +++ b/guiapp/messagehandler.h @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include "../src/ircclient.h" +#include "../src/dccserver.h" +#include "chatwindow.h" + +class MessageHandler : public QTabWidget { + Q_OBJECT +private: + IRCClient *irc; + QHash< QString, ChatWindow* > windows; + QHash< QString, QVariant > variables; + int DCCPortMin; + int DCCPortMax; + bool DCCSendAhead; + QHash< int, DCCServer* > DCCServers; + QHash< QString, int > DCCNicks; + + void _addWindow( QString name, QString defaultCmd = "", bool focusOnOpen = false ); + QString parseVar( QString text ); +public: + MessageHandler( IRCClient *irc, QWidget *parent = 0 ); +public slots: + void messageRcvd( QString type, QString src, QString dest, QStringList values, QString text ); + void messageSentDCC( QString nickName, QString text ); + void messageRcvdDCC( QString nickName, QString text ); + void processCmd( QString cmdText ); + void autoRun(); + void startDCC( QString nickName, QString fileName = "" ); + void connectedDCCChat( QString nickName ); + + void rcvdRAW( QString text ); + void sentRAW( QString text ); +signals: + void dispatchMessage( QString window, QString src, QString message ); +}; + diff --git a/ircclient.pro b/ircclient.pro new file mode 100644 index 0000000..e79bbd1 --- /dev/null +++ b/ircclient.pro @@ -0,0 +1,13 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Mon Aug 13 14:31:09 2007 +###################################################################### + +TEMPLATE = lib +TARGET = ircclient +DESTDIR = bin +DEPENDPATH += . src +INCLUDEPATH += . src +QT += network +# Input +HEADERS += src/ircclient.h +SOURCES += src/ircclient.cpp diff --git a/mirc/includes/mirc.h b/mirc/includes/mirc.h new file mode 100644 index 0000000..eedcef3 --- /dev/null +++ b/mirc/includes/mirc.h @@ -0,0 +1,18 @@ +#ifndef MIRC_H +#define MIRC_H + +#include +#include +#include +#include "script.h" + +class MIRCScriptManager : public QObject { + Q_OBJECT + +public: + //QMap variables; + + MIRCScriptManager(QObject *parent); +}; + +#endif diff --git a/mirc/includes/parser.h b/mirc/includes/parser.h new file mode 100644 index 0000000..c276423 --- /dev/null +++ b/mirc/includes/parser.h @@ -0,0 +1,269 @@ +/* + Mercenary + + mIRC Scripting Language Parser +*/ + +//#define BOOST_SPIRIT_DEBUG +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace boost; +using namespace boost::spirit; + +typedef QMap mirc_variables; + +struct mirc_alias { + mirc_alias(bool _global = true) { + global = _global; + } + bool global; + QString code; +}; + +typedef QMap mirc_aliases; + +enum mirc_engine_stage { + PARSE, + EXECUTE +}; + +class mirc_script_engine : public QObject { + Q_OBJECT +private: + mirc_aliases::iterator current_alias; + mirc_variables::iterator current_variable; + QStack stack; +public: + mirc_engine_stage stage; + mirc_alias script; + mirc_aliases aliases; + mirc_variables vars; + + mirc_script_engine() : script() { + stage = PARSE; + current_alias = aliases.end(); + current_variable = vars.end(); + } + + void handle_alias_definition(char const* str, char const* end) { + if (stage != PARSE) return; + + string s(str, end); + aliases.insert(s.c_str(), mirc_alias(true)); + current_alias = aliases.find(s.c_str()); + } + void handle_alias_definition_local(char const* str, char const* end) { + if (stage != PARSE) return; + + string s(str, end); + aliases.insert(s.c_str(), mirc_alias(false)); + current_alias = aliases.find(s.c_str()); + } + void close_alias(char const*, char const*) { + if (stage != PARSE) return; + + if (!aliases.empty() && current_alias != aliases.end()) { + current_alias = aliases.end(); + } + } + void store_code(char const* str, char const* end) { + if (stage != PARSE) return; + + string s(str, end); + if (!aliases.empty() && current_alias != aliases.end()) { + current_alias->code.append(s.c_str()).append("\n"); + } else { + script.code.append(s.c_str()).append("\n"); + } + } + void call_alias(char const*, char const*) { + if (stage != EXECUTE) return; + + } + void return_alias(char const*, char const*) { + } + void declare_variable(char const* str, char const* end) { + if (stage != EXECUTE) return; + string s(str, end); + vars.insert(s.c_str(), ""); + current_variable = vars.find(s.c_str()); + stack.push(QStringList()); + } + void assign_variable(char const* str, char const* end) { + if (stage != EXECUTE) return; + + if (current_variable != vars.end()) { + string s(str, end); + *current_variable = (!stack.isEmpty() ? stack.pop().join(" ") : ""); + current_variable = vars.end(); + } + } + void fetch_variable(char const*, char const*) { + if (stage != EXECUTE) return; + + } + void append_expression(char const* str, char const *end) { + if (stage != EXECUTE) return; + + string s(str, end); + QStringList list; + if (stack.isEmpty()) { + list << s.c_str(); + } else { + list = stack.pop(); + list << s.c_str(); + } + stack.push(list); + } +}; + +struct mirc_script : public grammar { + mirc_script_engine *actions; + + mirc_script( mirc_script_engine *_actions ) { + actions = _actions; + } + + template + struct definition { + rule script, + space, + identifier, + string, + expression, + expression_group, + parameters, + variable, + assignment, + alias_action, + alias_function, + alias_definition, + code_line, + code_block, + comment; + + definition(mirc_script const &self) { + + typedef function< void(const char*, const char*) > s_action; + s_action a_def ( bind( &mirc_script_engine::handle_alias_definition, self.actions, _1, _2 ) ); + s_action l_def ( bind( &mirc_script_engine::handle_alias_definition_local, self.actions, _1, _2 ) ); + s_action a_close ( bind( &mirc_script_engine::close_alias, self.actions, _1, _2 ) ); + s_action v_def ( bind( &mirc_script_engine::declare_variable, self.actions, _1, _2 ) ); + s_action v_assign ( bind( &mirc_script_engine::assign_variable, self.actions, _1, _2 ) ); + s_action e_append ( bind( &mirc_script_engine::append_expression, self.actions, _1, _2 ) ); + s_action s_code ( bind( &mirc_script_engine::store_code, self.actions, _1, _2 ) ); + + script + = *( + alias_definition[a_close] + | code_block + ) >> end_p + ; + space + = ( blank_p + //| (ch_p('\\') >> eol_p) + | str_p("$&") >> *blank_p >> eol_p + ) + ; + identifier + = alpha_p >> *alnum_p + ; + string + = ( variable + | alias_function + | +(graph_p - ch_p(',') - ch_p('(') - ch_p(')')) + )[e_append] + ; + expression + = string >> *(*space >> string) + ; + expression_group + = expression | expression_group + ; + parameters + = expression >> *(*space >> ch_p(',') >> *space >> expression) + ; + variable + = ch_p('%') >> identifier + ; + assignment + = !(str_p("var") >> *space) + >> variable[v_def] + >> *space >> ch_p('=') >> *space + >> expression[v_assign] + ; + alias_action + = !ch_p('/') >> !ch_p('/') + >> identifier >> *space >> parameters + ; + alias_function + = ch_p('$') >> identifier + >> !( + ch_p('(') >> *space + >> parameters >> *space + /* + >> (expression - ch_p(',')) + >> *( *space + >> ch_p(',') >> *space + >> (expression - ch_p(',')) + ) + */ + >> ch_p(')') + ) + ; + alias_definition + = str_p("alias") >> *space + //>> !(str_p("-l") >> *space) + >> if_p(str_p("-l") >> *space)[identifier[l_def]].else_p[identifier[a_def]] + >> *space >> !eol_p + >> code_block + ; + code_line + = *space + >> ( comment + | ( + assignment /* Must come first to avoid "var" being caught as an action */ + | alias_action + )[s_code] + ) + >> !eol_p + ; + code_block + = ( *space >> ch_p('{') >> *space >> !eol_p + >> (*code_line) + >> ch_p('}') >> *space >> !eol_p + ) + | code_line + ; + comment = comment_p(";"); + + BOOST_SPIRIT_DEBUG_NODE(script); + BOOST_SPIRIT_DEBUG_NODE(space); + BOOST_SPIRIT_DEBUG_NODE(identifier); + BOOST_SPIRIT_DEBUG_NODE(string); + BOOST_SPIRIT_DEBUG_NODE(expression); + BOOST_SPIRIT_DEBUG_NODE(variable); + BOOST_SPIRIT_DEBUG_NODE(assignment); + BOOST_SPIRIT_DEBUG_NODE(alias_action); + BOOST_SPIRIT_DEBUG_NODE(alias_function); + BOOST_SPIRIT_DEBUG_NODE(alias_definition); + BOOST_SPIRIT_DEBUG_NODE(code_line); + BOOST_SPIRIT_DEBUG_NODE(code_block); + BOOST_SPIRIT_DEBUG_NODE(comment); + BOOST_SPIRIT_DEBUG_NODE(*this); + } + rule const& start() const { return script; } + }; +}; diff --git a/mirc/includes/script.h b/mirc/includes/script.h new file mode 100644 index 0000000..d59d44d --- /dev/null +++ b/mirc/includes/script.h @@ -0,0 +1,29 @@ +#ifndef MIRC_SCRIPT_H +#define MIRC_SCRIPT_H + +#include +#include +#include +#include "mirc.h" +#include "parser.h" + +class MIRCScript : public QObject { + Q_OBJECT +private: + QString script; + QMap _aliases; + QMap _variables; + bool loaded; +public: + MIRCScript(); + bool load(QString filename); + bool parse(QString script); + bool run(); + bool run(QString alias); + QString code(); + QString code(QString alias); + QMap aliases(); + QMap variables(); +}; + +#endif diff --git a/mirc/mirc.pro b/mirc/mirc.pro new file mode 100644 index 0000000..649cdc6 --- /dev/null +++ b/mirc/mirc.pro @@ -0,0 +1,16 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Sat Aug 30 23:01:47 2008 +###################################################################### + +TEMPLATE = app +# CONFIG += plugin +TARGET = mirc +DESTDIR = bin +DEPENDPATH += . includes src +INCLUDEPATH += . includes + +# Input +HEADERS += includes/mirc.h includes/parser.h includes/script.h +SOURCES += src/mirc.cpp \ + src/parser.cpp \ + src/script.cpp diff --git a/mirc/src/mirc.cpp b/mirc/src/mirc.cpp new file mode 100644 index 0000000..5becc27 --- /dev/null +++ b/mirc/src/mirc.cpp @@ -0,0 +1,4 @@ +#include "mirc.h" + +MIRCScriptManager::MIRCScriptManager(QObject *parent = 0) { +} diff --git a/mirc/src/parser.cpp b/mirc/src/parser.cpp new file mode 100644 index 0000000..4c28b09 --- /dev/null +++ b/mirc/src/parser.cpp @@ -0,0 +1,77 @@ +#include +#include "script.h" + +int main() { + QTextStream output(stdout); + + QString foo; + foo.append("; Hey, here's some test code\n"); + foo.append("set name Correl\n"); + foo.append("%name = Correl $&\n\tRoush\n"); + foo.append("echo Hello, %name!\n"); + foo.append("alias dostuff {\n"); + foo.append(" ; Not very useful, but good for testing the parser!\n"); + foo.append(" var %b = 42\n"); + foo.append(" %b = $calc(%b * 3)\n"); + foo.append(" return %b;\n"); + foo.append("}\n"); + foo.append("alias -l dosomethingelse {\n"); + foo.append(" ; Useless local alias!\n"); + foo.append(" echo -s Busy doing nothing\n"); + foo.append("}\n"); + foo.append("alias -l dosomethingelse {\n"); + foo.append(" ; Useless local alias!\n"); + foo.append(" echo -s Busy doing nothing\n"); + foo.append("}\n"); + + output << "Code:\n" + << "================================================================================\n" + << foo + << "================================================================================\n" + << "\n"; + + mirc_script_engine *interpreter = new mirc_script_engine(); + mirc_script parser(interpreter); + + parse_info<> info = parse((const char*)foo.toLatin1(), parser); + + if (info.full) + { + output << "-------------------------\n"; + output << "Parsing succeeded\n"; + output << "-------------------------\n"; + + MIRCScript *ms = new MIRCScript(); + output << "Creating new MIRCScript object\n"; + //if (ms->parse(foo)) { + if (ms->load("test.mrc")) { + output << "MRC LOADED\n"; + output << "Code:\n" << ms->code(); + output << "Aliases:\n"; + QMapIterator alias(ms->aliases()); + while(alias.hasNext()) { + alias.next(); + output << (alias.value().global ? "global" : "local"); + output << " " << alias.key() << "\n"; + } + if (ms->run()) { + output << "MRC RAN SUCCESSFULLY\n"; + QMapIterator i(ms->variables()); + output << "Variables:\n"; + while(i.hasNext()) { + i.next(); + output << i.key() << " = " << i.value() << "\n"; + } + } + } + } + else + { + output << "-------------------------\n"; + output << "Parsing failed\n"; + output << "stopped at: \": " << info.stop << "\"\n"; + output << "-------------------------\n"; + } + + return 0; +} diff --git a/mirc/src/script.cpp b/mirc/src/script.cpp new file mode 100644 index 0000000..d997021 --- /dev/null +++ b/mirc/src/script.cpp @@ -0,0 +1,61 @@ +#include "script.h" +#include + +MIRCScript::MIRCScript() { +} + +bool MIRCScript::load(QString filename) { + QFile file(filename); + if (file.exists() && file.open(QIODevice::ReadOnly)) { + QString code(file.readAll()); + file.close(); + return parse(code); + } else { + return false; + } +} + +bool MIRCScript::parse(QString code) { + mirc_script_engine *interpreter = new mirc_script_engine(); + mirc_script parser(interpreter); + parse_info<> info = boost::spirit::parse((const char*)code.toLatin1(), parser); + loaded = info.full; + if (loaded) { + script = interpreter->script.code; + _aliases = interpreter->aliases; + } + return loaded; +} + +bool MIRCScript::run() { + if (!loaded) return false; + mirc_script_engine *interpreter = new mirc_script_engine(); + interpreter->stage = EXECUTE; + mirc_script parser(interpreter); + parse_info<> info = boost::spirit::parse((const char*)script.toLatin1(), parser); + if (info.full) { + _variables = interpreter->vars; + } + return info.full; +} + +bool MIRCScript::run(QString alias) { + if (!loaded) return false; + return false; +} + +QString MIRCScript::code() { + return script; +} + +QString MIRCScript::code(QString alias) { + return QString(); +} + +QMap MIRCScript::aliases() { + return _aliases; +} + +QMap MIRCScript::variables() { + return _variables; +} diff --git a/mirc/test.mrc b/mirc/test.mrc new file mode 100644 index 0000000..857b10d --- /dev/null +++ b/mirc/test.mrc @@ -0,0 +1,15 @@ +; Hey, here's some test code +set name Correl +%name = Correl $& + Roush +echo Hello, %name! +alias dostuff { + ; Not very useful, but good for testing the parser! + var %b = 42 + %b = $calc(%b * 3) + return %b; +} +alias -l dosomethingelse { + ; Useless local alias! + echo -s Busy doing nothing +} diff --git a/src/dccserver.cpp b/src/dccserver.cpp new file mode 100644 index 0000000..d7e3247 --- /dev/null +++ b/src/dccserver.cpp @@ -0,0 +1,77 @@ +#include "dccserver.h" + +DCCServer::DCCServer( QObject *parent ) : QTcpServer( parent ) { + connect( this, SIGNAL( newConnection() ), this, SLOT( connectClient() ) ); +} + +void DCCServer::connectClient() { + conn = nextPendingConnection(); + connect( conn, SIGNAL( readyRead() ), this, SLOT( readSocket() ) ); + close(); // No more than one client +} + +DCCChatServer::DCCChatServer( QString nickName, QObject *parent ) : DCCServer( parent ) { + this->nickName = nickName.trimmed(); + connect( this, SIGNAL( newConnection() ), this, SLOT( connectChatClient() ) ); +} + +void DCCChatServer::sendText( QString text ) { + emit sentText( nickName, text ); + text.append( "\r\n" ); + conn->write( text.toLatin1() ); +} + +void DCCChatServer::connectChatClient() { + emit connectedChat( nickName ); +} + +void DCCChatServer::readSocket() { + if( !conn->canReadLine() ) return; + char buf[512]; + while( conn->readLine( buf, sizeof( buf ) ) > 0 ) + emit rcvdText( nickName, QString( buf ).trimmed() ); +} + +DCCFileServer::DCCFileServer( QString fileName, bool sendAhead, QObject *parent ) : DCCServer( parent ) { + chunkSize = 1024; + sent = 0; + verified = 0; + this->sendAhead = sendAhead; + file.setFileName( fileName ); + file.open( QIODevice::ReadOnly ); + connect( this, SIGNAL( newConnection() ), this, SLOT( connectFileClient() ) ); +} + +void DCCFileServer::connectFileClient() { + emit connectedFile( conn->localPort() ); + // Send the initial chunk of data + sendNextChunk(); +} + +void DCCFileServer::readSocket() { + char buf[4]; + while( conn->bytesAvailable() >= 4 ) { + conn->read( buf, 4 ); + quint32 v = *reinterpret_cast( &buf[0] ); + v = qFromBigEndian( v ); + verified = v; + if( sendAhead || verified == sent ) { sendNextChunk(); } + if( !file.isOpen() ) { conn->close(); } + } +} + +void DCCFileServer::sendNextChunk() { + char buf[chunkSize]; + if( !file.isOpen() || !file.isReadable() || !conn->isOpen() ) { return; } + qint64 bytesRead = 0; + while( ( sendAhead || sent == verified ) && ( bytesRead = file.read( buf, chunkSize ) ) ) { + if( bytesRead > 0 ) { + conn->write( buf, bytesRead ); + } + sent += bytesRead; + if( conn->bytesAvailable() >= 4 ) { readSocket(); } + } + if( bytesRead < chunkSize && sent == verified ) { + file.close(); + } +} diff --git a/src/dccserver.h b/src/dccserver.h new file mode 100644 index 0000000..be58f84 --- /dev/null +++ b/src/dccserver.h @@ -0,0 +1,60 @@ +#ifndef DCCSERVER_H +#define DCCSERVER_H + +#include +#include +#include +#include +#include +#include + +class DCCServer: public QTcpServer { + Q_OBJECT +protected: + QTcpSocket *conn; +public: + DCCServer( QObject *parent = 0 ); +protected slots: + void connectClient(); +}; + +class DCCChatServer : public DCCServer { + Q_OBJECT +private: + QString nickName; +public: + DCCChatServer( QString nickName, QObject *parent = 0 ); +private slots: + void connectChatClient(); + void readSocket(); +public slots: + void sendText( QString text ); +signals: + void connectedChat( QString nickName ); + void sentText( QString nickName, QString text ); + void rcvdText( QString nickName, QString text ); +}; + +class DCCFileServer : public DCCServer { + Q_OBJECT +private: + int port; + QFile file; + int chunkSize; + quint64 sent; + quint64 verified; + bool sendAhead; +public: + DCCFileServer( QString fileName, bool sendAhead = false, QObject *parent = 0 ); +private slots: + void connectFileClient(); + void readSocket(); + void sendNextChunk(); +signals: + void connectedFile( int port ); + void progress( int port, double percentage ); + void complete( int port, bool success ); +}; + +#endif + diff --git a/src/ircclient.cpp b/src/ircclient.cpp new file mode 100644 index 0000000..338dd1b --- /dev/null +++ b/src/ircclient.cpp @@ -0,0 +1,121 @@ +#include "ircclient.h" +#include + +IRCClient::IRCClient( QObject *parent ) : QObject( parent ) { + conn = new QTcpSocket(); + QObject::connect( conn, SIGNAL( readyRead() ), this, SLOT( readSocket() ) ); + QObject::connect( conn, SIGNAL( connected() ), this, SLOT( _connected() ) ); + QObject::connect( conn, SIGNAL( disconnected() ), this, SLOT( _disconnected() ) ); + QObject::connect( this, SIGNAL( rcvdRAW( QString ) ), this, SLOT( parseMessage( QString ) ) ); + nickName = userName = realName = ""; +} + +void IRCClient::connectAndRegister( QString host, uint port, QString nickName, QString userName, QString realName ) { + conn->abort(); + conn->connectToHost( host, port ); + this->nickName = nickName; + this->userName = userName; + this->realName = realName; +} + +void IRCClient::raw( QString text ) { + // Send raw text to the IRC Server + text.append( "\r\n" ); + conn->write( text.toLatin1() ); + emit sentRAW( text ); +} + +void IRCClient::msg( QString dest, QString text ) { raw( "PRIVMSG " + dest + " :" + text ); } +void IRCClient::ctcp( QString dest, QString text ) { raw( "PRIVMSG " + dest + " :\001" + text + "\001" ); } +void IRCClient::notice( QString dest, QString text ) { raw( "NOTICE " + dest + " :" + text ); } +void IRCClient::join( QString channel ) { raw( "JOIN " + channel ); } +void IRCClient::quit( QString reason ) { raw( "QUIT :" + reason ); conn->flush(); } +void IRCClient::quit() { quit( tr( "Quit" ) ); } +void IRCClient::nick( QString nick ) { raw( "NICK " + nick ); } + +void IRCClient::_connected() { + localHost = conn->localAddress(); + emit connected(); + // Register + raw( "USER " + userName + " +iw localhost :" + realName ); + nick( nickName ); +} +void IRCClient::_disconnected() { emit disconnected(); } + +void IRCClient::closeSocket() { + emit disconnected(); +} + +void IRCClient::updateHostInfo( QHostInfo host ) { + if (host.error() != QHostInfo::NoError) { + qDebug() << "Lookup failed: " + host.errorString(); + return; + } + + foreach (QHostAddress address, host.addresses()) + localHost = address; + qDebug() << "Host updated: " + localHost.toString(); +} + +void IRCClient::readSocket() { + if( !conn->canReadLine() ) return; + char buf[512]; + while( conn->readLine( buf, sizeof( buf ) ) > 0 ) + emit rcvdRAW( buf ); +} + +void IRCClient::parseMessage( QString text ) { + text = text.trimmed(); + int split = text.indexOf( " :" ); + QString message = split >= 0 ? text.left( split ) : text; + QString messageText = split >= 0 ? text.right( text.length() - split - 2 ) : ""; + + QString from = ""; + QString type = ""; + QString dest = ""; + QStringList values; + QStringList parsedMessage = message.split( ' ' ); + if( parsedMessage.count() > 0 ) { + if( message.startsWith( ':' ) ) from = parsedMessage.takeFirst(); + if( parsedMessage.count() > 0 ) type = parsedMessage.takeFirst(); + if( parsedMessage.count() > 0 ) dest = parsedMessage.takeFirst(); + values = parsedMessage; + } else { + type = message; + } + + // The following are special cases that can be taken care of immediately + // Anything else should probably be dealt with by an actual client + if( type == "001" ) { + emit registered(); + // Fetch the hostname from the welcome text if it's there and perform a nonblocking dns lookup + QString hostMask = messageText.right( messageText.length() - messageText.lastIndexOf( ' ' ) - 1 ); + if( hostMask.contains( '@' ) ) { + QString hostName = hostMask.right( hostMask.length() - hostMask.indexOf( '@' ) - 1 ); + qDebug() << "Looking up " + hostName; + QHostInfo::lookupHost( hostName, this, SLOT( updateHostInfo( QHostInfo ) ) ); + } else { + raw( "WHOIS " + nickName ); + } + } else if( type == "311" ) { + // RPL_WHOIS + if( values.count() < 3 ) return; + if( values[0] == nickName ) { + qDebug() << "Looking up " + values[2]; + QHostInfo::lookupHost( values[2], this, SLOT( updateHostInfo( QHostInfo ) ) ); + } + } else if( type == "NICK" && from.startsWith( ":" + nickName + "!" ) ) { + nickName = messageText; + } else if( type == "PING" ) { + raw( "PONG :" + messageText ); + emit pingPong(); + } else if( type == "PRIVMSG" ) { + if( messageText.startsWith( "\001" ) && messageText.endsWith( "\001" ) ) { + // CTCP Message + type = "CTCP"; + messageText = messageText.mid( 1, messageText.length() - 2 ); + } + } + //qDebug() << "t" << type << "s" << from << "d" << dest << "v" << values.join( "," ) << "m" << messageText; + emit messageRcvd( type, from, dest, values, messageText ); +} diff --git a/src/ircclient.h b/src/ircclient.h new file mode 100644 index 0000000..c820434 --- /dev/null +++ b/src/ircclient.h @@ -0,0 +1,65 @@ +#ifndef IRCCLIENT_H +#define IRCCLIENT_H + +#include +#include +#include +#include +#include +#include + + +class IRCClient : public QObject { + Q_OBJECT +private: + QString nickName, userName, realName; + +public: + QTcpSocket *conn; + QHostAddress localHost; + + IRCClient( QObject *parent = 0 ); + //void connect( QString host, uint port, QString nickName ); + void connectAndRegister( QString host, uint port, QString nickName, QString userName, QString realName ); + void raw( QString text ); + void msg( QString dest, QString message ); + void ctcp( QString dest, QString message ); + void notice( QString dest, QString message ); + void join( QString channel ); + void nick( QString nick ); + + QString getNickName() { return nickName; } + QString getUserName() { return userName; } + QString getRealName() { return realName; } + QString getIPAddress() { return localHost.toString(); } + +signals: + // Basic + void connected(); + void disconnected(); + void registered(); + + // Debug + void rcvdRAW( QString text ); + void sentRAW( QString text ); + void debug( QString text ); + + // Events + void pingPong(); + void messageRcvd( QString type, QString src, QString dest, QStringList values, QString text ); + +public slots: + void quit( QString reason ); + void quit(); // No reason... + void updateHostInfo( QHostInfo host ); + +private slots: + void _connected(); + void _disconnected(); + void closeSocket(); + void readSocket(); + void parseMessage( QString text ); +}; + +#endif + diff --git a/testapp/main.cpp b/testapp/main.cpp new file mode 100644 index 0000000..2887ffb --- /dev/null +++ b/testapp/main.cpp @@ -0,0 +1,8 @@ +#include +#include +#include "testapp.h" + +int main( int argc, char *argv[] ) { + TestApp app( argc, argv ); + return app.exec(); +} diff --git a/testapp/testapp.cpp b/testapp/testapp.cpp new file mode 100644 index 0000000..1eac087 --- /dev/null +++ b/testapp/testapp.cpp @@ -0,0 +1,38 @@ +#include "testapp.h" + +TestApp::TestApp( int argc, char *argv[] ) : QCoreApplication( argc, argv ) { + irc = new IRCClient(); + connect( irc->conn, SIGNAL( connected() ), this, SLOT( connected() ) ); + connect( irc->conn, SIGNAL( disconnected() ), this, SLOT( disconnected() ) ); + connect( irc, SIGNAL( registered() ), this, SLOT( registered() ) ); + connect( irc, SIGNAL( sentRAW( QString ) ), this, SLOT( sent( QString ) ) ); + connect( irc, SIGNAL( rcvdRAW( QString ) ), this, SLOT( rcvd( QString ) ) ); + connect( irc, SIGNAL( debug( QString ) ), this, SLOT( debug( QString ) ) ); + + irc->connectAndRegister( argc > 1 ? argv[1] : "dev1", 6667, argc > 2 ? argv[2] : "Test", "correlr", "Correl Roush" ); +} + +void TestApp::connected() { + printf( "Connected!\n" ); +} + +void TestApp::registered() { + irc->join( "#pinet" ); +} + +void TestApp::disconnected() { + printf( "Disconnected.\n" ); +} + +void TestApp::sent( QString text ) { + printf( ">>> %s", (const char*)text.toLatin1() ); +} + +void TestApp::rcvd( QString text ) { + printf( "<<< %s", (const char*)text.toLatin1() ); +} + +void TestApp::debug( QString text ) { + printf( "DEBUG: %s\n", (const char*)text.toLatin1() ); +} + diff --git a/testapp/testapp.h b/testapp/testapp.h new file mode 100644 index 0000000..d876c8e --- /dev/null +++ b/testapp/testapp.h @@ -0,0 +1,21 @@ +#include +#include +#include "../src/ircclient.h" + +class TestApp : public QCoreApplication { + Q_OBJECT + +public: + TestApp( int argc, char *argv[] ); + +private: + IRCClient *irc; + +private slots: + void connected(); + void disconnected(); + void registered(); + void sent( QString text ); + void rcvd( QString text ); + void debug( QString text ); +}; diff --git a/testapp/testapp.pro b/testapp/testapp.pro new file mode 100644 index 0000000..fe3c222 --- /dev/null +++ b/testapp/testapp.pro @@ -0,0 +1,14 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Mon Aug 13 15:01:40 2007 +###################################################################### + +TEMPLATE = app +TARGET = testapp +DESTDIR = bin +DEPENDPATH += . +INCLUDEPATH += . +QT += network + +# Input +HEADERS += testapp.h ../src/ircclient.h +SOURCES += main.cpp testapp.cpp ../src/ircclient.cpp