kaZax

ChatGuard

Jun 5th, 2013
143
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Pawn 16.43 KB | None | 0 0
  1. /*
  2.     CHAT GUARD
  3.  
  4.     автор:  MX_Master
  5.     версия: 0.1
  6.  
  7.     описание:
  8.         Это фильтрскрипт для мультиплеера SA-MP, который следит за порядком
  9.         в игровом чате. Он отлавливает неправильные сообщения или имена
  10.         игроков.
  11.  
  12.         Неправильные сообщения не отображаются в общем чате.
  13.         Сообщения игроков с неправильными именами, также не видны в общем чате.
  14.  
  15.         ФС запрещает писать в общем чате:
  16.             - IP адреса
  17.             - домены или имена сайтов
  18.             - сообщения, злоупотребляющие верхним регистром букв
  19.             - попытки ввода команд чата в другой раскладке клавы
  20.             - одинаковые или похожие сообщения подряд
  21.             - пустые сообщения или сообщения только с пробельными символами
  22.             - если имя игрока содержит IP адрес / домен
  23.             - слишком частые сообщения (флуд)
  24. */
  25.  
  26. #include <a_samp>
  27.  
  28.  
  29.  
  30.  
  31.  
  32. #define SPACE_CHARS ' ', '\t', '\r', '\n'
  33.  
  34. /*
  35.     Обрежет по обоим краям строки все пробельные символы.
  36. -----------
  37.     Ничего не возвращает.
  38. */
  39.  
  40. stock trimSideSpaces ( string[] )
  41. {
  42.     new c, len = strlen(string);
  43.  
  44.     for ( ; c < len; c++ ) // вырежем пробелы в начале
  45.     {
  46.         switch ( string[c] )
  47.         {
  48.             case SPACE_CHARS : continue;
  49.             default:
  50.             {
  51.                 if ( c != 0 ) strmid( string, string, c, len, len );
  52.                 break;
  53.             }
  54.         }
  55.     }
  56.  
  57.     for ( c = len - c - 1; c >= 0; c-- ) // вырежем пробелы в конце
  58.     {
  59.         switch ( string[c] )
  60.         {
  61.             case SPACE_CHARS : continue;
  62.             default:
  63.             {
  64.                 string[c + 1] = 0;
  65.                 break;
  66.             }
  67.         }
  68.     }
  69. }
  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81. #define cells *4
  82.  
  83. /*
  84.     Заменяет группы пробельных символов на единичные пробелы
  85. -----------
  86.     Ничего не возвращает
  87. */
  88.  
  89. stock spaceGroupsToSpaces ( string[] )
  90. {
  91.     new len = strlen(string), c = len - 1, spaces;
  92.  
  93.     for ( ; c >= 0; c-- )
  94.     {
  95.         switch ( string[c] )
  96.         {
  97.             case SPACE_CHARS : spaces++;
  98.             default :
  99.             {
  100.                 if ( spaces > 1 )
  101.                 {
  102.                     memcpy( string, string[c + spaces + 1], (c + 2) cells, (len - c - spaces - 1) cells, len );
  103.                     len -= spaces - 1;
  104.                 }
  105.  
  106.                 if ( spaces > 0 )
  107.                 {
  108.                     string[c + 1] = ' ';
  109.                     spaces        =  0;
  110.                 }
  111.             }
  112.         }
  113.     }
  114.  
  115.     if ( spaces > 1 )
  116.     {
  117.         memcpy( string, string[c + spaces + 1], (c + 2) cells, (len - c - spaces - 1) cells, len );
  118.         len -= spaces - 1;
  119.     }
  120.     if ( spaces > 0 ) string[c + 1] = ' ';
  121.  
  122.     string[len] = 0;
  123. }
  124.  
  125. #undef SPACE_CHARS
  126.  
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137. /*
  138.     Вернет 1, если строка содержит любой реальный IP адрес,
  139.     или более 3 групп чисел, разделенных любыми другими символами.
  140. ------------
  141.     Иначе, вернет 0.
  142. */
  143.  
  144. stock containsAnyIP ( string[] )
  145. {
  146.     new digits, digitGroups;
  147.  
  148.     for ( new pos; ; pos++ )
  149.     {
  150.         switch ( string[pos] )
  151.         {
  152.             case 0 : break;
  153.             case '0'..'9', 'o', 'O', 'о', 'О', 'З', 'з' : digits++;
  154.             default :
  155.             {
  156.                 if ( digits >= 2 )
  157.                 {
  158.                     digitGroups++;
  159.                     digits = 0;
  160.                 }
  161.             }
  162.         }
  163.     }
  164.  
  165.     if ( digits >= 2 ) digitGroups++;
  166.     if ( digitGroups >= 4 ) return 1;
  167.  
  168.     return 0;
  169. }
  170.  
  171.  
  172.  
  173.  
  174.  
  175.  
  176.  
  177.  
  178.  
  179.  
  180.  
  181. // список запрещенных доменов 1 уровня
  182. stock forbiddenDomain[][] =
  183. {
  184.     ".net", ".nеt",
  185.     ".com", ".сom", ".соm", ".cоm",
  186.     ".ru", ".ру",
  187.     ".kz", ".кz", ".kз", ".кз",
  188.     ".info", ".infо"
  189. };
  190.  
  191. /*
  192.     Вернет 1, если строка содержит любой домен из списка выше.
  193. ------------
  194.     Иначе, вернет 0.
  195. */
  196.  
  197. stock containsDomainName ( string[] )
  198. {
  199.     new strLen = strlen(string);
  200.  
  201.     for ( new d = sizeof(forbiddenDomain) - 1, foundPos, domainLen; d >= 0; d-- )
  202.     {
  203.         foundPos  = -1;
  204.         domainLen = strlen(forbiddenDomain[d]);
  205.  
  206.         for ( ; ( foundPos = strfind( string, forbiddenDomain[d], true, foundPos + 1 ) ) >= 0;  )
  207.         {
  208.             if ( foundPos + domainLen >= strLen ) return 1;
  209.  
  210.             switch ( string[ foundPos + domainLen ] )
  211.             {
  212.                 case 0..64, 91..96, 123..191 : return 1;
  213.             }
  214.         }
  215.     }
  216.  
  217.     return 0;
  218. }
  219.  
  220.  
  221.  
  222.  
  223.  
  224.  
  225.  
  226.  
  227.  
  228.  
  229.  
  230. // список флагов для каждого игрока
  231. stock forbiddenName [ MAX_PLAYERS char ];
  232.  
  233. /*
  234.     Вернет 1, если ник игрока содержит доменное имя или любой реальный IP.
  235. -------------
  236.     Иначе вернет 0.
  237. */
  238.  
  239. stock playerHasForbiddenName ( playerid )
  240. {
  241.     if ( forbiddenName{playerid} == 0 )
  242.     {
  243.         new name[MAX_PLAYER_NAME];
  244.         GetPlayerName( playerid, name, MAX_PLAYER_NAME );
  245.  
  246.         if ( containsAnyIP(name) || containsDomainName(name) )
  247.         {
  248.             forbiddenName{playerid} = 1;
  249.             return 1;
  250.         }
  251.  
  252.         forbiddenName{playerid} = -1;
  253.         return 0;
  254.     }
  255.     else
  256.     {
  257.         if ( forbiddenName{playerid} == 1 ) return 1;
  258.         else return 0;
  259.     }
  260. }
  261.  
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.  
  272. /*
  273.     Вернет 1, если строка является неудачной попыткой ввода какой-то команды чата.
  274.     Такое бывает при вводе текста команды не в той раскладке.
  275. -------------
  276.     Иначе вернет 0.
  277. */
  278.  
  279. #define INCORRECT_CMD_CHARS '.', '?'
  280.  
  281. stock incorrectCmdAttempt ( string[] )
  282. {
  283.     switch ( string[0] )
  284.     {
  285.         case INCORRECT_CMD_CHARS :
  286.         {
  287.             switch ( string[1] )
  288.             {
  289.                 case 65..90, 97..122, 192..255 : return 1;
  290.             }
  291.         }
  292.     }
  293.  
  294.     return 0;
  295. }
  296.  
  297. #undef INCORRECT_CMD_CHARS
  298.  
  299.  
  300.  
  301.  
  302.  
  303.  
  304.  
  305.  
  306.  
  307.  
  308.  
  309. #define CHAT_STR_SIZE           128 // макс кол-во символов в сообщении чата
  310. #define CHAT_HISTORY_SIZE       20 // кол-во сохраненных сообщений чата
  311. #define DUBLPOSTS_SIMILARITY    40 // допустимый % похожести, идущих подряд, сообщений игрока
  312. #define MAX_MESSAGES_PER_TIME   3 // максимум сообщений за, указанную ниже, единицу времени
  313. #define MAX_MESSAGES_TIME       3000 // мс. За указанное время должно быть не более MAX_FAST_MESSAGES сообщений игрока
  314.  
  315. enum chatMsInfo // инфо о каждом сообщении чата
  316. {
  317.     chTick,
  318.     chPosterID,
  319.     chText [ CHAT_STR_SIZE ]
  320. }
  321.  
  322. // массив, где хранятся посл CHAT_HISTORY_SIZE сообщений чата
  323. stock ms [ CHAT_HISTORY_SIZE ] [ chatMsInfo ];
  324.  
  325. /*
  326.     Строка source[] будет разделена на несколько подстрок
  327.     с помощью символа delimiter. Нужная подстрока под номером (индексом)
  328.     substrIndex будет помещена в строку dest[]
  329. --------------
  330.     Ничего не возвращает.
  331. */
  332.  
  333. stock sparam
  334. (
  335.     dest[],             maxSize     = sizeof(dest),
  336.     const source[],     delimiter   = ' ',
  337.     substrIndex = 0,    withRest    = 0
  338. )
  339. {
  340.     dest[0] = 0; // очистим строку назначения
  341.  
  342.     for ( new cur, pre, i = -1; ; cur++ ) // пробежимся по каждому символу в строке source
  343.     {
  344.         if ( source[cur] == 0 ) // если текущий символ в source - это символ конца строки
  345.         {
  346.             if ( ++i == substrIndex ) // если индекс текущей подстроки и есть sourceIndex
  347.                 // скопируем в dest нужную подстроку из source
  348.                 strmid( dest, source, pre, ( withRest ? strlen(source) : cur ), maxSize );
  349.  
  350.             goto sparam_end;
  351.         }
  352.  
  353.         if ( source[cur] == delimiter ) // если текущий символ в source - это символ для разделения строки
  354.         {
  355.             if ( ++i == substrIndex ) // если индекс текущей подстроки и есть sourceIndex
  356.             {
  357.                 // скопируем в dest нужную подстроку из source
  358.                 strmid( dest, source, pre, ( withRest ? strlen(source) : cur ), maxSize );
  359.                 goto sparam_end;
  360.             }
  361.  
  362.             pre = cur + 1;
  363.         }
  364.     }
  365.  
  366.     sparam_end:
  367.     return; // завершим работу функции
  368. }
  369.  
  370. /*
  371.     Вернет 1, если предыдущее сообщение игрока такое же как текущее,
  372.     или текущее сообщение похоже на предыдущее на DUBLPOSTS_SIMILARITY процентов.
  373. ------------
  374.     Иначе вернет 0.
  375. */
  376.  
  377. stock sameTextAsLastBefore ( playerid, text[] )
  378. {
  379.     // найдем предыдущее сообщение игрока в истории чата
  380.     new m;
  381.  
  382.     for ( ; m < CHAT_HISTORY_SIZE; m++ )
  383.         if ( ms[m][chPosterID] == playerid ) break;
  384.  
  385.     if ( m >= CHAT_HISTORY_SIZE ) return 0;
  386.  
  387.  
  388.     // узнаем, сколько в строке пробелов
  389.     new words = 1, w = strfind( text, " ", false, 0 );
  390.     while ( w >= 0 )
  391.     {
  392.         words++;
  393.         w = strfind( text, " ", false, w + 1 );
  394.     }
  395.  
  396.  
  397.     // узнаем, кол-во одинаковых символов во всех словах обеих строк
  398.     new word[2][CHAT_STR_SIZE], sameChars, c;
  399.     for ( w = 0; w < words; w++ )
  400.     {
  401.         sparam( word[0], CHAT_STR_SIZE, text,          ' ', w );
  402.         sparam( word[1], CHAT_STR_SIZE, ms[m][chText], ' ', w );
  403.  
  404.         for ( c = 0; word[0][c] != 0; c++ )
  405.             if ( toupper( word[0][c] ) == toupper( word[1][c] ) ) sameChars++;
  406.     }
  407.  
  408.     if ( float(sameChars) / float( strlen(ms[m][chText]) ) * 100.0 > DUBLPOSTS_SIMILARITY.0 ) return 1;
  409.  
  410.  
  411.     return 0;
  412. }
  413.  
  414. /*
  415.     Обновит историю сообщений чата
  416. ----------
  417.     Ничего не вернет.
  418. */
  419.  
  420. stock updateMsHistory ( playerid, msTick, text[] )
  421. {
  422.     // сдвиг всей истории на 1 слот вперед
  423.     for ( new i = 1; i < CHAT_HISTORY_SIZE; i++ )
  424.         memcpy( ms[i], ms[i - 1], 0, sizeof(ms[]) cells );
  425.  
  426.     // добавим текст и автора в историю сообщений чата
  427.     ms[0][chTick]     = msTick;
  428.     ms[0][chPosterID] = playerid;
  429.     strmid( ms[0][chText], text, 0, strlen(text), CHAT_STR_SIZE );
  430. }
  431.  
  432. /*
  433.     Вернет 1, если игрок слишком быстро набирает сообщения подряд.
  434. -------------
  435.     Иначе вернет 0.
  436. */
  437.  
  438. stock tooManyMessagesForShortTime ( playerid, lastMsTick )
  439. {
  440.     // найдем самое первое сообщение игрока в истории чата из его всех быстрых сообщений
  441.     new m, messages;
  442.  
  443.     for ( ; m < CHAT_HISTORY_SIZE; m++ )
  444.         if ( ms[m][chPosterID] == playerid && ++messages > MAX_MESSAGES_PER_TIME ) break;
  445.  
  446.     if ( m >= CHAT_HISTORY_SIZE ) m--;
  447.     if ( messages > MAX_MESSAGES_PER_TIME && lastMsTick - ms[m][chTick] < MAX_MESSAGES_TIME ) return 1;
  448.  
  449.     return 0;
  450. }
  451.  
  452. #undef cells
  453. #undef CHAT_STR_SIZE
  454. #undef DUBLPOSTS_SIMILARITY
  455. #undef MAX_MESSAGES_PER_TIME
  456. #undef MAX_MESSAGES_TIME
  457.  
  458.  
  459.  
  460.  
  461.  
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468. /*
  469.     Вернет 1, если в строке слишком много букв в верхнем регистре.
  470. -----------
  471.     Иначе вернет 0.
  472. */
  473.  
  474. #define MAX_UPPERCASES    40 // допустимый % букв в верхнем регистре
  475.  
  476. stock tooManyUpperChars ( string[] )
  477. {
  478.     new len = strlen(string), c = len - 1, upperChars;
  479.  
  480.     for ( ; c >= 0; c-- )
  481.     {
  482.         switch ( string[c] )
  483.         {
  484.             case 'A'..'Z', 'А'..'Я' : upperChars++;
  485.         }
  486.     }
  487.  
  488.     if ( float(upperChars) / float(len) * 100.0 > MAX_UPPERCASES.0 ) return 1;
  489.  
  490.     return 0;
  491. }
  492.  
  493. #undef MAX_UPPERCASES
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.  
  507.  
  508.  
  509.  
  510.  
  511.  
  512.  
  513.  
  514.  
  515. public OnFilterScriptInit()
  516. {
  517.     for ( new i; i < CHAT_HISTORY_SIZE; i++ ) ms[i][chPosterID] = -1;
  518. }
  519.  
  520. #undef CHAT_HISTORY_SIZE
  521.  
  522. public OnPlayerConnect ( playerid )
  523. {
  524.     forbiddenName{playerid} = 0;
  525.     return 1;
  526. }
  527.  
  528. public OnPlayerDisconnect ( playerid, reason )
  529. {
  530.     forbiddenName{playerid} = 0;
  531.     return 1;
  532. }
  533.  
  534.  
  535.  
  536.  
  537.  
  538. #define WARN_MS_COLOR   0xFF5050AA
  539. #define WARN_MS_PREFIX  " * "
  540.  
  541. public OnPlayerText ( playerid, text[] )
  542. {
  543.     new msgTick = GetTickCount();
  544.  
  545.  
  546.  
  547.     // проверка ника игрока на содержание любого IP/домена
  548.     if ( playerHasForbiddenName(playerid) )
  549.     {
  550.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "В твоём нике засекли IP или запрещенный домен. Чат закрыт." );
  551.         return 0;
  552.     }
  553.  
  554.     // сообщения чата, похожие на попытку ввода команды чата - отображаться не будут
  555.     if ( incorrectCmdAttempt(text) )
  556.     {
  557.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "Попытка ввода команды? Повнимательней." );
  558.         return 0;
  559.     }
  560.  
  561.  
  562.  
  563.     // замена любых групп пробельных символов на единичные пробелы
  564.     spaceGroupsToSpaces(text);
  565.  
  566.     // обрезка пробельных символов по краям
  567.     trimSideSpaces(text);
  568.  
  569.     // пустые сообщения отображаться не будут
  570.     if ( text[0] == 0 ) return 0;
  571.  
  572.     // нельзя писать соообщения, содержащие много букв в верхнем регистре
  573.     if ( tooManyUpperChars(text) )
  574.     {
  575.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "Отключи CAPS LOCK, горе луковое." );
  576.         return 0;
  577.     }
  578.  
  579.     // нельзя писать сообщения, содержащие IP адреса
  580.     if ( containsAnyIP(text) )
  581.     {
  582.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "IP адреса писать нельзя." );
  583.         return 0;
  584.     }
  585.  
  586.     // нельзя писать сообщения, содержащие запрещенные доменные имена
  587.     if ( containsDomainName(text) )
  588.     {
  589.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "Кое-какие домены и сайты писать нельзя." );
  590.         return 0;
  591.     }
  592.  
  593.  
  594.  
  595.     // нельзя быстро написать неск сообщений подряд
  596.     if ( tooManyMessagesForShortTime( playerid, msgTick ) )
  597.     {
  598.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "Не флудить!" );
  599.         return 0;
  600.     }
  601.  
  602.     // нельзя писать похожие сообщения подряд
  603.     if ( sameTextAsLastBefore( playerid, text ) )
  604.     {
  605.         SendClientMessage( playerid, WARN_MS_COLOR, WARN_MS_PREFIX "Похожие сообщения писать нельзя. Задумайся." );
  606.         return 0;
  607.     }
  608.  
  609.     // обновление истории сообщений чата
  610.     updateMsHistory( playerid, msgTick, text );
  611.  
  612.  
  613.  
  614.     // покажем сообщение в чате
  615.     return 1;
  616. }
  617.  
  618. #undef WARN_MS_COLOR
  619. #undef WARN_MS_PREFIX
Add Comment
Please, Sign In to add comment