Beta launch
This commit is contained in:
commit
4d238c2972
BIN
__pycache__/app.cpython-310.pyc
Normal file
BIN
__pycache__/app.cpython-310.pyc
Normal file
Binary file not shown.
108
app.py
Normal file
108
app.py
Normal file
@ -0,0 +1,108 @@
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
import xml.etree.ElementTree as ET
|
||||
import logging
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
# Parse the XML file and extract data
|
||||
def parse_xml(file_path):
|
||||
try:
|
||||
tree = ET.parse(file_path)
|
||||
root = tree.getroot()
|
||||
|
||||
items = []
|
||||
run_time_element = root.find("run_time")
|
||||
|
||||
if run_time_element is not None:
|
||||
run_time = run_time_element.text
|
||||
else:
|
||||
run_time = 'N/A'
|
||||
|
||||
for item in root.findall('.//item'):
|
||||
title_element = item.attrib.get('title')
|
||||
title = title_element if title_element is not None else 'N/A'
|
||||
|
||||
link_element = item.attrib.get('link')
|
||||
link = link_element if link_element is not None else '#'
|
||||
|
||||
description_element = item.attrib.get('description')
|
||||
description = description_element if description_element is not None else 'N/A'
|
||||
|
||||
source_element = item.attrib.get('source')
|
||||
source = source_element if source_element is not None else 'N/A'
|
||||
|
||||
guid_element = item.attrib.get('guid')
|
||||
guid = guid_element if guid_element is not None else 'N/A'
|
||||
|
||||
pub_date_element = item.attrib.get('pubDate')
|
||||
pub_date = pub_date_element if pub_date_element is not None else 'N/A'
|
||||
|
||||
georss_point_element = item.attrib.get('{http://www.georss.org/georss}point')
|
||||
georss_point = georss_point_element if georss_point_element is not None else 'N/A'
|
||||
|
||||
items.append({
|
||||
'title': title,
|
||||
'link': link,
|
||||
'description': description,
|
||||
'source': source,
|
||||
'guid': guid,
|
||||
'pub_date': pub_date,
|
||||
'georss_point': georss_point
|
||||
})
|
||||
|
||||
return items, run_time # Return the extracted items and run time value
|
||||
|
||||
except FileNotFoundError:
|
||||
app.logger.error('XML file not found.')
|
||||
|
||||
return [], ''
|
||||
|
||||
def write_to_xml(file_path, items):
|
||||
root = ET.Element("root")
|
||||
|
||||
for item in items:
|
||||
new_element = ET.SubElement(root, "item")
|
||||
new_element.set("title", item['title'])
|
||||
new_element.set("link", item['link'])
|
||||
|
||||
tree = ET.ElementTree(root)
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
items, run_time = parse_xml('./indeed_output.xml') # Retrieve the items and run time
|
||||
return render_template('index.html', items=items, run_time=run_time) # Pass the run time to the template
|
||||
|
||||
@app.route('/search')
|
||||
def search():
|
||||
query = request.args.get('query')
|
||||
items, run_time = parse_xml('./indeed_output.xml') # Retrieve the items and run time
|
||||
|
||||
# Filter the items based on the search query
|
||||
filtered_items = []
|
||||
for item in items:
|
||||
if query.lower() in item['title'].lower() or query.lower() in item['description'].lower():
|
||||
filtered_items.append(item)
|
||||
|
||||
return render_template('index.html', items=filtered_items, run_time=run_time) # Pass the run time to the template
|
||||
|
||||
@app.route('/report', methods=['POST'])
|
||||
def report():
|
||||
title = request.form.get('title')
|
||||
link = request.form.get('link')
|
||||
|
||||
reported_items = list(parse_xml('./reported.xml'))
|
||||
reported_items[0].append({'title': title, 'link': link}) # Append the item to the first element of the tuple
|
||||
write_to_xml('./reported.xml', reported_items[0]) # Pass the first element of the tuple to write_to_xml
|
||||
|
||||
response = {
|
||||
'message': 'Item reported successfully! Our team will review this listing to see if it is in line with our Statement of Faith. Appropriate filtering will be applied.'
|
||||
}
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
108
app_backup.py
Normal file
108
app_backup.py
Normal file
@ -0,0 +1,108 @@
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
import xml.etree.ElementTree as ET
|
||||
import logging
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
# Parse the XML file and extract data
|
||||
def parse_xml(file_path):
|
||||
try:
|
||||
tree = ET.parse(file_path)
|
||||
root = tree.getroot()
|
||||
|
||||
items = []
|
||||
run_time_element = root.find("run_time")
|
||||
|
||||
if run_time_element is not None:
|
||||
run_time = run_time_element.text
|
||||
else:
|
||||
run_time = 'N/A'
|
||||
|
||||
for item in root.findall('.//item'):
|
||||
title_element = item.attrib.get('title')
|
||||
title = title_element if title_element is not None else 'N/A'
|
||||
|
||||
link_element = item.attrib.get('link')
|
||||
link = link_element if link_element is not None else '#'
|
||||
|
||||
description_element = item.attrib.get('description')
|
||||
description = description_element if description_element is not None else 'N/A'
|
||||
|
||||
source_element = item.attrib.get('source')
|
||||
source = source_element if source_element is not None else 'N/A'
|
||||
|
||||
guid_element = item.attrib.get('guid')
|
||||
guid = guid_element if guid_element is not None else 'N/A'
|
||||
|
||||
pub_date_element = item.attrib.get('pubDate')
|
||||
pub_date = pub_date_element if pub_date_element is not None else 'N/A'
|
||||
|
||||
georss_point_element = item.attrib.get('{http://www.georss.org/georss}point')
|
||||
georss_point = georss_point_element if georss_point_element is not None else 'N/A'
|
||||
|
||||
items.append({
|
||||
'title': title,
|
||||
'link': link,
|
||||
'description': description,
|
||||
'source': source,
|
||||
'guid': guid,
|
||||
'pub_date': pub_date,
|
||||
'georss_point': georss_point
|
||||
})
|
||||
|
||||
return items, run_time # Return the extracted items and run time value
|
||||
|
||||
except FileNotFoundError:
|
||||
app.logger.error('XML file not found.')
|
||||
|
||||
return [], ''
|
||||
|
||||
def write_to_xml(file_path, items):
|
||||
root = ET.Element("root")
|
||||
|
||||
for item in items:
|
||||
new_element = ET.SubElement(root, "item")
|
||||
new_element.set("title", item['title'])
|
||||
new_element.set("link", item['link'])
|
||||
|
||||
tree = ET.ElementTree(root)
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
items, run_time = parse_xml('./indeed_output.xml') # Retrieve the items and run time
|
||||
return render_template('index.html', items=items, run_time=run_time) # Pass the run time to the template
|
||||
|
||||
@app.route('/search')
|
||||
def search():
|
||||
query = request.args.get('query')
|
||||
items, run_time = parse_xml('./indeed_output.xml') # Retrieve the items and run time
|
||||
|
||||
# Filter the items based on the search query
|
||||
filtered_items = []
|
||||
for item in items:
|
||||
if query.lower() in item['title'].lower() or query.lower() in item['description'].lower():
|
||||
filtered_items.append(item)
|
||||
|
||||
return render_template('index.html', items=filtered_items, run_time=run_time) # Pass the run time to the template
|
||||
|
||||
@app.route('/report', methods=['POST'])
|
||||
def report():
|
||||
title = request.form.get('title')
|
||||
link = request.form.get('link')
|
||||
|
||||
reported_items = parse_xml('./reported.xml')
|
||||
reported_items.append({'title': title, 'link': link})
|
||||
write_to_xml('./reported.xml', reported_items)
|
||||
|
||||
response = {
|
||||
'message': 'Item reported successfully! Our team will review this listing to see if it is in line with our Statement of Faith. Appropriate filtering will be applied.'
|
||||
}
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
3
gunicorn_config.py
Normal file
3
gunicorn_config.py
Normal file
@ -0,0 +1,3 @@
|
||||
workers = 4
|
||||
bind = '0.0.0.0:8000'
|
||||
module = 'app:app'
|
1
indeed_input.xml
Normal file
1
indeed_input.xml
Normal file
File diff suppressed because one or more lines are too long
2
indeed_output.xml
Normal file
2
indeed_output.xml
Normal file
File diff suppressed because one or more lines are too long
2
reported.xml
Normal file
2
reported.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<root></root>
|
BIN
static/heartily1.gif
Normal file
BIN
static/heartily1.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
229
static/style.css
Normal file
229
static/style.css
Normal file
@ -0,0 +1,229 @@
|
||||
body {
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #a9dc76;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #78dce8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #e6a96b;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.logo {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
width: 50px;
|
||||
max-width: 100%;
|
||||
margin-right: 10px;
|
||||
|
||||
}
|
||||
|
||||
.logo p {
|
||||
font-size: 24px;
|
||||
color: #f8f8f2;
|
||||
margin: 10px;
|
||||
}
|
||||
.logo-name {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #f8f8f2;
|
||||
margin: 10px;
|
||||
}
|
||||
.logo-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none; /* Add this line to remove underline */
|
||||
}
|
||||
.search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
background-color: #F9F5DD;
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
background-color: #fd971f;
|
||||
color: #f8f8f2;
|
||||
cursor: pointer;
|
||||
margin:10px;
|
||||
}
|
||||
|
||||
.search-button:hover {
|
||||
background-color: #f4bf75;
|
||||
}
|
||||
|
||||
|
||||
.notification-overlay {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #ff5795c7;
|
||||
color: #f8f8f2;
|
||||
border: 1px solid #272822;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
animation: fadeIn 0.5s, fadeOut 0.5s 5s forwards;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.report-button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
color: #FF5794;
|
||||
border:none;
|
||||
background-color: transparent;
|
||||
margin: 5px;
|
||||
|
||||
}
|
||||
.wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: #f8f8f2;
|
||||
background-color: #fd971f;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.wrapper .button-text {
|
||||
display: inline-block;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.wrapper .button-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #ffffff;
|
||||
font-size: 24px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.wrapper:hover {
|
||||
background-color: #f4bf75;
|
||||
}
|
||||
.wrapper:hover .button-text {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.wrapper:hover .button-icon {
|
||||
opacity: 1;
|
||||
font-size: 20px;
|
||||
}
|
||||
.report-button .button-text {
|
||||
display: inline-block;
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.report-button .button-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 24px;
|
||||
transition: opacity 0.3s ease;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.report-button:hover .button-text {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.report-button:hover .button-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
125
templates/index.html
Normal file
125
templates/index.html
Normal file
@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Heartily</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', filename='style.css') }}"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="logo">
|
||||
<a href="/" class="logo-link">
|
||||
<img
|
||||
src="{{ url_for('static', filename='heartily1.gif') }}"
|
||||
alt="Heartily Logo"
|
||||
class="logo-img"
|
||||
/>
|
||||
<p class="logo-name">Heartily</p>
|
||||
<p>Col. 3:23</p>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<form action="{{ url_for('search') }}" method="GET" class="search-form">
|
||||
<input
|
||||
type="text"
|
||||
name="query"
|
||||
placeholder="Search..."
|
||||
class="search-input"
|
||||
/>
|
||||
<button type="submit" class="search-button">Search</button>
|
||||
<a href="/" class="wrapper">
|
||||
<span class="button-text">Refresh</span>
|
||||
<span class="button-icon"><i class="far fa-heart"></i></span>
|
||||
</a>
|
||||
</form>
|
||||
<h1>Latest Jobs:</h1>
|
||||
<p>This list is updated hourly. Last update: {{ run_time }}</p>
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>
|
||||
<h2>{{ item.title }}</h2>
|
||||
<p>Source: {{ item.source }}</p>
|
||||
<p>Post Date: {{ item.pub_date }}</p>
|
||||
<div class = "list-container">
|
||||
<a href="{{ item.link }}" target="_blank" class="wrapper">
|
||||
<span class="button-text">Apply Now</span>
|
||||
<span class="button-icon"><i class="far fa-heart"></i></span>
|
||||
</a>
|
||||
<form
|
||||
action="{{ url_for('report') }}"
|
||||
method="POST"
|
||||
class="report-form"
|
||||
>
|
||||
<input type="hidden" name="title" value="{{ item.title }}" />
|
||||
<input type="hidden" name="link" value="{{ item.link }}" />
|
||||
<button type="submit" class="report-button">
|
||||
<span class="button-text">Report</span>
|
||||
<span class="button-icon">
|
||||
<i class="fas fa-heart-circle-exclamation"></i>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
© 2023 Heartily. All rights reserved. Version:
|
||||
<a href="https://githaven.org/Shiloh/heartily" target="_blank">Beta</a>.
|
||||
Powered by
|
||||
<a href="https://shilohcode.com" target="_blank">Shiloh</a>.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<!-- Notification overlay -->
|
||||
<div id="notification-overlay" class="notification-overlay">
|
||||
<span id="notification-message"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// JavaScript code for displaying the notification overlay
|
||||
function showNotification(message) {
|
||||
var overlay = document.getElementById("notification-overlay");
|
||||
var notification = document.getElementById("notification-message");
|
||||
notification.textContent = message;
|
||||
overlay.style.display = "block";
|
||||
setTimeout(function () {
|
||||
overlay.style.display = "none";
|
||||
}, 10000); // Display the notification for 10 seconds
|
||||
}
|
||||
|
||||
// JavaScript code for handling the AJAX request and displaying the notification
|
||||
var reportForms = document.getElementsByClassName("report-form");
|
||||
Array.prototype.forEach.call(reportForms, function (form) {
|
||||
form.addEventListener("submit", function (event) {
|
||||
event.preventDefault(); // Prevent the form from submitting
|
||||
|
||||
// Create a new FormData object to send the form data
|
||||
var formData = new FormData(form);
|
||||
|
||||
// Send the AJAX request
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/report");
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
showNotification(response.message); // Display the notification
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
73
xmldownloader.py
Normal file
73
xmldownloader.py
Normal file
@ -0,0 +1,73 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
import urllib.request
|
||||
import random
|
||||
import time
|
||||
from fake_useragent import UserAgent
|
||||
import requests
|
||||
import datetime
|
||||
|
||||
# Get the current date and time
|
||||
current_datetime = datetime.datetime.now()
|
||||
|
||||
# Format the date and time as a string
|
||||
run_time = current_datetime.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Print the run time
|
||||
print("Script run time:", run_time)
|
||||
|
||||
# Define the URL of the XML file to download
|
||||
url = "https://rss.indeed.com/rss?q=(it+OR+technology+OR+developer+OR+software)+AND+(bible+OR+christian+OR+jesus+OR+god)+-LDS+-%22Latter-Day+Saints%22.xml"
|
||||
|
||||
# Set a custom user agent
|
||||
ua = UserAgent()
|
||||
headers = {
|
||||
'User-Agent': ua.random,
|
||||
'Referer': 'https://google.com',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
}
|
||||
|
||||
# Create a session object to handle cookies
|
||||
session = requests.Session()
|
||||
session.headers.update(headers)
|
||||
|
||||
# Download the XML file
|
||||
response = session.get(url)
|
||||
filename = "/home/gordon/Documents/Code/heartily/indeed_input.xml"
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(response.content)
|
||||
|
||||
# Introduce a random delay between 2 and 5 seconds
|
||||
delay = random.uniform(2, 5)
|
||||
time.sleep(delay)
|
||||
|
||||
# Read the downloaded XML file
|
||||
tree = ET.parse(filename)
|
||||
root = tree.getroot()
|
||||
|
||||
# Define the desired attribute names
|
||||
attribute_names = ["title", "link", "source", "guid", "pubDate", "description", "{http://www.georss.org/georss}point"]
|
||||
|
||||
# Create a new XML root element to store the extracted values
|
||||
new_root = ET.Element("root")
|
||||
|
||||
# Iterate over the "item" elements and extract the desired values
|
||||
for item in root.findall(".//item"):
|
||||
new_element = ET.SubElement(new_root, "item")
|
||||
|
||||
# Extract the desired attributes from the "item" element
|
||||
for attribute_name in attribute_names:
|
||||
value = item.find(attribute_name).text
|
||||
if value is not None:
|
||||
new_element.set(attribute_name, value)
|
||||
|
||||
# Add a new element for the run time
|
||||
run_time_element = ET.SubElement(new_root, "run_time")
|
||||
run_time_element.text = run_time
|
||||
|
||||
# Create an ElementTree object and write it to a new XML file
|
||||
new_tree = ET.ElementTree(new_root)
|
||||
output_filename = "/home/gordon/Documents/Code/heartily/indeed_output.xml"
|
||||
new_tree.write(output_filename, encoding="utf-8", xml_declaration=True)
|
Reference in New Issue
Block a user