Skip to content

Instantly share code, notes, and snippets.

@guinslym
Created November 3, 2025 19:16
Show Gist options
  • Select an option

  • Save guinslym/ab9b88f210d85bbf4580d9139c069c80 to your computer and use it in GitHub Desktop.

Select an option

Save guinslym/ab9b88f210d85bbf4580d9139c069c80 to your computer and use it in GitHub Desktop.
import lh3.api
from datetime import datetime
import sys
#need to install openpyxl
try:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
except ImportError:
print("Error: openpyxl is required. Installing it now...")
import subprocess
subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl", "--break-system-packages"])
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
def format_header(ws, row, columns):
"""Apply formatting to header row."""
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_font = Font(bold=True, color="FFFFFF", size=11)
border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
for col_num, column_title in enumerate(columns, 1):
cell = ws.cell(row=row, column=col_num)
cell.value = column_title
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center', vertical='center')
cell.border = border
def auto_adjust_columns(ws):
"""Auto-adjust column widths based on content."""
for column in ws.columns:
max_length = 0
column_letter = get_column_letter(column[0].column)
for cell in column:
try:
if cell.value:
max_length = max(max_length, len(str(cell.value)))
except:
pass
adjusted_width = min(max_length + 2, 100) # Max width of 100
ws.column_dimensions[column_letter].width = adjusted_width
def apply_data_formatting(ws, start_row):
"""Apply formatting to data rows."""
border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
for row in ws.iter_rows(min_row=start_row, max_row=ws.max_row):
for cell in row:
cell.border = border
cell.alignment = Alignment(vertical='top', wrap_text=True)
def create_faq_summary_sheet(wb, faqs_data):
"""Create summary sheet with FAQ overview."""
ws = wb.active
ws.title = "FAQ Summary"
# Header
ws['A1'] = "LibraryH3lp FAQ Report"
ws['A1'].font = Font(bold=True, size=14)
ws['A2'] = f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
ws['A2'].font = Font(italic=True, size=10)
# Column headers
headers = ['FAQ ID', 'FAQ Name', 'Public', 'Queue', 'Theme',
'Total Questions', 'Total Views', 'Total Likes',
'Total Dislikes', 'Total Searches']
format_header(ws, 4, headers)
# Data rows
row_num = 5
for faq in faqs_data:
stats = faq.get('stats', {})
ws.cell(row=row_num, column=1, value=faq.get('id'))
ws.cell(row=row_num, column=2, value=faq.get('name'))
ws.cell(row=row_num, column=3, value='Yes' if faq.get('public') else 'No')
ws.cell(row=row_num, column=4, value=faq.get('queue') or 'N/A')
ws.cell(row=row_num, column=5, value=faq.get('theme') or 'N/A')
ws.cell(row=row_num, column=6, value=stats.get('num_questions', 0))
ws.cell(row=row_num, column=7, value=stats.get('num_views', 0))
ws.cell(row=row_num, column=8, value=stats.get('num_likes', 0))
ws.cell(row=row_num, column=9, value=stats.get('num_dislikes', 0))
ws.cell(row=row_num, column=10, value=stats.get('num_searches', 0))
row_num += 1
# Apply formatting
apply_data_formatting(ws, 5)
auto_adjust_columns(ws)
# Add totals row
total_row = row_num + 1
ws.cell(row=total_row, column=1, value="TOTALS").font = Font(bold=True)
ws.cell(row=total_row, column=6, value=f"=SUM(F5:F{row_num-1})").font = Font(bold=True)
ws.cell(row=total_row, column=7, value=f"=SUM(G5:G{row_num-1})").font = Font(bold=True)
ws.cell(row=total_row, column=8, value=f"=SUM(H5:H{row_num-1})").font = Font(bold=True)
ws.cell(row=total_row, column=9, value=f"=SUM(I5:I{row_num-1})").font = Font(bold=True)
ws.cell(row=total_row, column=10, value=f"=SUM(J5:J{row_num-1})").font = Font(bold=True)
def create_all_questions_sheet(wb, faqs_data):
"""Create sheet with all questions from all FAQs."""
ws = wb.create_sheet(title="All Questions")
# Column headers
headers = ['FAQ ID', 'FAQ Name', 'Question ID', 'Question Text',
'Published', 'Views', 'Likes', 'Dislikes',
'Like Ratio (%)', 'Last Updated', 'Topics']
format_header(ws, 1, headers)
# Data rows
row_num = 2
for faq in faqs_data:
faq_id = faq.get('id')
faq_name = faq.get('name')
questions = faq.get('questions', [])
if not questions:
# Add FAQ row even if no questions
ws.cell(row=row_num, column=1, value=faq_id)
ws.cell(row=row_num, column=2, value=faq_name)
ws.cell(row=row_num, column=3, value='')
ws.cell(row=row_num, column=4, value='(No questions)')
row_num += 1
else:
for question in questions:
ws.cell(row=row_num, column=1, value=faq_id)
ws.cell(row=row_num, column=2, value=faq_name)
ws.cell(row=row_num, column=3, value=question.get('id'))
ws.cell(row=row_num, column=4, value=question.get('question', ''))
ws.cell(row=row_num, column=5, value='Yes' if question.get('published') else 'No')
ws.cell(row=row_num, column=6, value=question.get('views', 0))
ws.cell(row=row_num, column=7, value=question.get('likes', 0))
ws.cell(row=row_num, column=8, value=question.get('dislikes', 0))
# Calculate like ratio
likes = question.get('likes', 0)
dislikes = question.get('dislikes', 0)
if likes + dislikes > 0:
like_ratio = (likes / (likes + dislikes)) * 100
ws.cell(row=row_num, column=9, value=round(like_ratio, 1))
else:
ws.cell(row=row_num, column=9, value='N/A')
ws.cell(row=row_num, column=10, value=question.get('updatedAt', ''))
# Topics
topics = question.get('topics', [])
ws.cell(row=row_num, column=11, value=', '.join(topics) if topics else '')
row_num += 1
# Apply formatting
apply_data_formatting(ws, 2)
auto_adjust_columns(ws)
def create_individual_faq_sheets(wb, faqs_data):
"""Create individual sheets for each FAQ."""
for faq in faqs_data:
faq_name = faq.get('name', 'Unknown')
faq_id = faq.get('id')
# Clean sheet name (Excel has restrictions)
sheet_name = f"{faq_id}_{faq_name}"[:31] # Max 31 chars
# Remove invalid characters
for char in ['\\', '/', '*', '?', ':', '[', ']']:
sheet_name = sheet_name.replace(char, '_')
ws = wb.create_sheet(title=sheet_name)
# FAQ Info
ws['A1'] = "FAQ Information"
ws['A1'].font = Font(bold=True, size=12)
ws['A2'] = "FAQ ID:"
ws['B2'] = faq_id
ws['A3'] = "FAQ Name:"
ws['B3'] = faq_name
ws['A4'] = "Public:"
ws['B4'] = 'Yes' if faq.get('public') else 'No'
ws['A5'] = "Queue:"
ws['B5'] = faq.get('queue') or 'N/A'
ws['A6'] = "Theme:"
ws['B6'] = faq.get('theme') or 'N/A'
# Statistics
stats = faq.get('stats', {})
ws['A8'] = "Statistics"
ws['A8'].font = Font(bold=True, size=12)
ws['A9'] = "Total Questions:"
ws['B9'] = stats.get('num_questions', 0)
ws['A10'] = "Total Views:"
ws['B10'] = stats.get('num_views', 0)
ws['A11'] = "Total Likes:"
ws['B11'] = stats.get('num_likes', 0)
ws['A12'] = "Total Dislikes:"
ws['B12'] = stats.get('num_dislikes', 0)
ws['A13'] = "Total Searches:"
ws['B13'] = stats.get('num_searches', 0)
# Questions
questions = faq.get('questions', [])
if questions:
ws['A15'] = "Questions"
ws['A15'].font = Font(bold=True, size=12)
headers = ['ID', 'Question', 'Published', 'Views', 'Likes', 'Dislikes', 'Updated', 'Topics']
format_header(ws, 16, headers)
row_num = 17
for question in questions:
ws.cell(row=row_num, column=1, value=question.get('id'))
ws.cell(row=row_num, column=2, value=question.get('question', ''))
ws.cell(row=row_num, column=3, value='Yes' if question.get('published') else 'No')
ws.cell(row=row_num, column=4, value=question.get('views', 0))
ws.cell(row=row_num, column=5, value=question.get('likes', 0))
ws.cell(row=row_num, column=6, value=question.get('dislikes', 0))
ws.cell(row=row_num, column=7, value=question.get('updatedAt', ''))
topics = question.get('topics', [])
ws.cell(row=row_num, column=8, value=', '.join(topics) if topics else '')
row_num += 1
apply_data_formatting(ws, 17)
else:
ws['A15'] = "No questions for this FAQ"
auto_adjust_columns(ws)
def main():
"""Main function to generate Excel report."""
print("Connecting to LibraryH3lp...")
try:
client = lh3.api.Client()
except Exception as e:
print(f"Error: Could not connect to LibraryH3lp: {e}")
return
print("Retrieving FAQ data...")
try:
all_faqs = client.all('faqs').get_list()
if not all_faqs:
print("No FAQs found.")
return
print(f"Found {len(all_faqs)} FAQs. Processing...")
faqs_data = []
for idx, faq_summary in enumerate(all_faqs, 1):
faq_id = faq_summary.get('id')
faq_name = faq_summary.get('name', 'Unknown')
print(f" Processing FAQ {idx}/{len(all_faqs)}: {faq_name} (ID: {faq_id})")
try:
faq = client.one('faqs', faq_id)
faq_data = faq.get()
if not faq_data or isinstance(faq_data, str):
continue
# Get questions
questions = []
try:
questions_list = faq.all('questions').get_list()
if questions_list and isinstance(questions_list, list):
questions = questions_list
except Exception:
pass
faq_info = {
'id': faq_data.get('id'),
'name': faq_data.get('name'),
'public': faq_data.get('public'),
'queue': faq_data.get('queue'),
'theme': faq_data.get('theme'),
'stats': faq_data.get('stats_overview', {}),
'questions': questions
}
faqs_data.append(faq_info)
except Exception as e:
print(f" Warning: Error processing FAQ {faq_id}: {e}")
continue
print(f"\nCreating Excel workbook...")
# Create workbook
wb = Workbook()
# Create sheets
print(" Creating FAQ Summary sheet...")
create_faq_summary_sheet(wb, faqs_data)
print(" Creating All Questions sheet...")
create_all_questions_sheet(wb, faqs_data)
print(" Creating individual FAQ sheets...")
create_individual_faq_sheets(wb, faqs_data)
# Save file
filename = f"libraryh3lp_faqs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
wb.save(filename)
print(f"\n✓ Excel file created successfully: {filename}")
print(f" Total FAQs: {len(faqs_data)}")
total_questions = sum(len(faq['questions']) for faq in faqs_data)
print(f" Total Questions: {total_questions}")
print(f"\nThe file contains:")
print(f" • FAQ Summary sheet - Overview of all FAQs")
print(f" • All Questions sheet - All questions from all FAQs")
print(f" • Individual sheets for each FAQ with details")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment