refactor major
This commit is contained in:
@@ -1 +1,29 @@
|
||||
|
||||
import environ
|
||||
#import pysolr
|
||||
from fet2020api import fet2020postapi
|
||||
from urllib.parse import urljoin
|
||||
# This is the settings part for the bot logic
|
||||
#from misc import post_to_solr
|
||||
from .users import UserManager
|
||||
|
||||
|
||||
env=environ.Env(
|
||||
CHATFILE=(str,"chats.yaml"),
|
||||
USERFILE=(str,"users.yaml"),
|
||||
TARGET=(str,"https://alpha.2020.fet.at"),
|
||||
URL_HOSTNAME=(str, "bot.2020.fet.at" ),
|
||||
|
||||
)
|
||||
|
||||
CHATFILE= env('CHATFILE')
|
||||
USERFILE=env('USERFILE')
|
||||
TARGET=env('TARGET')
|
||||
URL_HOSTNAME=env('URL_HOSTNAME')
|
||||
|
||||
fet=fet2020postapi(urljoin(TARGET,"api/posts/"))
|
||||
|
||||
users=UserManager()
|
||||
|
||||
|
||||
class Storage():
|
||||
pass
|
||||
|
||||
263
bot1/chats.py
263
bot1/chats.py
@@ -1,10 +1,261 @@
|
||||
class Chat():
|
||||
def __init__(id):
|
||||
import yaml
|
||||
from . import CHATFILE, TARGET, URL_HOSTNAME
|
||||
from pytgbot.bot.sync import SyncBot
|
||||
from pytgbot.api_types.sendable.inline import InlineQueryResultArticle, InputTextMessageContent
|
||||
from pytgbot.api_types.receivable.inline import InlineQuery
|
||||
from pytgbot.api_types.receivable.updates import Update
|
||||
from .states import CreatePostWorkflow
|
||||
from urllib.parse import urljoin
|
||||
#from . import solr,reindex
|
||||
from solrfet2020 import SolrFet2020
|
||||
from . import fet, users
|
||||
from .users import User
|
||||
from io import BytesIO
|
||||
from key import API_KEY
|
||||
import requests
|
||||
from .workflows import AuthWorkflow
|
||||
import logging
|
||||
logger=logging.getLogger()
|
||||
from misc import SaveFileMapping, SaveFileObject
|
||||
from lxml.html.clean import clean_html, Cleaner
|
||||
|
||||
solr=SolrFet2020()
|
||||
def TelegramIntegrationException(Exception):
|
||||
pass
|
||||
|
||||
def get_chat_id(update):
|
||||
if update.message and update.message.chat:
|
||||
return update.message.chat.id
|
||||
if update.callback_query:
|
||||
return update.callback_query.message.chat.id
|
||||
|
||||
def get_message_id(update):
|
||||
if update.message:
|
||||
return update.message.message_id
|
||||
|
||||
def get_from_id(update):
|
||||
if update.message and update.message.to_array():
|
||||
return str(update.message.to_array()["from"]["id"])
|
||||
if update.callback_query and update.callback_query.to_array():
|
||||
return str(update.callback_query.to_array()["from"]["id"])
|
||||
|
||||
def get_from(update):
|
||||
if update.message and update.message.to_array():
|
||||
return (update.message.to_array()["from"])
|
||||
if update.callback_query and update.callback_query.to_array():
|
||||
return (update.callback_query.to_array()["from"])
|
||||
|
||||
def download_file(url):
|
||||
local_filename = url.split('/')[-1]
|
||||
# NOTE the stream=True parameter below
|
||||
with requests.get(url, stream=True) as r:
|
||||
r.raise_for_status()
|
||||
with open(local_filename, 'wb') as f:
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
# If you have chunk encoded response uncomment if
|
||||
# and set chunk_size parameter to None.
|
||||
#if chunk:
|
||||
f.write(chunk)
|
||||
return local_filename
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Chat(SaveFileObject):
|
||||
def __init__(self,id, bot):
|
||||
self.id=id
|
||||
#self.chat_id=chat_id
|
||||
self.bot=bot
|
||||
self.workflows={}
|
||||
self.last_message_id=None
|
||||
self.debug=False
|
||||
self.type="group"
|
||||
self.mode =""
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"workflows": self.workflows,
|
||||
"debug": self.debug,
|
||||
"type": self.type,
|
||||
"mode": self.mode
|
||||
}
|
||||
def asdict(self):
|
||||
return self.to_dict()
|
||||
@classmethod
|
||||
def from_dict(self, bot, d):
|
||||
c=Chat(d["id"],bot)
|
||||
c.debug=d["debug"]
|
||||
c.type=d["type"]
|
||||
c.mode=d.get("mode","")
|
||||
return c
|
||||
|
||||
def inline(self, query):
|
||||
InlineQueryResultArticle
|
||||
r=solr.search("text_txt: %s" % query.query,sort="date_dt desc").docs
|
||||
r= [fet.find_one({"slug": rr["id"]}) for rr in r ]
|
||||
res=[ InlineQueryResultArticle(
|
||||
id=d["url"],
|
||||
title=d["title"],
|
||||
url=urljoin(TARGET,d["url"]),
|
||||
thumb_url = urljoin(TARGET,d["image"]),
|
||||
input_message_content = InputTextMessageContent(message_text=urljoin(TARGET,d["url"]))
|
||||
) for d in r]
|
||||
links=[p["title"]+": "+"(%s) "% p["public_date"] + urljoin(TARGET,p["url"]) for p in r]
|
||||
self.bot.answer_inline_query(inline_query_id=query.id,results=res )
|
||||
return
|
||||
|
||||
def process_update(self,update):
|
||||
|
||||
if update.inline_query:
|
||||
return self.inline(update.inline_query)
|
||||
self.last_message_id=get_message_id(update)
|
||||
|
||||
w=self.workflows.get(get_from_id(update),None)
|
||||
if w:
|
||||
self.workflows[get_from_id(update)]=w
|
||||
if w and w.is_finished:
|
||||
self.workflows[get_from_id(update)]=None
|
||||
w=None
|
||||
if self.debug:
|
||||
self.echo(update)
|
||||
|
||||
if update.message and w:
|
||||
w.process_message(update.message.text)
|
||||
|
||||
if update.callback_query:
|
||||
self.send_msg("processing callback %s" % str(w))
|
||||
if w:
|
||||
w.process_message(update.callback_query.data)
|
||||
self.bot.edit_message_text(
|
||||
chat_id=update.callback_query.message.chat.id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
text=update.callback_query.message.text+": "+update.callback_query.data)
|
||||
|
||||
if update.message and update.message.photo:
|
||||
ff=max(update.message.photo, key=lambda x: x.file_size)
|
||||
fff = self.bot.get_file(ff.file_id)
|
||||
url=fff.get_download_url(API_KEY)
|
||||
r = requests.get(url)
|
||||
fn = url.split('/')[-1]
|
||||
media=(fn, BytesIO(r.content),r.headers["content-type"])
|
||||
if w:
|
||||
w.process_message(update.message.text, media=media)
|
||||
if update.message and update.message.chat:
|
||||
self.type=update.message.chat.type
|
||||
u=users[update]
|
||||
users.to_file()
|
||||
|
||||
def echo(self,update):
|
||||
self.reply_msg(yaml.dump(update._raw)+"\nfrom"+str(get_from_id(update)))
|
||||
def send_msg(self, text,**kwargs):
|
||||
self.bot.send_message(chat_id=self.id, text=text, **kwargs)
|
||||
def reply_msg(self, text, **kwargs):
|
||||
self.bot.send_message(chat_id=self.id, text=text, reply_to_message_id=self.last_message_id,**kwargs)
|
||||
|
||||
def process_command(self,cmd,txt,update):
|
||||
self.last_message_id=get_message_id(update)
|
||||
|
||||
u=users[update]
|
||||
|
||||
print("--\ndict: %s" % str(users.asdict()))
|
||||
|
||||
#users[get_from_id(update)]=u
|
||||
if cmd == "/s" or cmd =="/search":
|
||||
self.reply_msg("searching ...")
|
||||
links,hits=solr.search(txt)
|
||||
for d in links:
|
||||
self.send_msg(str(d["text"]), parse_mode="html")
|
||||
if hits==0:
|
||||
self.send_msg("Nichts gefunden sorry.")
|
||||
return True
|
||||
|
||||
elif cmd =="/mode":
|
||||
self.mode=txt
|
||||
self.reply_msg("Mode: %s" % txt)
|
||||
return True
|
||||
elif cmd == "/debug":
|
||||
if self.debug:
|
||||
self.debug = False
|
||||
else:
|
||||
self.debug = True #not self.debug
|
||||
self.reply_msg("Debug: %s" % str(self.debug))
|
||||
return True
|
||||
elif cmd =="/post":
|
||||
if not u.fet_user:
|
||||
self.reply_msg("bitte vorher /auth ausführen wenn du ein FET Mitglied bist")
|
||||
return True
|
||||
self.workflows[get_from_id(update)]=CreatePostWorkflow(chat=self)
|
||||
return True
|
||||
elif cmd == "/reindex":
|
||||
self.reply_msg("Das kann ein bissl dauern...")
|
||||
solr.reindex()
|
||||
self.send_msg("Fertig mit dem neuen Index")
|
||||
return True
|
||||
elif cmd == "/auth":
|
||||
if u.fet_user:
|
||||
self.reply_msg("Du bist schon authentifiziert...")
|
||||
else:
|
||||
u.token="afwerve"
|
||||
u.a=1
|
||||
self.reply_msg(urljoin("https://"+URL_HOSTNAME, "/auth/%s/%s" %(u.id, u.token)))
|
||||
self.send_msg(urljoin("https://",URL_HOSTNAME))
|
||||
users.to_file()
|
||||
return True
|
||||
else: return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def get_command_from_update(update):
|
||||
"get the command and the text if there is one"
|
||||
if update.message and update.message.entities and update.message.entities[0].type=="bot_command":
|
||||
return update.message.text[update.message.entities[0].offset:update.message.entities[0].offset+update.message.entities[0].length], \
|
||||
update.message.text[update.message.entities[0].offset+update.message.entities[0].length:].strip()
|
||||
return None, None
|
||||
|
||||
|
||||
class ChatManager():
|
||||
def __init__():
|
||||
self.chats=dict()
|
||||
|
||||
|
||||
class ChatManager(SaveFileMapping):
|
||||
|
||||
def __init__(self,bot):
|
||||
super().__init__(file=CHATFILE)
|
||||
if not type(bot) is SyncBot:
|
||||
raise TelegramIntegrationException("This ChatManager needs a SyncBot from pytgbot as an argument")
|
||||
self.bot=bot
|
||||
self.from_file()
|
||||
def __getitem__(self,key):
|
||||
if type(key) is Update:
|
||||
return self.__getitem__(get_chat_id(key))
|
||||
c=super().__getitem__(key)
|
||||
if not c:
|
||||
c=Chat(id=key,bot=self.bot)
|
||||
self[key]=c
|
||||
return c
|
||||
|
||||
def from_dict(self,dd):
|
||||
if type(dd) is dict:
|
||||
self.data={dd[d]["id"]: Chat.from_dict(self.bot,dd[d]) for d in dd}
|
||||
elif dd is not None:
|
||||
raise AttributeError("from_dict needs a dict got %s" % str(type(dd)))
|
||||
|
||||
def send(self, chat_id=None, text=None):
|
||||
if int(chat_id) in self.chats:
|
||||
self.chats[int(chat_id)].send_msg(text)
|
||||
else:
|
||||
logger.debug(yaml.dump(self.chats))
|
||||
|
||||
def process_update(self,update):
|
||||
chat=self[update]
|
||||
cmd, txt = get_command_from_update(update)
|
||||
if cmd:
|
||||
if not chat.process_command(cmd,txt,update):
|
||||
chat.reply_msg("Wrong command %s" % cmd)
|
||||
else:
|
||||
chat.process_update(update)
|
||||
self.to_file()
|
||||
|
||||
0
bot1/integration.py
Normal file
0
bot1/integration.py
Normal file
182
bot1/states.py
182
bot1/states.py
@@ -1,128 +1,150 @@
|
||||
from statemachine import StateMachine, State
|
||||
from pytgbot.api_types.sendable.reply_markup import InlineKeyboardButton,InlineKeyboardMarkup
|
||||
|
||||
from . import fet,TARGET
|
||||
from urllib.parse import urljoin
|
||||
import logging
|
||||
logger=logging.getLogger()
|
||||
import datetime
|
||||
|
||||
class TelegramStateMachine(StateMachine):
|
||||
def __init__(self,chat_id=None, bot=None,*args,**kwargs):
|
||||
self.chat_id = chat_id
|
||||
self.user_id = user_id
|
||||
# self.context=context
|
||||
self.bot = bot
|
||||
def __init__(self,chat=None,*args,**kwargs):
|
||||
self.chat = chat
|
||||
super().__init__(*args,**kwargs)
|
||||
|
||||
def send(self,text:str):
|
||||
self.bot.send_message(chat_id=self.chat_id, text=text)
|
||||
self.chat.send_msg(text)
|
||||
|
||||
def send_confirm(self, text:str):
|
||||
"Ask for a specific confirmation"
|
||||
keyboard = [[InlineKeyboardButton("OK", callback_data='ok'),
|
||||
InlineKeyboardButton("Repeat", callback_data='retry')],
|
||||
[ InlineKeyboardButton("Cancel", callback_data='cancel')] ]
|
||||
#,
|
||||
# InlineKeyboardButton("Cancel", callback_data='3')]
|
||||
#keyboard = [[InlineKeyboardButton("OK", callback_data='1'),
|
||||
#]]
|
||||
|
||||
self.bot.send_message(chat_id=self.chat_id,
|
||||
text=text,
|
||||
self.chat.send_msg(text,
|
||||
reply_markup=InlineKeyboardMarkup(keyboard))
|
||||
|
||||
|
||||
|
||||
class CreatePostWorkflow(TelegramStateMachine):
|
||||
init =State('init', initial=True)
|
||||
class TelegramWorkflow(TelegramStateMachine):
|
||||
def process_message(self, text, is_callback=False):
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
class CreatePostWorkflow(TelegramWorkflow):
|
||||
init =State('init', initial=True) # Initialize the workflow
|
||||
finished = State('finished') # Workflow has ended
|
||||
|
||||
wait = State('wait',value=0)
|
||||
|
||||
confirm =State('confirm')
|
||||
finished = State('finished')
|
||||
|
||||
# finished = State('finished')
|
||||
|
||||
initialize = init.to(wait)
|
||||
entered = wait.to(confirm)
|
||||
next=confirm.to(wait)
|
||||
retry=confirm.to(wait)
|
||||
|
||||
wait_photo=State('wait_photo')
|
||||
initialize = init.to(wait) # init --> wait
|
||||
entered = wait.to(confirm) # wait --> confirm
|
||||
next=confirm.to(wait) # confirm --> wait
|
||||
retry=confirm.to(wait) # confirm --> wait
|
||||
get_photo = confirm.to(wait_photo) # confirm --> wait_photo
|
||||
|
||||
steps={
|
||||
0:'title',
|
||||
1:'text'
|
||||
}
|
||||
#finish = wait_photo.to(finished)
|
||||
finish = finished.from_(wait_photo, confirm)
|
||||
cancel = finished.from_(wait,confirm,wait_photo)
|
||||
|
||||
steps= \
|
||||
[
|
||||
{
|
||||
"id": "title",
|
||||
"question": "Bitte einen Titel für den Post eingeben"
|
||||
},
|
||||
{
|
||||
"id": "body",
|
||||
"question": "jetzt bitte den Text"
|
||||
}
|
||||
]
|
||||
|
||||
@property
|
||||
def current_field(self):
|
||||
print(self.steps[self.step])
|
||||
return self.steps[self.step]["id"]
|
||||
|
||||
finish =confirm.to(finished)
|
||||
cancel = finished.from_(wait,confirm)
|
||||
@property
|
||||
def value(self):
|
||||
return self.p[self.steps[self.step]]
|
||||
return self.p[self.current_field]
|
||||
|
||||
def __init__(self,chat_id=None, context=None,*args, **kwargs):
|
||||
super().__init__(chat_id, context, *args, **kwargs)
|
||||
def __init__(self,chat=None, debug=False,*args, **kwargs):
|
||||
super().__init__(chat, *args, **kwargs)
|
||||
self.p=dict()
|
||||
self.step=0
|
||||
self.debug = debug
|
||||
self.initialize()
|
||||
|
||||
|
||||
def confirmed(self):
|
||||
if self.step >= len(self.steps)-1:
|
||||
self.finish()
|
||||
else:
|
||||
self.next()
|
||||
|
||||
|
||||
# processing messages
|
||||
def process_message(self, text, is_callback=False):
|
||||
if self.current_state==self.wait:
|
||||
self.p[self.steps[self.step]]=text
|
||||
self.send(str(self .p))
|
||||
def process_message(self, text, is_callback=False,media=None):
|
||||
if self.debug: self.send("Workflow Input: %s" % text)
|
||||
if self.current_state==self.wait and text:
|
||||
self.p[self.current_field]=text
|
||||
if self.debug: self.send(str(self.p))
|
||||
self.entered()
|
||||
elif self.current_state==self.confirm:
|
||||
if text=="ok":
|
||||
self.confirmed()
|
||||
elif query.data=='cancel':
|
||||
elif text =='cancel':
|
||||
self.cancel()
|
||||
else:
|
||||
elif text == 'retry':
|
||||
self.retry()
|
||||
|
||||
def button_handler(self, update, context):
|
||||
query = update.callback_query
|
||||
if self.current_state ==self.confirm:
|
||||
if query.data=='ok':
|
||||
self.confirmed()
|
||||
elif query.data=='cancel':
|
||||
self.cancel()
|
||||
else:
|
||||
self.retry()
|
||||
query.edit_message_text(text="Selected option: {}".format(query.data))
|
||||
|
||||
def command_handler(self,update,context):
|
||||
logger.info("Processing Command: %s" % update.message.text)
|
||||
|
||||
def message_handler(self,update,context):
|
||||
if self.current_state==self.wait:
|
||||
self.p[self.steps[self.step]]=update.message.text
|
||||
self.send(str(self.p))
|
||||
self.entered()
|
||||
elif self.current_state==self.confirm:
|
||||
if update.message.text=="ok":
|
||||
self.confirmed()
|
||||
else:
|
||||
self.retry()
|
||||
#file = bot.getFile(update.message.photo[-1].file_id
|
||||
|
||||
|
||||
self.send("Ich warte noch auf eine Antwort bitte ok/cancel/retry")
|
||||
#elif self.current_state == self.wait_photo:
|
||||
if media:
|
||||
if self.debug:
|
||||
self.send("Workflow received media -- yey")
|
||||
self.p["files"]={"image":media}
|
||||
if self.current_state == self.wait_photo:
|
||||
self.finish()
|
||||
#else:
|
||||
# self.retry()
|
||||
return ""
|
||||
def confirmed(self):
|
||||
if self.step >= len(self.steps)-1:
|
||||
self.get_photo()
|
||||
else:
|
||||
self.next()
|
||||
|
||||
def on_next(self):
|
||||
self.step+=1
|
||||
|
||||
def on_retry(self):
|
||||
if self.debug: self.send("retry")
|
||||
|
||||
def on_cancel(self):
|
||||
self.p={}
|
||||
self.step=0
|
||||
self.send("Canceled")
|
||||
#self.p={}
|
||||
#self.step=0
|
||||
if self.debug: self.send("Canceled")
|
||||
|
||||
def on_finish(self):
|
||||
self.step=0
|
||||
self.send("Eingabe fertig:%s" % str(self.p))
|
||||
|
||||
#self.step=0
|
||||
self.p["legacy_id"]=""
|
||||
self.p["post_type"]="N"
|
||||
self.p["has_agenda"]="False"
|
||||
self.p["public_date"]=datetime.date.today()
|
||||
self.send("posting to %s" % TARGET)
|
||||
r,d=fet.create(self.p)
|
||||
print(r)
|
||||
logger.info(r)
|
||||
if r ==201:
|
||||
self.send("Eingabe fertig:%s" % str(urljoin(TARGET,"posts/"+d["slug"])))
|
||||
else:
|
||||
self.send("Fehler beim posten -- sorry")
|
||||
def on_enter_confirm(self):
|
||||
self.send_confirm("Bitte die Eingabe von %s bestaetigen" % self.value)
|
||||
|
||||
def on_enter_wait(self):
|
||||
self.send("Bitte folgendes das Attribut %s eingeben " % self.steps[self.step])
|
||||
self.send(self.steps[self.step]["question"])
|
||||
|
||||
def on_get_photo(self):
|
||||
if not "files" in self.p:
|
||||
self.send("Bitte jetzt ein Foto schicken .. ")
|
||||
else:
|
||||
self.finish()
|
||||
|
||||
|
||||
|
||||
59
bot1/users.py
Normal file
59
bot1/users.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from misc import SaveFileMapping, SaveFileObject
|
||||
from dataclasses import dataclass
|
||||
from pytgbot.api_types.receivable.updates import Update
|
||||
|
||||
def get_from_id(update):
|
||||
if update.message and update.message.to_array():
|
||||
return str(update.message.to_array()["from"]["id"])
|
||||
if update.callback_query and update.callback_query.to_array():
|
||||
return str(update.callback_query.to_array()["from"]["id"])
|
||||
|
||||
def get_from(update):
|
||||
if update.message and update.message.to_array():
|
||||
return (update.message.to_array()["from"])
|
||||
if update.callback_query and update.callback_query.to_array():
|
||||
return (update.callback_query.to_array()["from"])
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class User(SaveFileObject):
|
||||
# pass
|
||||
id: str
|
||||
a: int = 0
|
||||
first_name: str= ''
|
||||
is_bot: bool = False
|
||||
fet_user: bool = False
|
||||
token: str = ""
|
||||
auth: bool= False
|
||||
user: str=""
|
||||
|
||||
def load_from(self,d):
|
||||
if isinstance(d, Update):
|
||||
self.load_from(get_from(d))
|
||||
return
|
||||
self.update(d)
|
||||
|
||||
|
||||
|
||||
class UserManager(SaveFileMapping):
|
||||
def __init__(self):
|
||||
super().__init__(file="users.yaml")
|
||||
self.from_file()
|
||||
|
||||
def from_dict(self,d):
|
||||
for k in d:
|
||||
self[k]=User(**d[k])
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, Update):
|
||||
u=self.__getitem__(get_from_id(key))
|
||||
u.load_from(key)
|
||||
return u
|
||||
u=super().__getitem__(key)
|
||||
if u is None:
|
||||
u=User(id=key)
|
||||
self[key]=u
|
||||
return u
|
||||
|
||||
6
bot1/workflows.py
Normal file
6
bot1/workflows.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .states import TelegramWorkflow
|
||||
from statemachine import StateMachine, State
|
||||
|
||||
class AuthWorkflow(TelegramWorkflow):
|
||||
init =State('init', initial=True) # Initialize the workflow
|
||||
finished = State('finished') # Workflow has ended
|
||||
Reference in New Issue
Block a user