Added resource description

This commit is contained in:
Stijn Bannink 2025-02-12 19:31:52 +01:00
parent 1b4d94a8de
commit d7298e0c02
Signed by: SBDeveloper
GPG key ID: B730712F2C3A9D7A
8 changed files with 470 additions and 1 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@
*.ipr
.internal/Tools/*
.internal/Sources/config.ini
*.bbcode

View file

@ -0,0 +1,26 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Convert Description for SpigotMC" type="PythonConfigurationType" factoryName="Python">
<module name="VehiclesPlus" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="Python 3.10" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Description" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Description/html_to_bbcode.py" />
<option name="PARAMETERS" value="vp.html" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,357 @@
import os
import re
import sys
from bs4 import BeautifulSoup, NavigableString, Tag, Comment
class HTMLToBBCode:
def __init__(self):
self.conversion_map = {
'b': ['[b]', '[/b]'],
'i': ['[i]', '[/i]'],
'u': ['[u]', '[/u]'],
'strong': ['[b]', '[/b]'],
'em': ['[i]', '[/i]'],
'strike': ['[s]', '[/s]'],
'h1': ['[size=7]', '[/size]'],
'h2': ['[size=6]', '[/size]'],
'h3': ['[size=5]', '[/size]'],
'h4': ['[size=4]', '[/size]'],
'h5': ['[size=3]', '[/size]'],
'h6': ['[size=2]', '[/size]'],
'center': ['[center]', '[/center]'],
'div': ['', ''], # Handle div attributes separately
}
# URL replacements (from -> to)
self.url_replacements = {
'https://polymart.org/resource/vehiclesplus-1-12-1-20-2.633?purchase=1':
'https://www.spigotmc.org/resources/vehiclesplus-1-12-1-20-2.70523/purchase',
'https://polymart.org/resource?spigot_id=1997':
'https://www.spigotmc.org/resources/protocollib.1997/',
'https://polymart.org/resource?spigot_id=34315':
'https://www.spigotmc.org/resources/vault.34315/',
'https://polymart.org/resource?spigot_id=9089':
'https://www.spigotmc.org/resources/essentialsx.9089/',
'https://sbdevelopment.tech/images/buttons/buy2.png':
'https://sbdevelopment.tech/images/buttons/buy.png',
'https://sbdevelopment.tech/images/buttons/wiki2.png':
'https://sbdevelopment.tech/images/buttons/wiki.png',
'https://sbdevelopment.tech/images/buttons/discord2.png':
'https://sbdevelopment.tech/images/buttons/discord.png',
'https://sbdevelopment.tech/images/buttons/website2.png':
'https://sbdevelopment.tech/images/buttons/website.png'
}
def _convert_font_size(self, size, unit):
"""Convert font size to BBCode size scale 1-7"""
if unit == 'em':
# Convert em to scale 1-7
# em values: 1=normal, 2=large, 3=larger
size = float(size)
if size <= 0.8:
return 1
elif size <= 1:
return 2
elif size <= 1.5:
return 3
elif size <= 2:
return 4
elif size <= 2.5:
return 5
elif size <= 3:
return 6
else:
return 7
elif unit == 'pt':
# Convert pt to scale 1-7
size = int(size)
if size <= 8:
return 1
elif size <= 10:
return 2
elif size <= 12:
return 3
elif size <= 14:
return 4
elif size <= 16:
return 5
elif size <= 20:
return 6
else:
return 7
return 3 # Default to normal size
@staticmethod
def _convert_youtube_url(url):
# Extract video ID from YouTube URL
if 'youtube.com' in url:
video_id = url.split('v=')[-1].split('&')[0]
return f'[MEDIA=youtube]{video_id}[/MEDIA]'
elif 'youtu.be' in url:
video_id = url.split('/')[-1].split('?')[0]
return f'[MEDIA=youtube]{video_id}[/MEDIA]'
return None
def convert(self, html_content):
# Create BeautifulSoup object
soup = BeautifulSoup(html_content, 'html.parser')
# Remove script, style elements and comments
for element in soup(['script', 'style']):
element.decompose()
# Remove HTML comments
for comment in soup.find_all(string=lambda text: isinstance(text, Comment)):
comment.extract()
# Convert the HTML to BBCode
bbcode = self._process_tag(soup)
# Fix [CENTER] tag formatting - ensure single newline before [CENTER] and after content
bbcode = re.sub(r'\n+\[CENTER\]', '\n[CENTER]', bbcode)
bbcode = re.sub(r'([^\n])\[CENTER\]', r'\1\n[CENTER]', bbcode)
# Ensure [/CENTER] is on its own line
bbcode = re.sub(r'\[/CENTER\]([^\n])', r'[/CENTER]\n\1', bbcode)
# Clean up multiple consecutive newlines, but preserve single newlines
bbcode = re.sub(r'\n{3,}', '\n\n', bbcode)
# Remove any trailing whitespace
return bbcode.strip()
def _process_tag(self, element):
if isinstance(element, NavigableString):
# Clean up text content but preserve trailing space
text = element.string if element.string else ''
# Keep trailing space if it exists
has_trailing_space = text.endswith(' ')
# Normalize internal spaces
text = ' '.join(text.split())
# Restore trailing space if it existed
if has_trailing_space:
text += ' '
return text
# Skip elements with hidden-bbcode class
if isinstance(element, Tag) and 'hidden-bbcode' in element.get('class', []):
return ''
# Handle br tags immediately
if element.name == 'br':
return '\n'
# Handle p tags
if element.name == 'p':
content = self._get_inner_content(element)
style = element.get('style', '')
if style:
tags = []
# Handle font size
size_match = re.search(r'font-size:\s*(\d+(?:\.\d+)?)(pt|em)', style)
if size_match:
size = size_match.group(1)
unit = size_match.group(2)
bbcode_size = self._convert_font_size(size, unit)
tags.append(f'[SIZE={bbcode_size}]')
# Handle color
color_match = re.search(r'color:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)', style)
if color_match:
rgb = f'#{int(color_match.group(1)):02x}{int(color_match.group(2)):02x}{int(color_match.group(3)):02x}'
tags.append(f'[COLOR={rgb}]')
# Apply all opening tags
for tag in tags:
content = tag + content
# Apply all closing tags in reverse order
for tag in reversed(tags):
content += f'[/{tag[1:tag.index("=")]}]' if '=' in tag else f'[/{tag[1:]}]'
# Only add newlines if there's actual content and we're not in a special container
if content.strip() and not element.find_parent(['blockquote', 'center']):
# Check if the content is just a single element (like an IMG or MEDIA tag)
if re.match(r'^\[(?:IMG|MEDIA)[^\]]*\][^\[]*\[/(?:IMG|MEDIA)\]$', content.strip()):
return f'{content}\n'
return f'{content}\n\n'
return content
# Handle styled elements (span)
if element.name == 'span':
style = element.get('style', '')
if style:
content = self._get_inner_content(element)
tags = []
# Handle font size
size_match = re.search(r'font-size:\s*(\d+(?:\.\d+)?)(pt|em)', style)
if size_match:
size = size_match.group(1)
unit = size_match.group(2)
bbcode_size = self._convert_font_size(size, unit)
tags.append(f'[SIZE={bbcode_size}]')
# Handle color
color_match = re.search(r'color:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)', style)
if color_match:
rgb = f'#{int(color_match.group(1)):02x}{int(color_match.group(2)):02x}{int(color_match.group(3)):02x}'
tags.append(f'[COLOR={rgb}]')
# Apply all opening tags
for tag in tags:
content = tag + content
# Apply all closing tags in reverse order
for tag in reversed(tags):
content += f'[/{tag[1:tag.index("=")]}]' if '=' in tag else f'[/{tag[1:]}]'
return content
else:
content = self._get_inner_content(element)
return content
# Handle warning paragraph
if element.name == 'p' and element.find('a', {'class': 'externalLink ProxyLink'}):
warning_text = self._get_inner_content(element)
return f'[SIZE=3][COLOR=rgb(255, 128, 0)]{warning_text}[/COLOR][/SIZE]\n\n'
# Handle text alignment in divs
if element.name == 'div':
style = element.get('style', '')
if 'text-align: center' in style:
inner_content = self._get_inner_content(element).strip()
if inner_content:
# Both opening and closing tags on their own lines
return f'[CENTER]\n{inner_content}\n[/CENTER]'
return ''
# Handle blockquotes - strip all tags inside
if element.name == 'blockquote':
# Get raw text without any formatting
content = ''
for text in element.stripped_strings:
content += text + '\n'
return f'[QUOTE]\n{content.strip()}\n[/QUOTE]'
# Handle links
if element.name == 'a':
href = element.get('href', '')
if href:
# Check if it's a YouTube URL first
youtube_bbcode = self._convert_youtube_url(href)
if youtube_bbcode:
return youtube_bbcode
# Otherwise handle as normal URL
href = self.url_replacements.get(href, href)
content = self._get_inner_content(element)
# Check if there's space after the link
next_sibling = element.next_sibling
has_space = next_sibling and isinstance(next_sibling, NavigableString) and next_sibling.startswith(' ')
# Use single quotes for URLs and preserve space
return f"[URL='{href}']{content}[/URL]" + (' ' if has_space else '')
return ''
# Handle images
if element.name == 'img':
src = element.get('src', '')
if src:
src = self.url_replacements.get(src, src)
return f'[IMG]{src}[/IMG]'
return ''
# Handle lists
if element.name in ['ul', 'ol']:
result = '[LIST]' if element.name == 'ul' else '[LIST=1]'
result += '\n'
for item in element.find_all('li', recursive=False):
result += '[*]' + self._get_inner_content(item).strip() + '\n'
result += '[/LIST]\n'
return result
# Handle pre/code blocks
if element.name == 'pre':
if element.find('code'):
# Get the raw content preserving original formatting
code_content = ''
for string in element.find('code').strings:
code_content += string
if code_content:
# Preserve original formatting for HTML content
if 'language-markup' in element.get('class', []):
return f'[html]\n{code_content}\n[/html]'
return f'[code]\n{code_content}\n[/code]'
return ''
# Handle basic formatting
if element.name in self.conversion_map:
content = self._get_inner_content(element)
if content.strip():
return f"{self.conversion_map[element.name][0]}{content}{self.conversion_map[element.name][1]}"
# Process all other tags
return self._get_inner_content(element)
def _get_inner_content(self, element):
return ''.join(self._process_tag(child) for child in element.children)
def convert_html_to_bbcode(html_content):
"""
Convert HTML content to BBCode.
Args:
html_content (str): The HTML content to convert
Returns:
str: The converted BBCode content
"""
converter = HTMLToBBCode()
return converter.convert(html_content)
def convert_file(input_file):
"""
Convert an HTML file to BBCode and save it with .bbcode extension
Args:
input_file (str): Path to the HTML file
"""
if not input_file.endswith('.html'):
print(f"Error: Input file '{input_file}' must have .html extension")
return False
output_file = input_file[:-5] + '.bbcode' # Replace .html with .bbcode
try:
with open(input_file, 'r', encoding='utf-8') as f:
html_content = f.read()
bbcode = convert_html_to_bbcode(html_content)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(bbcode)
print(f"Successfully converted '{input_file}' to '{output_file}'")
return True
except Exception as e:
print(f"Error converting file: {str(e)}")
return False
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python html_to_bbcode.py <input_file.html>")
sys.exit(1)
input_file = sys.argv[1]
if not os.path.exists(input_file):
print(f"Error: File '{input_file}' does not exist")
sys.exit(1)
success = convert_file(input_file)
sys.exit(0 if success else 1)

3
Description/index.html Normal file
View file

@ -0,0 +1,3 @@
<body style="background: #232323;">
<iframe width="1150" height="99%" src="vp.html" style="display: block; margin: 0 auto; background: white;"></iframe>
</body>

View file

@ -0,0 +1 @@
beautifulsoup4==4.12.2

81
Description/vp.html Normal file
View file

@ -0,0 +1,81 @@
<!-- v2 WARNING -->
<p style="font-size: 16px; color: rgb(255, 128, 0); display: inline;">NOTE FOR LEGACY USERS: v3 will create a new datafolder. You can convert the data of v2 using the <a class="externalLink ProxyLink" href="https://git.sbdevelopment.tech/SBDevelopment/VehiclesPlusConverter/releases/tag/0.2" target="_blank" rel="nofollow noopener" data-proxy-href="https://git.sbdevelopment.tech/SBDevelopment/VehiclesPlusConverter/releases/tag/0.2">VehiclesPlusConverter</a>.</p>
<!-- Name header -->
<p>
<img src="https://sbdevelopment.tech/images/headers/vp.png"/>
</p>
<!-- Button array -->
<div style="text-align: center;">
<a href="https://polymart.org/resource/vehiclesplus-1-12-1-20-2.633?purchase=1">
<img src="https://sbdevelopment.tech/images/buttons/buy2.png"
width="220" height="88"/></a>&nbsp;
<a href="https://docs.sbdevelopment.tech/vehiclesplus-v3/about">
<img src="https://sbdevelopment.tech/images/buttons/wiki2.png"
width="220" height="88"/></a>&nbsp;
<a href="https://discord.gg/z26ZGrrFWB">
<img src="https://sbdevelopment.tech/images/buttons/discord2.png"
width="220" height="88"/></a>&nbsp;
<a href="https://sbdevelopment.tech">
<img src="https://sbdevelopment.tech/images/buttons/website2.png"
width="220" height="88"/></a>
</div>
<!-- New line -->
<div style="text-align: center;">&nbsp;</div>
<!-- Polymart only note: Discount for Turkey, Brazil and Ukraine -->
<div style="text-align: center;">
<em class="hidden-bbcode">Buyers from Turkey, Brazil or Ukraine will receive a currency-based discount!</em>
</div>
<!-- USAGE -->
<p><img src="https://sbdevelopment.tech/images/banners/usage.png" width="100%"/></p>
<p>The plugin is pretty easy to start with!<br/>Follow our <a href="https://docs.sbdevelopment.tech/vehiclesplus-v3/setup">Getting Started</a> wiki page to find out more.</p>
<!-- FUNCTIONALITIES -->
<p><img src="https://sbdevelopment.tech/images/banners/functionalities.png" width="100%"/></p>
<p>The plugin has a lot of features to make your server more fun!<br/>You can find out more about the features <a href="https://docs.sbdevelopment.tech/vehiclesplus-v3/vehicles">on our Wiki</a>.</p>
<!-- Tutorials -->
<p><img src="https://sbdevelopment.tech/images/banners/tutorials.png" width="100%"/></p>
<p><a href="https://www.youtube.com/watch?v=i3o2s4g-vBs">https://www.youtube.com/watch?v=i3o2s4g-vBs</a></p>
<p><a href="https://youtu.be/07H_CQcgBrY">https://youtu.be/07H_CQcgBrY</a></p>
<!-- For developers -->
<p><img src="https://sbdevelopment.tech/images/banners/fordevelopers.png" width="100%"/></p>
<p>The plugin has a big API available for developers to create cool add-ons!<br/>Follow <a href="https://docs.sbdevelopment.tech/vehiclesplus-v3/api/gettingstarted">the API Usage instructions on our Wiki</a>.</p>
<!-- Terms of Service -->
<p><img src="https://sbdevelopment.tech/images/banners/termsofservice.png" width="100%"/></p>
<ul>
<li>By purchasing this plugin, you affirm that you have thoroughly read and comprehended the complete description of
the plugin.
</li>
<li>You are expressly prohibited from modifying, reselling, or redistributing this plugin without explicit
authorization from the plugin's rights holder.
</li>
<li>Decompiling or reverse engineering of this plugin is strictly prohibited, and you shall not engage in such
activities without prior written consent from the plugin's rights holder.
</li>
<li>It is forbidden to request support through the reviews section. Please utilize the designated channels such as
the Discord server or GitHub issues page for seeking support.
</li>
<li>By clicking on the download link, you expressly waive any right to withdraw this product as per the provisions
of European Directive 2011/83/EU on Consumer Rights.
</li>
<li>These Terms of Service are subject to modification without prior notice.</li>
</ul>
<!-- DISCLAIMER -->
<blockquote>
<p><span style="font-size: 8pt;">Copyright (C) SBDevelopment - All Rights Reserved.</span></p>
<p>
<span style="font-size: 8pt;">THE CONTENTS OF THIS PROJECT ARE PROPRIETARY AND CONFIDENTIAL.</span><br/>
<span style="font-size: 8pt;">UNAUTHORIZED COPYING, TRANSFERRING OR REPRODUCTION OF THE CONTENTS OF THIS PROJECT, VIA ANY MEDIUM, IS STRICTLY PROHIBITED.</span>
</p>
<p>
<span style="font-size: 8pt;">The receipt or possession of the source code and/or any parts thereof does not convey or imply any right to use them</span><br/>
<span style="font-size: 8pt;">for any purpose other than the purpose for which they were provided to you.</span>
</p>
<p>
<span style="font-size: 8pt;">The software is provided "AS IS", without warranty of any kind, express or implied, including but not limited to</span><br/>
<span style="font-size: 8pt;">the warranties of merchantability, fitness for a particular purpose and non infringement.</span><br/>
<span style="font-size: 8pt;">In no event shall the authors or copyright holders be liable for any claim, damages or other liability,</span><br/>
<span style="font-size: 8pt;">whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software</span><br/>
<span style="font-size: 8pt;">or the use or other dealings in the software.</span>
</p>
<p><span style="font-size: 8pt;">The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</span>
</p>
</blockquote>