This commit is contained in:
Joan Cano
2023-03-01 22:19:10 +01:00
parent f961bfd3cf
commit 53004cb3d1
10 changed files with 67 additions and 82 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

View File

View File

@@ -2,6 +2,7 @@ import json
import os import os
import threading import threading
import logging import logging
import prettytable
from worker import Worker from worker import Worker
from telegram import ForceReply, Update from telegram import ForceReply, Update
@@ -21,9 +22,13 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def parse_json_file(): def parse_json_file():
f = open("args.json") f = open("products.json")
return json.load(f) return json.load(f)
def save_json_file(products):
with open('products.json', 'w') as outfile:
json.dump(products, outfile, indent=2)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /help is issued.""" """Send a message when the command /help is issued."""
message = "Add with /add product;min_price;max_price" message = "Add with /add product;min_price;max_price"
@@ -32,10 +37,11 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
async def add_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def add_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
parsed = update.message.text.split(";") parsed = update.message.text.split(";")
logging.info(parsed) logging.info(parsed)
if len(parsed) != 3: if len(parsed) < 3:
message = "You must put the correct number of arguments: /add product;min_price;max_price" #message = "You must pass the correct number of arguments: /add product;min_price;max_price;latitude(optional);longitude(optional);distance(optional);title_exclude(optional);title_description_exclude(optional)"
message = "You must pass the correct number of arguments: /add product;min_price;max_price"
else: else:
argument = {"product_name": f"{parsed[0][5:]}", #removes "/add " product = {"product_name": f"{parsed[0][len('/add '):]}", #removes "/add "
"distance": "0", "distance": "0",
"latitude": f"{LATITUDE}", "latitude": f"{LATITUDE}",
"longitude": f"{LONGITUDE}", "longitude": f"{LONGITUDE}",
@@ -45,31 +51,40 @@ async def add_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Non
"title_keyword_exclude" : [], "title_keyword_exclude" : [],
"exclude": [] "exclude": []
} }
p = threading.Thread(target=Worker.run, args=(argument, )) products = parse_json_file()
products.append(product)
save_json_file(products)
p = threading.Thread(target=Worker.run, args=(product, ))
p.start() p.start()
message = f"Added {parsed[0][5:]}" message = f"Added {parsed[0][5:]}"
await update.message.reply_text(message) await update.message.reply_text(message)
async def remove_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def remove_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Help!") products = parse_json_file()
product_to_remove = update.message.text[len('/remove '):]
for product in products:
if product['product_name'] == product_to_remove:
products.remove(product)
save_json_file(products)
await update.message.reply_text(f"{product_to_remove} removed!")
async def list_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def list_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
args = parse_json_file() products = parse_json_file()
product_list = ""
for product in args:
product_list = f"{product_list}\n{product['product_name']};{product['min_price']}-{product['max_price']}"
await update.message.reply_text(product_list)
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: table = prettytable.PrettyTable(['Product', 'Min', 'Max'])
"""Echo the user message.""" table.align['Product'] = 'l'
await update.message.reply_text(update.message.text) table.align['Min Price'] = 'r'
table.align['Max Price'] = 'r'
for product in products:
table.add_row([product['product_name'], f"{product['min_price']}", f"{product['max_price']}"])
await update.message.reply_markdown_v2(f'```{table}```')
def main()->None: def main()->None:
args = parse_json_file() products = parse_json_file()
for argument in args: for product in products:
logging.info(argument) logging.info(product)
p = threading.Thread(target=Worker.run, args=(argument, )) p = threading.Thread(target=Worker.run, args=(product, ))
p.start() p.start()
"""Start the bot.""" """Start the bot."""

View File

@@ -1,24 +0,0 @@
[
{
"product_name": "zapatillas",
"distance": "0",
"latitude": "40.4165",
"longitude": "-3.70256",
"condition": "all",
"min_price": "0",
"max_price": "75",
"title_keyword_exclude" : [],
"exclude": []
},
{
"product_name": "placa base",
"distance": "0",
"latitude": "40.4165",
"longitude": "-3.70256",
"condition": "all",
"min_price": "0",
"max_price": "75",
"title_keyword_exclude" : [],
"exclude": []
}
]

View File

@@ -1 +0,0 @@
grafica worker crashed. 'title_key_word_exclude'grafica: Trying to parse 9jd5lyeq726k: portatil toshiba satelite pro i3 r50 .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzvlmp1dg46l: torre pc acer para piezas sin el disco duro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 8j3xn10dmlj9: Samsung Galaxy J5 2015 , SM-J500FN . Dorado .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse pzp1p4nql9z3: Memoria ram kingston hyperx ddr2 4 gb a 1.066 MH .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 8z8gxdxyol63: MÓVILES HUAWEI P8 LITE .grafica worker crashed. 'title_key_word_exclude'grafica: Trying to parse 9jd5lyeq726k: portatil toshiba satelite pro i3 r50 .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse e6530nvvpgzo: Dos módulos de memoria RAM .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse qjwdo3ly5wzo: Torre AMD Athlon 64 X2 Dual Core 6000+ .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzy72wddwvz5: Memoria ram Kingston 3gb DDR2,800mhz y 667mhz .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 36enl0qm3y6d: Servicio Técnico Apple Valencia .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse x6q90e52oozy: GALAXY J3 (2016) 8GB negro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse p617rwnr7565: Memoria Ram DDR3 1600 mHz (2 módulos x 4GB) .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse pj9g19o1d06e: Ram ddr4 1gb .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzvlmp1dg46l: torre pc acer para piezas sin el disco duro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 8j3xn10dmlj9: Samsung Galaxy J5 2015 , SM-J500FN . Dorado .grafica worker crashed. 'title_key_word_exclude'grafica: Trying to parse mznv5n09ok6n: torre ordenador i5 8GB SSD 240GB HDMI .grafica worker crashed. 'title_key_word_exclude'grafica: Trying to parse nzxyk71xg1j2: ASUS PH-GT1030-O2G GT 1030 2GB GDDR5 .grafica worker crashed. 'title_key_word_exclude'grafica: Trying to parse wzvlmpw7k46l: Ordenador portatil HP Probook (560) .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse e6530nvvpgzo: Dos módulos de memoria RAM .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse qjwdo3ly5wzo: Torre AMD Athlon 64 X2 Dual Core 6000+ .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzy72wddwvz5: Memoria ram Kingston 3gb DDR2,800mhz y 667mhz .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 36enl0qm3y6d: Servicio Técnico Apple Valencia .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse x6q90e52oozy: GALAXY J3 (2016) 8GB negro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse p617rwnr7565: Memoria Ram DDR3 1600 mHz (2 módulos x 4GB) .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse pj9g19o1d06e: Ram ddr4 1gb .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzvlmp1dg46l: torre pc acer para piezas sin el disco duro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 8j3xn10dmlj9: Samsung Galaxy J5 2015 , SM-J500FN . Dorado .grafica worker crashed. 'title_key_word_exclude'grafica: Trying to parse nzxyk71xg1j2: ASUS PH-GT1030-O2G GT 1030 2GB GDDR5 .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse e6530nvvpgzo: Dos módulos de memoria RAM .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse qjwdo3ly5wzo: Torre AMD Athlon 64 X2 Dual Core 6000+ .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzy72wddwvz5: Memoria ram Kingston 3gb DDR2,800mhz y 667mhz .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 36enl0qm3y6d: Servicio Técnico Apple Valencia .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse x6q90e52oozy: GALAXY J3 (2016) 8GB negro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse p617rwnr7565: Memoria Ram DDR3 1600 mHz (2 módulos x 4GB) .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse pj9g19o1d06e: Ram ddr4 1gb .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzvlmp1dg46l: torre pc acer para piezas sin el disco duro .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse 8j3xn10dmlj9: Samsung Galaxy J5 2015 , SM-J500FN . Dorado .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse e6530nvvpgzo: Dos módulos de memoria RAM .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse qjwdo3ly5wzo: Torre AMD Athlon 64 X2 Dual Core 6000+ .ram worker crashed. 'title_key_word_exclude'ram: Trying to parse wzy72wddwvz5: Memoria ram Kingston 3gb DDR2,800mhz y 667mhz .

1
wallamanta/products.json Normal file
View File

@@ -0,0 +1 @@
[{"product_name": "zapatillas", "distance": "0", "latitude": "40.4165", "longitude": "-3.70256", "condition": "all", "min_price": "0", "max_price": "75", "title_keyword_exclude": [], "exclude": []}, {"product_name": "placa base", "distance": "0", "latitude": "40.4165", "longitude": "-3.70256", "condition": "all", "min_price": "0", "max_price": "75", "title_keyword_exclude": [], "exclude": []}]

View File

@@ -1,2 +1,3 @@
python-telegram-bot==20.1 python-telegram-bot==20.1
requests==2.28.1 requests==2.28.1
prettytable==3.6.0

View File

@@ -1,18 +0,0 @@
import telegram
import os
TELEGRAM_CHANNEL_ID = os.getenv("TELEGRAM_CHANNEL_ID")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
LATITUDE = os.getenv("LATITUDE")
LONGITUDE = os.getenv("LONGITUDE")
SLEEP_TIME = os.getenv("SLEEP_TIME")
class TelegramHandler:
def run():
updater = Updater(TELEGRAM_TOKEN)
dispatcher = updater.dispatcher
dispatcher.add_handler())
updater.start_polling()
updater.idle()

View File

@@ -3,6 +3,7 @@ import requests
import telegram import telegram
import os import os
import logging import logging
import json
TELEGRAM_CHANNEL_ID = os.getenv("TELEGRAM_CHANNEL_ID") TELEGRAM_CHANNEL_ID = os.getenv("TELEGRAM_CHANNEL_ID")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN") TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
@@ -45,25 +46,33 @@ class Worker:
json_data = response.json() json_data = response.json()
return json_data['search_objects'] return json_data['search_objects']
def first_run(self, args): def first_run(self, product):
list = [] list = []
articles = self.request(args['product_name'], 0, args['latitude'], args['longitude'], args['distance'], args['condition'], args['min_price'], args['max_price']) articles = self.request(product['product_name'], 0, product['latitude'], product['longitude'], product['distance'], product['condition'], product['min_price'], product['max_price'])
for article in articles: for article in articles:
list.insert(0, article['id']) list.insert(0, article['id'])
return list return list
def work(self, args, list): def work(self, product, list):
exec_times = [] exec_times = []
bot = telegram.Bot(token = TELEGRAM_TOKEN) bot = telegram.Bot(token = TELEGRAM_TOKEN)
while True: while True:
f = open("products.json")
products = json.load(f)
found = False
for fproduct in products:
if fproduct['product_name'] == product['product_name']:
found = True
break
if not found:
break # Exits worker if product not in list anymore
start_time = time.time() start_time = time.time()
articles = self.request(args['product_name'], 0, args['latitude'], args['longitude'], args['distance'], args['condition'], args['min_price'], args['max_price']) articles = self.request(product['product_name'], 0, product['latitude'], product['longitude'], product['distance'], product['condition'], product['min_price'], product['max_price'])
for article in articles: for article in articles:
if not article['id'] in list: if not article['id'] in list:
logging.info("Found article {}".format(article['title'])) logging.info("Found article {}".format(article['title']))
try: try:
if not self.has_excluded_words(article['title'].lower(), article['description'].lower(), args['exclude']) and not self.is_title_key_word_excluded(article['title'].lower(), args['title_keyword_exclude']): if not self.has_excluded_words(article['title'].lower(), article['description'].lower(), product['exclude']) and not self.is_title_key_word_excluded(article['title'].lower(), product['title_keyword_exclude']):
try: try:
text = f"*Artículo*: {article['title']}\n*Descripción*: {article['description']}\n*Precio*: {article['price']} {article['currency']}\n[Ir al anuncio](https://es.wallapop.com/item/{article['web_slug']})".replace(".", "\.") text = f"*Artículo*: {article['title']}\n*Descripción*: {article['description']}\n*Precio*: {article['price']} {article['currency']}\n[Ir al anuncio](https://es.wallapop.com/item/{article['web_slug']})".replace(".", "\.")
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage?chat_id={TELEGRAM_CHANNEL_ID}&text={text}&parse_mode=MarkdownV2" url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage?chat_id={TELEGRAM_CHANNEL_ID}&text={text}&parse_mode=MarkdownV2"
@@ -76,12 +85,12 @@ class Worker:
list.insert(0, article['id']) list.insert(0, article['id'])
except Exception as e: except Exception as e:
logging.info("---------- EXCEPTION -----------") logging.info("---------- EXCEPTION -----------")
logging.info(f"{args['product_name']} worker crashed. {e}") logging.info(f"{product['product_name']} worker crashed. {e}")
logging.info(f"{args['product_name']}: Trying to parse {article['id']}: {article['title']} .\n") logging.info(f"{product['product_name']}: Trying to parse {article['id']}: {article['title']} .\n")
time.sleep(SLEEP_TIME) time.sleep(SLEEP_TIME)
exec_times.append(time.time() - start_time) exec_times.append(time.time() - start_time)
logging.info(f"\'{args['product_name']}\' node-> last: {exec_times[-1]} max: {self.get_max_time(exec_times)} avg: {self.get_average_time(exec_times)}") logging.info(f"\'{product['product_name']}\' node-> last: {exec_times[-1]} max: {self.get_max_time(exec_times)} avg: {self.get_average_time(exec_times)}")
def has_excluded_words(self, title, description, excluded_words): def has_excluded_words(self, title, description, excluded_words):
for word in excluded_words: for word in excluded_words:
@@ -112,15 +121,16 @@ class Worker:
largest = i largest = i
return largest return largest
def run(args): def run(product):
worker = Worker() worker = Worker()
list = worker.first_run(args) list = worker.first_run(product)
while True: while True:
try: try:
logging.info(f"Wallapop monitor worker started. Checking for new items containing: \'{args['product_name']}\' with given parameters periodically") logging.info(f"Wallapop monitor worker started. Checking for new items containing: \'{product['product_name']}\' with given parameters periodically")
worker.work(args, list) worker.work(product, list)
break
except Exception as e: except Exception as e:
logging.info(f"Exception: {e}") logging.info(f"Exception: {e}")
logging.info(f"{args['product_name']} worker crashed. Restarting worker...") logging.info(f"{product['product_name']} worker crashed. Restarting worker...")
time.sleep(10) time.sleep(10)
logging.info(f"Wallapop monitor worker stopped for: \'{product['product_name']}\'")