-
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -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
1
wallamanta/products.json
Normal 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": []}]
|
||||||
@@ -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
|
||||||
@@ -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()
|
|
||||||
@@ -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']}\'")
|
||||||
Reference in New Issue
Block a user