Spaces:
Sleeping
Sleeping
| import panel as pn | |
| import lz4.block | |
| import xml.sax.saxutils | |
| import html | |
| import io | |
| import xml.dom.minidom | |
| import blackboxprotobuf | |
| # from wechatlog.adaptors.pywxdumpAdapter import decode_bytes_extra | |
| # Initialize Panel with Bootstrap design | |
| pn.extension(design="bootstrap", sizing_mode="stretch_width") | |
| # Social media links and icons | |
| ICON_URLS = { | |
| "brand-github": "https://github.com/yourusername/compressed-content-viewer", | |
| "message-circle": "https://discourse.holoviz.org/", | |
| } | |
| def decode_bytes_extra(data): | |
| """ | |
| Decode BytesExtra data using pywxdump's decode_bytes_extra function. | |
| """ | |
| def get_BytesExtra(BytesExtra): | |
| BytesExtra_message_type = { | |
| "1": { | |
| "type": "message", | |
| "message_typedef": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "int", | |
| "name": "" | |
| } | |
| }, | |
| "name": "1" | |
| }, | |
| "3": { | |
| "type": "message", | |
| "message_typedef": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "str", | |
| "name": "" | |
| } | |
| }, | |
| "name": "3", | |
| "alt_typedefs": { | |
| "1": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": {}, | |
| "name": "" | |
| } | |
| }, | |
| "2": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "13": { | |
| "type": "fixed32", | |
| "name": "" | |
| }, | |
| "12": { | |
| "type": "fixed32", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "3": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "15": { | |
| "type": "fixed64", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "4": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "15": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "14": { | |
| "type": "fixed32", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "5": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "12": { | |
| "type": "fixed32", | |
| "name": "" | |
| }, | |
| "7": { | |
| "type": "fixed64", | |
| "name": "" | |
| }, | |
| "6": { | |
| "type": "fixed64", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "6": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "7": { | |
| "type": "fixed64", | |
| "name": "" | |
| }, | |
| "6": { | |
| "type": "fixed32", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "7": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "12": { | |
| "type": "fixed64", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "8": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "6": { | |
| "type": "fixed64", | |
| "name": "" | |
| }, | |
| "12": { | |
| "type": "fixed32", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "9": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "15": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "12": { | |
| "type": "fixed64", | |
| "name": "" | |
| }, | |
| "6": { | |
| "type": "int", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| "10": { | |
| "1": { | |
| "type": "int", | |
| "name": "" | |
| }, | |
| "2": { | |
| "type": "message", | |
| "message_typedef": { | |
| "6": { | |
| "type": "fixed32", | |
| "name": "" | |
| }, | |
| "12": { | |
| "type": "fixed64", | |
| "name": "" | |
| } | |
| }, | |
| "name": "" | |
| } | |
| }, | |
| } | |
| } | |
| } | |
| if BytesExtra is None or not isinstance(BytesExtra, bytes): | |
| return None | |
| try: | |
| deserialize_data, message_type = blackboxprotobuf.decode_message(BytesExtra, BytesExtra_message_type) | |
| return deserialize_data | |
| except Exception as e: | |
| return None | |
| return get_BytesExtra(data) | |
| def process_content(data): | |
| """ | |
| Process both CompressContent and BytesExtra content. | |
| First tries to decompress (for CompressContent), if fails treats as BytesExtra. | |
| :param data: bytes data to process | |
| :return: tuple (decoded_string, content_type, error_message) | |
| """ | |
| if data is None or not isinstance(data, bytes): | |
| return "", "unknown", "Invalid input: data must be bytes" | |
| # First try to decompress (assuming it's CompressContent) | |
| try: | |
| dst = lz4.block.decompress(data, uncompressed_size=len(data) << 10) | |
| decoded_string = dst.decode().replace("\x00", "") # Remove any null characters | |
| return decoded_string, "CompressContent", None | |
| except Exception: # If decompression fails, try BytesExtra | |
| try: | |
| # Try to decode as BytesExtra | |
| decoded_dict = decode_bytes_extra(data) | |
| if isinstance(decoded_dict, dict): | |
| # Convert dictionary to a formatted string representation | |
| decoded_string = "{\n" | |
| for key, value in decoded_dict.items(): | |
| decoded_string += f" {key}: {value}\n" | |
| decoded_string += "}" | |
| return decoded_string, "BytesExtra", None | |
| else: | |
| return "", "unknown", "BytesExtra decoding did not return a dictionary" | |
| except Exception as decode_error: | |
| return "", "unknown", f"Failed to process content: {str(decode_error)}" | |
| def unescape_xml(text): | |
| # First handle standard XML entities | |
| text = xml.sax.saxutils.unescape(text) | |
| # Then handle HTML entities that might be present | |
| text = html.unescape(text) | |
| return text | |
| def format_xml(xml_string): | |
| try: | |
| # Parse the XML string | |
| dom = xml.dom.minidom.parseString(xml_string) | |
| # Get the pretty-printed XML | |
| return dom.toprettyxml(indent=" ") | |
| except Exception as e: | |
| # If parsing fails, return the original string with the error | |
| return f"XML formatting error: {str(e)}\n\nOriginal XML:\n{xml_string}" | |
| # Create the UI with modern styling | |
| file_input = pn.widgets.FileInput( | |
| accept='.bin', | |
| height=50, | |
| sizing_mode="stretch_width" | |
| ) | |
| content_type_indicator = pn.widgets.StaticText( | |
| value="", | |
| align="center" | |
| ) | |
| status = pn.widgets.StaticText( | |
| value="##### π Upload a .bin file to view its content", | |
| align="center" | |
| ) | |
| # Use a TextAreaInput with modern styling | |
| result_area = pn.widgets.TextAreaInput( | |
| value="", | |
| height=400, | |
| sizing_mode="stretch_width", | |
| disabled=True, | |
| stylesheets=['body { font-family: monospace; }'] | |
| ) | |
| # Styled checkbox | |
| unescape_checkbox = pn.widgets.Checkbox( | |
| name="Unescape XML entities", | |
| value=True, | |
| align="center", | |
| margin=(10, 10) | |
| ) | |
| def get_xml_content(): | |
| if processed_xml: | |
| return io.StringIO(processed_xml) | |
| return None | |
| # Modern styled download button | |
| download_button = pn.widgets.FileDownload( | |
| callback=get_xml_content, | |
| filename="content.xml", | |
| button_type="primary", | |
| label="π₯ Download XML", | |
| disabled=True, | |
| align="center", | |
| height=35 | |
| ) | |
| # Global variable to store the processed XML | |
| processed_xml = None | |
| def process_file(event): | |
| global processed_xml | |
| if event.new is not None: | |
| status.value = "##### βοΈ Processing file..." | |
| content_type_indicator.value = "" | |
| try: | |
| # Process the content (either CompressContent or BytesExtra) | |
| content, content_type, error = process_content(event.new) | |
| if error: | |
| status.value = f"##### β {error}" | |
| result_area.value = "" | |
| content_type_indicator.value = "" | |
| download_button.disabled = True | |
| processed_xml = None | |
| return | |
| # Update content type indicator with icon | |
| icon = "ποΈ" if content_type == "CompressContent" else "π" | |
| content_type_indicator.value = f"##### {icon} Detected type: {content_type}" | |
| if content: | |
| if content_type == "CompressContent": | |
| try: | |
| # Unescape XML entities if checkbox is checked | |
| if unescape_checkbox.value: | |
| content = unescape_xml(content) | |
| # Format XML with proper indentation | |
| pretty_content = format_xml(content) | |
| result_area.value = pretty_content | |
| processed_xml = pretty_content | |
| download_button.filename = f"{file_input.filename.rsplit('.', 1)[0]}_xml.xml" | |
| except Exception as xml_error: | |
| result_area.value = content | |
| processed_xml = content | |
| status.value = f"##### β οΈ Content extracted, but XML formatting failed: {str(xml_error)}" | |
| else: # BytesExtra | |
| result_area.value = content | |
| processed_xml = content | |
| download_button.filename = f"{file_input.filename.rsplit('.', 1)[0]}_dict.txt" | |
| status.value = "##### β Content processed successfully!" | |
| download_button.disabled = False | |
| else: | |
| status.value = "##### β Processing failed" | |
| result_area.value = "" | |
| download_button.disabled = True | |
| processed_xml = None | |
| except Exception as e: | |
| status.value = f"##### β Error: {str(e)}" | |
| result_area.value = "" | |
| content_type_indicator.value = "" | |
| download_button.disabled = True | |
| processed_xml = None | |
| file_input.param.watch(process_file, 'value') | |
| def toggle_unescape(event): | |
| if file_input.value is not None: | |
| process_file(pn.param.ParamEvent(obj=file_input, name='value', old=None, new=file_input.value)) | |
| unescape_checkbox.param.watch(toggle_unescape, 'value') | |
| # Add footer with social links | |
| footer_row = pn.Row(pn.Spacer(), align="center") | |
| for icon, url in ICON_URLS.items(): | |
| href_button = pn.widgets.Button(icon=icon, width=35, height=35) | |
| href_button.js_on_click(code=f"window.open('{url}')") | |
| footer_row.append(href_button) | |
| footer_row.append(pn.Spacer()) | |
| # Create main content | |
| main = pn.WidgetBox( | |
| pn.Column( | |
| pn.pane.Markdown("##### π Drop your .bin file here or click to upload"), | |
| file_input, | |
| content_type_indicator, | |
| pn.Row( | |
| unescape_checkbox, | |
| download_button, | |
| align="center" | |
| ), | |
| status, | |
| pn.pane.Markdown("##### π Content Preview"), | |
| result_area, | |
| footer_row, | |
| sizing_mode="stretch_width" | |
| ) | |
| ) | |
| # Create the template | |
| title = "Content Viewer" | |
| template = pn.template.BootstrapTemplate( | |
| title=title, | |
| main=main, | |
| main_max_width="min(80%, 1000px)", | |
| header_background="#F08080", | |
| ) | |
| template.servable(title=title) | |