8 Commits
0.1 ... 0.6

8 changed files with 174 additions and 76 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ assets/cert*
main.db main.db
main.db-journal main.db-journal
settings.ini settings.ini
code.png

BIN
.worker.py.swo Normal file

Binary file not shown.

BIN
.worker.py.swp Normal file

Binary file not shown.

View File

@ -3,8 +3,27 @@
# Create your own spy bot in Telegram ¯\_(ツ)_/¯ # Create your own spy bot in Telegram ¯\_(ツ)_/¯
This bot is able to get every message in your group, log it and send simple stat about top 10 used words. This bot is able to get every message in your group, log it and send simple stat about top 10 used words.
Also bot can summon every active member of group with @here command.
/code - highlight any code snippet following command. There is lexer guesser but you are able to specify language by adding last line comment like this:
```
/code
class Cat {
constructor(name) {
this.name = name;
}
}
function get_name(any_object) {
console.log(any_object.name);
}
var my_cat = new Cat('Cassandra');
get_name(my_cat)
#js
```
/sql - perform an SQL request to internal bot SQLite database. Any WRITE requests are prohibited.
/stat - your top 10 words in conf.
Also bot can summon every active member of group with @here command or just with mentioning bot username like @test_huy_bot .
**Copyright** **Copyright**
*As probabilistic morphological analyzer (PMA) this bot uses mystem app, which developed by yandex and provided as binary package.* *As probabilistic morphological analyzer (PMA) this bot uses mystem app, which developed by yandex and provided as binary package.*

View File

@ -1,5 +1,6 @@
import datetime as dt import datetime as dt
class DataBase: class DataBase:
def __init__(self, basefile, scheme): def __init__(self, basefile, scheme):
import sqlite3 import sqlite3
@ -35,10 +36,10 @@ class DataBase:
return(self.execute(sql)[0][0]) return(self.execute(sql)[0][0])
def add_user(self, def add_user(self,
username, username,
user_id, user_id,
first_name, first_name,
last_name): last_name):
date = int(dt.datetime.now().strftime("%s")) date = int(dt.datetime.now().strftime("%s"))
sql = """INSERT OR IGNORE INTO sql = """INSERT OR IGNORE INTO
user('id', 'username', 'first_name', 'last_name', 'date') user('id', 'username', 'first_name', 'last_name', 'date')
@ -74,9 +75,9 @@ class DataBase:
date date
) )
self.execute(sql) self.execute(sql)
def get_top(self, user_id, conf_id, limit=10): def get_top(self, user_id, conf_id, limit=10):
sql= """ sql = """
SELECT w.word, COUNT(*) as count FROM relations r SELECT w.word, COUNT(*) as count FROM relations r
LEFT JOIN word w ON w.id = r.word_id LEFT JOIN word w ON w.id = r.word_id
LEFT JOIN `user` u ON u.id = r.user_id LEFT JOIN `user` u ON u.id = r.user_id
@ -99,7 +100,7 @@ class DataBase:
return(result) return(result)
def here(self, user_id, conf_id): def here(self, user_id, conf_id):
sql= """ sql = """
SELECT DISTINCT(u.username) FROM relations r SELECT DISTINCT(u.username) FROM relations r
LEFT JOIN user u LEFT JOIN user u
ON u.id = r.user_id ON u.id = r.user_id
@ -129,20 +130,20 @@ class DataBase:
def command(self, sql): def command(self, sql):
if 'DELETE' in sql.upper() \ if 'DELETE' in sql.upper() \
or 'INSERT' in sql.upper() \ or 'INSERT' in sql.upper() \
or 'UPDATE' in sql.upper() \ or 'UPDATE' in sql.upper() \
or 'DROP' in sql.upper() \ or 'DROP' in sql.upper() \
or 'CREATE' in sql.upper() \ or 'CREATE' in sql.upper() \
or 'ALTER' in sql.upper(): or 'ALTER' in sql.upper():
return('gtfo') return('gtfo')
try: try:
if 'LIMIT' in sql.upper()[-9:]: if 'LIMIT' in sql.upper()[-9:]:
result = self.execute(sql) result = self.execute(sql)
else: else:
result = self.execute(sql + ' limit 20') result = self.execute(sql + ' limit 20')
except Exception as err: except Exception as err:
result = err result = err
return(result) return(result)
def close(self): def close(self):
self.conn.close() self.conn.close()

View File

@ -12,4 +12,4 @@ db = DataBase(
scheme='assets/main.db.sql') scheme='assets/main.db.sql')
global worker global worker
worker = MessageWorker(db = db) worker = MessageWorker(db=db)

View File

@ -1,20 +1,21 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from http.server import HTTPServer,SimpleHTTPRequestHandler,CGIHTTPRequestHandler from http.server import HTTPServer, SimpleHTTPRequestHandler, CGIHTTPRequestHandler
from socketserver import BaseServer from socketserver import BaseServer
import ssl import ssl
import json import json
import settings import settings
# fuckin dirty hack. idk the best way to inherit return func into # fuckin dirty hack. idk the best way to inherit return func into
# RequestHandler class # RequestHandler class
class RequestHandler(SimpleHTTPRequestHandler): class RequestHandler(SimpleHTTPRequestHandler):
def __init__(self, def __init__(self,
request, request,
client_address, client_address,
server): server):
self.worker = settings.worker self.worker = settings.worker
super(RequestHandler, self).__init__( super(RequestHandler, self).__init__(
request=request, request=request,
@ -26,30 +27,29 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'text/html') self.send_header('Content-Type', 'text/html')
self.end_headers() self.end_headers()
length = self.headers.get('content-length') length = self.headers.get('content-length')
post_body = self.rfile.read(int(length)) post_body = self.rfile.read(int(length))
msg = json.loads(post_body.decode("utf-8")) msg = json.loads(post_body.decode("utf-8"))
self.worker.handleUpdate(msg) self.worker.handleUpdate(msg)
def do_GET(self): def do_GET(self):
pass pass
class WebHook: class WebHook:
def __init__(self, def __init__(self,
certfile, certfile,
keyfile, keyfile,
address = '0.0.0.0', address='0.0.0.0',
port=8443, port=8443,
RequestHandler=RequestHandler): RequestHandler=RequestHandler):
self.httpd = HTTPServer((address, port), RequestHandler) self.httpd = HTTPServer((address, port), RequestHandler)
self.httpd.socket = ssl.wrap_socket (self.httpd.socket, self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
certfile=certfile, certfile=certfile,
keyfile=keyfile, keyfile=keyfile,
server_side=True) server_side=True)
def serve(self): def serve(self):
try: try:

161
worker.py
View File

@ -7,17 +7,63 @@ from database import DataBase
import os import os
import urllib.request import urllib.request
from urllib.parse import urlencode from urllib.parse import urlencode
import requests
import json
import settings import settings
import re import re
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.lexers import guess_lexer, get_lexer_by_name
from pygments.styles import get_style_by_name
from pygments.formatters import ImageFormatter
class MessageWorker: class MessageWorker:
def __init__(self, db, stop_words = 'assets/stop-word.ru'): def __init__(self, db, stop_words='assets/stop-word.ru', settings=settings):
self.stop_words = stop_words self.stop_words = stop_words
self.db = db self.db = db
self.telegram_key = settings.parser.get('bot', 'telegram_key')
self.telegram_api = settings.parser.get('bot', 'telegram_api')
self.me = self.getMe()
print("My name is %s" % self.me['result']['username'])
def getMe(self):
url = self.telegram_api + 'bot' + self.telegram_key + '/getMe'
print(url)
urllib.request.Request(url)
request = urllib.request.Request(url)
raw = urllib.request.urlopen(request).read().decode()
return json.loads(raw)
def handleUpdate(self, msg): def handleUpdate(self, msg):
try: try:
if msg['message']['text'] == '/scheme': try:
input_message = msg['message']['text']
if ('@here' in input_message) or (' @'+self.me['result']['username'] in input_message):
conf_id = msg['message']['chat']['id']
user_id = msg['message']['from']['id']
chat_title = msg['message']['chat']['title']
self.db.add_conf(conf_id, chat_title)
if msg['message']['text'] != '@here':
message = msg['message']['text'].replace('@here', '\n').replace(' @'+self.me['result']['username'], '\n')
else:
message = """I summon you!\n"""
users = self.db.here(
user_id=user_id,
conf_id=conf_id
)
for user in users:
message += ' @%s ' % (user[0])
self.send(id=conf_id, msg=message)
return True
input_message = msg['message']['text'].replace(
'@' + self.me['result']['username'], '')
except:
input_message = msg['message']['text']
if input_message == '/scheme':
conf_id = msg['message']['chat']['id'] conf_id = msg['message']['chat']['id']
user_id = msg['message']['from']['id'] user_id = msg['message']['from']['id']
chat_title = msg['message']['chat']['title'] chat_title = msg['message']['chat']['title']
@ -25,7 +71,7 @@ class MessageWorker:
self.send(id=conf_id, msg='```\n' + self.db.scheme + '\n```') self.send(id=conf_id, msg='```\n' + self.db.scheme + '\n```')
return True return True
if msg['message']['text'] == '/stat': if input_message == '/stat':
conf_id = msg['message']['chat']['id'] conf_id = msg['message']['chat']['id']
user_id = msg['message']['from']['id'] user_id = msg['message']['from']['id']
chat_title = msg['message']['chat']['title'] chat_title = msg['message']['chat']['title']
@ -41,7 +87,7 @@ class MessageWorker:
self.send(id=conf_id, msg=message) self.send(id=conf_id, msg=message)
return True return True
if msg['message']['text'] == '/reset': if input_message == '/reset':
conf_id = msg['message']['chat']['id'] conf_id = msg['message']['chat']['id']
user_id = msg['message']['from']['id'] user_id = msg['message']['from']['id']
chat_title = msg['message']['chat']['title'] chat_title = msg['message']['chat']['title']
@ -53,7 +99,7 @@ class MessageWorker:
user_id=user_id) user_id=user_id)
return True return True
if msg['message']['text'][:4] == '/sql': if input_message[:4] == '/sql':
conf_id = msg['message']['chat']['id'] conf_id = msg['message']['chat']['id']
user_id = msg['message']['from']['id'] user_id = msg['message']['from']['id']
chat_title = msg['message']['chat']['title'] chat_title = msg['message']['chat']['title']
@ -62,35 +108,63 @@ class MessageWorker:
res = self.db.command(sql) res = self.db.command(sql)
if 'syntax' in str(res) \ if 'syntax' in str(res) \
or 'ambiguous' in str(res): or 'ambiguous' in str(res):
self.send(id=conf_id, msg=str(res)) self.send(id=conf_id, msg=str(res))
return False return False
try: try:
msg = '```\n' msg = '```\n'
for z in res: for z in res:
for i in z: for i in z:
msg = msg + str(i) + '\t' msg = msg + str(i) + '\t'
msg = msg + '\n' msg = msg + '\n'
except: except:
msg = res msg = res
self.send(id=conf_id, msg=msg + ' ```') self.send(id=conf_id, msg=msg + ' ```')
return True return True
if msg['message']['text'][:5] == '@here' or \ # if '@here' in input_message:
msg['message']['text'][5:] == '@here': # conf_id = msg['message']['chat']['id']
# user_id = msg['message']['from']['id']
# chat_title = msg['message']['chat']['title']
# self.db.add_conf(conf_id, chat_title)
# if msg['message']['text'] != '@here':
# message = msg['message']['text'].replace('@here', '\n')
# else:
# message = """I summon you!\n"""
# users = self.db.here(
# user_id=user_id,
# conf_id=conf_id
# )
# for user in users:
# message += ' @%s ' % (user[0])
# self.send(id=conf_id, msg=message)
# return True
if input_message[:5] == '/code':
conf_id = msg['message']['chat']['id'] conf_id = msg['message']['chat']['id']
user_id = msg['message']['from']['id'] user_id = msg['message']['from']['id']
chat_title = msg['message']['chat']['title'] chat_title = msg['message']['chat']['title']
self.db.add_conf(conf_id, chat_title) self.db.add_conf(conf_id, chat_title)
if len(msg['message']['text'][6:]) < 10000:
message = """I summon you!\n""" code = msg['message']['text'][6:]
users = self.db.here( code_tag = code[code.rfind('#') + 1:]
user_id=user_id, try:
conf_id=conf_id lexer = get_lexer_by_name(code_tag)
) code = code[:code.rfind('#')]
for user in users: print("Lexer is defined as %s" % lexer)
message += '@%s ' % (user[0]) except:
self.send(id=conf_id, msg=message) lexer = guess_lexer(code)
print("lexer is %s" % lexer)
if lexer.name == 'Text only':
lexer = get_lexer_by_name('python')
highlight(code, lexer, ImageFormatter(
font_size=16,
line_number_bg="#242e0c",
line_number_fg="#faddf2",
line_number_bold=True,
font_name='DejaVuSansMono',
style=get_style_by_name('monokai')), outfile="code.png")
self.send_img(conf_id)
return True return True
except: except:
return False return False
@ -108,35 +182,34 @@ class MessageWorker:
user_id = msg['message']['from']['id'] user_id = msg['message']['from']['id']
chat_id = msg['message']['chat']['id'] chat_id = msg['message']['chat']['id']
chat_title = msg['message']['chat']['title'] chat_title = msg['message']['chat']['title']
#print(self.clean_text(text))
except: except:
return False return False
collection = self.clean_text(text) collection = self.clean_text(text)
self.db.add_user(username, self.db.add_user(username,
user_id, user_id,
first_name, first_name,
last_name) last_name)
self.db.add_conf(chat_id, chat_title) self.db.add_conf(chat_id, chat_title)
for word in collection: for word in collection:
self.db.add_relation(word=word, user_id=user_id, conf_id=chat_id) self.db.add_relation(word=word, user_id=user_id, conf_id=chat_id)
def clean_text(self, s): def clean_text(self, s):
file = open(self.stop_words, 'rt') file = open(self.stop_words, 'rt')
sw = file.read().split('\n') sw = file.read().split('\n')
file.close() file.close()
s = re.sub(r'(https?:\/\/)?([\da-z\.-]+)\.([\/\w\.-]*)*\/?\S','',s,flags=re.MULTILINE) s = re.sub(
r'(https?:\/\/)?([\da-z\.-]+)\.([\/\w\.-]*)*\/?\S', '', s, flags=re.MULTILINE)
print(s) print(s)
# dirty hack with dat fucking file # dirty hack with dat fucking file
fh = open("tmp.txt","w") fh = open("tmp.txt", "w")
fh.write(s) fh.write(s)
fh.close() fh.close()
cmd = "./assets/mystem -nlwd < tmp.txt" cmd = "./assets/mystem -nlwd < tmp.txt"
ps = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE) ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
output = ps.communicate()[0] output = ps.communicate()[0]
os.remove("tmp.txt") os.remove("tmp.txt")
# end of the fuckin' dirty hack # end of the fuckin' dirty hack
@ -145,7 +218,7 @@ class MessageWorker:
s = s.split('\n') s = s.split('\n')
collection = [] collection = []
for word in s: for word in s:
if len(word) >2: if len(word) > 2:
if word not in sw: if word not in sw:
collection.append(word) collection.append(word)
else: else:
@ -155,17 +228,21 @@ class MessageWorker:
return collection return collection
def send(self, id, msg): def send(self, id, msg):
print(msg) # print(msg)
url = settings.parser.get('bot', 'telegram_api') + \ url = self.telegram_api + 'bot' + self.telegram_key + '/sendMessage'
'bot'+ settings.parser.get('bot', 'telegram_key') \
+ '/sendMessage'
post_fields = { post_fields = {
'text': msg, 'text': msg,
'chat_id': id, 'chat_id': id,
'parse_mode': 'Markdown', 'parse_mode': 'Markdown',
'disable_web_page_preview': 1 'disable_web_page_preview': 1
} }
urllib.request.Request(url, urlencode(post_fields).encode()) urllib.request.Request(url, urlencode(post_fields).encode())
request = urllib.request.Request(url, urlencode(post_fields).encode()) request = urllib.request.Request(url, urlencode(post_fields).encode())
json = urllib.request.urlopen(request).read().decode() json = urllib.request.urlopen(request).read().decode()
return json return json
def send_img(self, id):
url = self.telegram_api + 'bot' + self.telegram_key + '/sendPhoto'
data = {'chat_id': id}
files = {'photo': open('code.png', 'rb')}
r = requests.post(url, files=files, data=data)