meow
This commit is contained in:
45
.gitea/workflows/release.yml
Normal file
45
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Create Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pnpm install --no-frozen-lockfile
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run Semantic Release
|
||||||
|
run: npx semantic-release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
87
.gitignore
vendored
Normal file
87
.gitignore
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
.millennium/
|
||||||
|
.build
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
.tscache/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# Temporary folders
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# Package files
|
||||||
|
*.tgz
|
||||||
|
*.tar.gz
|
||||||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 175
|
||||||
|
}
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 tor968
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
17
README.md
Normal file
17
README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Leetify Extension for Millennium
|
||||||
|
|
||||||
|
A Millennium plugin that integrates Leetify data and functionality directly into the Steam client, providing enhanced Counter-Strike statistics and profile information.
|
||||||
|
|
||||||
|
## 📋 Prerequisites
|
||||||
|
|
||||||
|
Before installing this plugin, ensure you have:
|
||||||
|
|
||||||
|
- **[Millennium](https://steambrew.app/)** installed and configured
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 🔗 Links
|
||||||
|
|
||||||
|
- [Millennium Framework](https://github.com/SteamClientHomebrew/Millennium)
|
||||||
|
- [Leetify](https://leetify.com)
|
||||||
|
- [Steam Client](https://store.steampowered.com/about/)
|
||||||
83
RELEASE.md
Normal file
83
RELEASE.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Release Guide
|
||||||
|
|
||||||
|
This document explains how to create and manage releases for the Leetify Extension plugin.
|
||||||
|
|
||||||
|
## 📋 Overview
|
||||||
|
|
||||||
|
The project supports two types of releases:
|
||||||
|
|
||||||
|
1. **Automated Releases** - Using GitHub Actions and semantic-release
|
||||||
|
2. **Manual Releases** - Using local scripts for more control
|
||||||
|
|
||||||
|
## 🤖 Automated Releases (Recommended)
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Repository must be hosted on GitHub
|
||||||
|
- `GITHUB_TOKEN` secret must be configured in repository settings
|
||||||
|
- All changes committed to `main` or `master` branch
|
||||||
|
|
||||||
|
### How it Works
|
||||||
|
|
||||||
|
1. **Commit Messages**: Use [Conventional Commits](https://www.conventionalcommits.org/) format:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add new Leetify integration feature
|
||||||
|
fix: resolve profile loading issue
|
||||||
|
docs: update installation instructions
|
||||||
|
chore: update dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Automatic Triggering**: Releases are triggered automatically when:
|
||||||
|
|
||||||
|
- Commits are pushed to `main`/`master` branch
|
||||||
|
- Commit messages follow conventional format
|
||||||
|
- Changes affect code (not just documentation)
|
||||||
|
|
||||||
|
3. **Version Bumping**: Semantic-release automatically determines version based on commit types:
|
||||||
|
- `fix:` → Patch release (1.0.0 → 1.0.1)
|
||||||
|
- `feat:` → Minor release (1.0.0 → 1.1.0)
|
||||||
|
- `BREAKING CHANGE:` → Major release (1.0.0 → 2.0.0)
|
||||||
|
|
||||||
|
### Setting Up Automated Releases
|
||||||
|
|
||||||
|
1. **Configure GitHub Secrets**:
|
||||||
|
|
||||||
|
- Go to your repository → Settings → Secrets and variables → Actions
|
||||||
|
- Add `GITHUB_TOKEN` (usually automatically available)
|
||||||
|
|
||||||
|
2. **Make a Release Commit**:
|
||||||
|
|
||||||
|
````bash
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: add Leetify profile integration"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
3. **Monitor Release**:
|
||||||
|
- Check Actions tab in GitHub for release progress
|
||||||
|
- Release will appear in Releases section when complete
|
||||||
|
|
||||||
|
## 🔧 Manual Releases
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 18+ with pnpm installed
|
||||||
|
- Python 3.7+ with pip
|
||||||
|
- Git repository with clean working directory
|
||||||
|
|
||||||
|
### Creating a Manual Release
|
||||||
|
|
||||||
|
1. **Install Dependencies**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run Release Script**:
|
||||||
|
````bash
|
||||||
|
# Patch release (1.0.0 → 1.0.1)\n pnpm run release patch\n \n # Minor release (1.0.0 → 1.1.0)\n pnpm run release minor\n \n # Major release (1.0.0 → 2.0.0)\n pnpm run release major\n ```\n\n3. **Push Changes**:\n ```bash\n git push origin main\n git push origin v<version>\n ```\n\n4. **Create GitHub Release**:\n - Go to GitHub → Releases → \"Create a new release\"\n - Select the tag created by the script\n - Upload the generated ZIP file from `build/` directory\n - Add release notes\n\n### Available Scripts\n\n```bash\n# Build plugin package\npnpm run build-plugin\n\n# Create manual release\npnpm run release <patch|minor|major>\n\n# Sync versions between files\npnpm run sync-version <version>\n\n# Development build\npnpm run dev\n\n# Production build\npnpm run build\n```\n\n## 📦 Release Artifacts\n\nEach release creates:\n- **ZIP Package**: `leetify-extension-v<version>.zip`\n- **Updated Files**: `package.json`, `plugin.json`, `CHANGELOG.md`\n- **Git Tag**: `v<version>`\n- **GitHub Release**: With ZIP attachment and release notes\n\n### Package Contents\n```\nleetify-extension-v1.0.0.zip\n├── frontend/ # React frontend components\n├── webkit/ # Steam webkit integration\n├── backend/ # Python backend logic\n├── styles/ # CSS styles\n├── dist/ # Built assets (if exists)\n├── package.json # Node.js package configuration\n├── plugin.json # Millennium plugin configuration\n├── requirements.txt # Python dependencies\n├── README.md # Installation and usage guide\n└── LICENSE # License file\n```\n\n## 🔄 Version Management\n\n### Version Synchronization\nThe release system automatically keeps versions synchronized across:\n- `package.json` - Node.js package version\n- `plugin.json` - Millennium plugin version\n\n### Version Scheme\nFollows [Semantic Versioning](https://semver.org/):\n- **MAJOR** (X.0.0): Breaking changes, incompatible API changes\n- **MINOR** (0.X.0): New features, backwards compatible\n- **PATCH** (0.0.X): Bug fixes, backwards compatible\n\n## 🚀 Deployment Process\n\n### For Plugin Users\n1. Download latest release ZIP from GitHub Releases\n2. Extract to Steam plugins directory\n3. Enable plugin in Millennium settings\n4. Restart Steam\n\n### For Developers\n1. Clone repository\n2. Install dependencies\n3. Make changes\n4. Create release (automated or manual)\n5. Users download and install\n\n## 🛠️ Troubleshooting\n\n### Common Issues\n\n**Build Fails**:\n```bash\n# Clear cache and reinstall\npnpm clean-install\npip install --upgrade -r requirements.txt\n```\n\n**Version Sync Issues**:\n```bash\n# Manually sync versions\npnpm run sync-version 1.2.3\n```\n\n**Git Tag Conflicts**:\n```bash\n# Delete local tag\ngit tag -d v1.0.0\n\n# Delete remote tag\ngit push origin :refs/tags/v1.0.0\n```\n\n**Release Workflow Fails**:\n- Check GitHub Actions logs\n- Verify secrets are configured\n- Ensure commit messages follow conventional format\n- Check for merge conflicts\n\n### Debug Mode\n```bash\n# Enable verbose logging\nexport DEBUG=semantic-release:*\npnpm run release patch\n```\n\n## 📋 Release Checklist\n\n### Before Release\n- [ ] All tests pass\n- [ ] Documentation is updated\n- [ ] CHANGELOG.md reflects changes\n- [ ] Version compatibility verified\n- [ ] No uncommitted changes\n\n### During Release\n- [ ] Choose appropriate version bump\n- [ ] Verify build completes successfully\n- [ ] Check generated ZIP contains all files\n- [ ] Validate plugin.json and package.json versions match\n\n### After Release\n- [ ] GitHub release created successfully\n- [ ] ZIP file uploaded to release\n- [ ] Release notes are accurate\n- [ ] Installation instructions updated if needed\n- [ ] Community notified (if applicable)\n\n## 🔗 Related Links\n\n- [Conventional Commits](https://www.conventionalcommits.org/)\n- [Semantic Versioning](https://semver.org/)\n- [GitHub Actions Documentation](https://docs.github.com/en/actions)\n- [Millennium Plugin Documentation](https://docs.steambrew.app/developers/plugins/learn)\n\n---\n\n**Note**: This release system is designed specifically for Millennium plugins and follows the framework's conventions and requirements.
|
||||||
|
````
|
||||||
23
backend/logger.py
Normal file
23
backend/logger.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import PluginUtils # type: ignore[import]
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self):
|
||||||
|
self._logger = PluginUtils.Logger()
|
||||||
|
|
||||||
|
def info(self, message: str) -> None:
|
||||||
|
"""Log an info message"""
|
||||||
|
self._logger.log(message)
|
||||||
|
|
||||||
|
def error(self, message: str) -> None:
|
||||||
|
"""Log an error message"""
|
||||||
|
self._logger.log(f"ERROR: {message}")
|
||||||
|
|
||||||
|
def warning(self, message: str) -> None:
|
||||||
|
"""Log a warning message"""
|
||||||
|
self._logger.log(f"WARNING: {message}")
|
||||||
|
|
||||||
|
def debug(self, message: str) -> None:
|
||||||
|
"""Log a debug message"""
|
||||||
|
self._logger.log(f"DEBUG: {message}")
|
||||||
|
|
||||||
|
logger = Logger()
|
||||||
28
backend/main.py
Normal file
28
backend/main.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import Millennium # pyright: ignore[reportMissingImports]
|
||||||
|
from logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
def _load(self) -> None:
|
||||||
|
try:
|
||||||
|
logger.info("Leetify Extension: Starting plugin initialization...")
|
||||||
|
Millennium.ready()
|
||||||
|
logger.info("Leetify Extension: Plugin loaded successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Leetify Extension: Failed to load plugin: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _front_end_loaded(self) -> None:
|
||||||
|
try:
|
||||||
|
logger.info("Leetify Extension: Frontend loaded successfully")
|
||||||
|
# Add any frontend-specific initialization logic here if needed
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Leetify Extension: Error during frontend load: {str(e)}")
|
||||||
|
|
||||||
|
def _unload(self) -> None:
|
||||||
|
try:
|
||||||
|
logger.info("Leetify Extension: Plugin unloading...")
|
||||||
|
# Add any cleanup logic here if needed
|
||||||
|
logger.info("Leetify Extension: Plugin unloaded successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Leetify Extension: Error during plugin unload: {str(e)}")
|
||||||
78
backend/settings.py
Normal file
78
backend/settings.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from typing import Literal
|
||||||
|
from MillenniumUtils import ( # pyright: ignore[reportMissingImports]
|
||||||
|
CheckBox,
|
||||||
|
DefineSetting,
|
||||||
|
DropDown,
|
||||||
|
NumberTextInput,
|
||||||
|
Settings,
|
||||||
|
FloatSlider,
|
||||||
|
StringTextInput,
|
||||||
|
FloatTextInput,
|
||||||
|
NumberSlider,
|
||||||
|
)
|
||||||
|
from logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class PluginSettings(metaclass=Settings):
|
||||||
|
@DefineSetting(
|
||||||
|
name="CheckBox Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=CheckBox(),
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
def checkboxInput(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@DefineSetting(
|
||||||
|
name="Dropdown Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=DropDown(items=["String Value", False, 69]),
|
||||||
|
default="String Value",
|
||||||
|
)
|
||||||
|
def dropDownInput(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@DefineSetting(
|
||||||
|
name="Float Slider Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=FloatSlider(range=(0.0, 10.0), step=0.5),
|
||||||
|
default=0.5,
|
||||||
|
)
|
||||||
|
def floatSliderInput(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@DefineSetting(
|
||||||
|
name="Number Slider Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=NumberSlider(range=(0, 10), step=1),
|
||||||
|
default=5,
|
||||||
|
)
|
||||||
|
def numberSliderInput(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@DefineSetting(
|
||||||
|
name="Number Text Input Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=NumberTextInput(range=(0, 10000)),
|
||||||
|
default=1234,
|
||||||
|
)
|
||||||
|
def numberTextInput(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@DefineSetting(
|
||||||
|
name="String Text Input Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=StringTextInput(),
|
||||||
|
default="Hello World!",
|
||||||
|
)
|
||||||
|
def stringTextInput(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@DefineSetting(
|
||||||
|
name="Float Text Input Example",
|
||||||
|
description="lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
style=FloatTextInput(range=(0, 10000)),
|
||||||
|
default=1234.0,
|
||||||
|
)
|
||||||
|
def floatTextInput(self):
|
||||||
|
pass
|
||||||
5
frontend/index.tsx
Normal file
5
frontend/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { definePlugin } from '@steambrew/client';
|
||||||
|
|
||||||
|
export default definePlugin(() => {
|
||||||
|
console.log('.rip Extension: Frontend plugin initializing...');
|
||||||
|
});
|
||||||
16
frontend/settings.tsx
Normal file
16
frontend/settings.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { BindPluginSettings } from '@steambrew/client';
|
||||||
|
|
||||||
|
type CheckBox = true | false;
|
||||||
|
type EnumerateInternal<N extends number, Acc extends number[] = []> = Acc['length'] extends N ? Acc[number] : EnumerateInternal<N, [...Acc, Acc['length']]>;
|
||||||
|
type Enumerate<Min extends number, Max extends number> = Exclude<EnumerateInternal<Max>, EnumerateInternal<Min>> | Max;
|
||||||
|
type NumberTextInput<Min extends number, Max extends number> = Min | Enumerate<Min, Max>;
|
||||||
|
type DropDown<T extends readonly any[]> = T[number];
|
||||||
|
|
||||||
|
interface SettingsProps {
|
||||||
|
doFrontEndCall: CheckBox;
|
||||||
|
overrideWebkitDocument: CheckBox;
|
||||||
|
numberTextInput: NumberTextInput<1, 100>;
|
||||||
|
frontEndMessage: DropDown<['hello', 'hi', 'hello again', false, 69]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let PluginSettings: SettingsProps = BindPluginSettings();
|
||||||
9
frontend/tsconfig.json
Normal file
9
frontend/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsxFactory": "window.SP_REACT.createElement",
|
||||||
|
"jsxFragmentFactory": "window.SP_REACT.Fragment"
|
||||||
|
},
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["node_modules", "../webkit"]
|
||||||
|
}
|
||||||
38
package.json
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "millennium-ttc --build dev",
|
||||||
|
"watch": "nodemon --watch webkit --watch frontend --ext ts,tsx,js,jsx --exec \"millennium-ttc --build dev\"",
|
||||||
|
"build": "millennium-ttc --build prod",
|
||||||
|
"sync-version": "npx tsx scripts/sync-version.ts 1.0.0",
|
||||||
|
"release": "npx tsx scripts/build-plugin.ts 1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
|
"@semantic-release/commit-analyzer": "^11.1.0",
|
||||||
|
"@semantic-release/exec": "^6.0.3",
|
||||||
|
"@semantic-release/git": "^10.0.1",
|
||||||
|
"@semantic-release/github": "^9.2.6",
|
||||||
|
"@semantic-release/release-notes-generator": "^12.1.0",
|
||||||
|
"@types/react": "^19.1.12",
|
||||||
|
"@types/react-dom": "^19.1.8",
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"archiver": "^6.0.0",
|
||||||
|
"nodemon": "^3.1.9",
|
||||||
|
"semantic-release": "^22.0.12",
|
||||||
|
"tsx": "^4.7.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"name": "rip-extension",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A Millennium plugin that adds .rip integration to Steam profiles",
|
||||||
|
"main": "./frontend/index.tsx",
|
||||||
|
"author": "mx Extension Team",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@steambrew/api": "^5.5.3",
|
||||||
|
"@steambrew/client": "^5.5.3",
|
||||||
|
"@steambrew/ttc": "^2.7.3",
|
||||||
|
"@steambrew/webkit": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
plugin.json
Normal file
7
plugin.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/SteamClientHomebrew/Millennium/main/src/sys/plugin-schema.json",
|
||||||
|
"name": ".rip-extension",
|
||||||
|
"common_name": "rip Extension",
|
||||||
|
"description": "A Millennium plugin that adds csst.at integration to Steam profiles.",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
5872
pnpm-lock.yaml
generated
Normal file
5872
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ignoredBuiltDependencies:
|
||||||
|
- '@parcel/watcher'
|
||||||
38
release.config.mjs
Normal file
38
release.config.mjs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export default {
|
||||||
|
branches: ['main', 'master'],
|
||||||
|
plugins: [
|
||||||
|
'@semantic-release/commit-analyzer',
|
||||||
|
'@semantic-release/release-notes-generator',
|
||||||
|
[
|
||||||
|
'@semantic-release/changelog',
|
||||||
|
{
|
||||||
|
changelogFile: 'CHANGELOG.md',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'@semantic-release/exec',
|
||||||
|
{
|
||||||
|
prepareCmd: 'npx tsx scripts/sync-version.ts ${nextRelease.version}',
|
||||||
|
publishCmd: 'npm run build && npx tsx scripts/build-plugin.ts ${nextRelease.version}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'@semantic-release/github',
|
||||||
|
{
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
path: 'leetify-extension-*.zip',
|
||||||
|
label: 'Leetify Extension Plugin (${nextRelease.gitTag})',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'@semantic-release/git',
|
||||||
|
{
|
||||||
|
assets: ['package.json', 'plugin.json', 'CHANGELOG.md'],
|
||||||
|
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
requests>=2.25.0
|
||||||
127
scripts/build-plugin.ts
Normal file
127
scripts/build-plugin.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env tsx
|
||||||
|
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { existsSync, mkdirSync, rmSync, cpSync, createWriteStream } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const archiver = require('archiver');
|
||||||
|
|
||||||
|
const version = process.argv[2];
|
||||||
|
if (!version) {
|
||||||
|
console.error('Version is required as first argument');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootDir = process.cwd();
|
||||||
|
const releaseDir = join(rootDir, 'release');
|
||||||
|
const zipName = `leetify-extension-v${version}.zip`;
|
||||||
|
|
||||||
|
console.log(`Building plugin version ${version}...`);
|
||||||
|
|
||||||
|
async function buildPlugin() {
|
||||||
|
try {
|
||||||
|
// Clean up any existing release directory
|
||||||
|
if (existsSync(releaseDir)) {
|
||||||
|
rmSync(releaseDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create release directory
|
||||||
|
mkdirSync(releaseDir, { recursive: true });
|
||||||
|
|
||||||
|
// Copy necessary files and directories
|
||||||
|
const filesToCopy = [
|
||||||
|
{ src: '.millennium', dest: '.millennium' },
|
||||||
|
{ src: 'backend', dest: 'backend' },
|
||||||
|
{ src: 'plugin.json', dest: 'plugin.json' },
|
||||||
|
{ src: 'requirements.txt', dest: 'requirements.txt' },
|
||||||
|
{ src: 'README.md', dest: 'README.md' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if LICENSE file exists and add it if it does
|
||||||
|
if (existsSync(join(rootDir, 'LICENSE'))) {
|
||||||
|
filesToCopy.push({ src: 'LICENSE', dest: 'LICENSE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy styles if they exist
|
||||||
|
if (existsSync(join(rootDir, 'styles'))) {
|
||||||
|
mkdirSync(join(releaseDir, 'static'), { recursive: true });
|
||||||
|
cpSync(join(rootDir, 'styles'), join(releaseDir, 'static'), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all files
|
||||||
|
for (const { src, dest } of filesToCopy) {
|
||||||
|
const srcPath = join(rootDir, src);
|
||||||
|
const destPath = join(releaseDir, dest);
|
||||||
|
|
||||||
|
if (existsSync(srcPath)) {
|
||||||
|
cpSync(srcPath, destPath, { recursive: true });
|
||||||
|
console.log(`Copied ${src} to release directory`);
|
||||||
|
} else {
|
||||||
|
console.warn(`Warning: ${src} does not exist, skipping...`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Git metadata
|
||||||
|
console.log('Generating Git metadata...');
|
||||||
|
try {
|
||||||
|
const commitId = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
|
||||||
|
const pluginId = execSync('git rev-list --max-parents=0 HEAD', { encoding: 'utf8' }).trim();
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
id: pluginId,
|
||||||
|
commitId: commitId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadataPath = join(releaseDir, 'metadata.json');
|
||||||
|
|
||||||
|
// Write metadata.json to release directory
|
||||||
|
require('fs').writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
||||||
|
console.log('Generated metadata.json with Git information');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Warning: Could not generate Git metadata:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create zip file using archiver for cross-platform compatibility
|
||||||
|
console.log('Creating zip file...');
|
||||||
|
const zipPath = join(rootDir, zipName);
|
||||||
|
const output = createWriteStream(zipPath);
|
||||||
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||||
|
|
||||||
|
// Listen for archive events
|
||||||
|
archive.on('error', (err: any) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
output.on('close', () => {
|
||||||
|
console.log(`✅ Successfully created ${zipName}`);
|
||||||
|
// Clean up release directory
|
||||||
|
rmSync(releaseDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pipe archive data to the file
|
||||||
|
archive.pipe(output);
|
||||||
|
|
||||||
|
// Add all files from release directory to archive
|
||||||
|
archive.directory(releaseDir, false);
|
||||||
|
|
||||||
|
// Finalize the archive
|
||||||
|
await archive.finalize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Build failed:', error);
|
||||||
|
|
||||||
|
// Clean up on error
|
||||||
|
if (existsSync(releaseDir)) {
|
||||||
|
rmSync(releaseDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the build
|
||||||
|
buildPlugin().catch((error) => {
|
||||||
|
console.error('❌ Build failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
56
scripts/sync-version.ts
Normal file
56
scripts/sync-version.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
const rootDir = join(__dirname, '..');
|
||||||
|
|
||||||
|
interface PackageJson {
|
||||||
|
version: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PluginJson {
|
||||||
|
version: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncVersion(newVersion: string = '1.0.0'): void {
|
||||||
|
if (!newVersion) {
|
||||||
|
console.error('Error: Version argument is required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update package.json
|
||||||
|
const packagePath: string = join(rootDir, 'package.json');
|
||||||
|
const packageJson: PackageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
|
||||||
|
packageJson.version = newVersion;
|
||||||
|
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||||
|
console.log(`✓ Updated package.json to version ${newVersion}`);
|
||||||
|
|
||||||
|
// Update plugin.json
|
||||||
|
const pluginPath: string = join(rootDir, 'plugin.json');
|
||||||
|
const pluginJson: PluginJson = JSON.parse(readFileSync(pluginPath, 'utf8'));
|
||||||
|
pluginJson.version = newVersion;
|
||||||
|
writeFileSync(pluginPath, JSON.stringify(pluginJson, null, '\t') + '\n');
|
||||||
|
console.log(`✓ Updated plugin.json to version ${newVersion}`);
|
||||||
|
|
||||||
|
console.log(`✅ Version synchronization complete: ${newVersion}`);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error syncing version:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run if called directly
|
||||||
|
if (import.meta.url.startsWith('file:') && process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||||
|
// Get version from command line argument
|
||||||
|
const newVersion: string = process.argv[2];
|
||||||
|
syncVersion(newVersion);
|
||||||
|
}
|
||||||
17
scripts/tsconfig.json
Normal file
17
scripts/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitReturns": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["*.ts"],
|
||||||
|
"exclude": ["node_modules", "build"]
|
||||||
|
}
|
||||||
0
styles/styles.css
Normal file
0
styles/styles.css
Normal file
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": ".millennium/Dist",
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES2020",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": false,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
}
|
||||||
|
}
|
||||||
89
webkit/index.tsx
Normal file
89
webkit/index.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
type Millennium = {
|
||||||
|
callServerMethod: (methodName: string, kwargs?: any) => Promise<any>;
|
||||||
|
findElement: (privateDocument: Document, querySelector: string, timeOut?: number) => Promise<NodeListOf<Element>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare const Millennium: Millennium;
|
||||||
|
|
||||||
|
export default async function WebkitMain() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (typeof Millennium === 'undefined') {
|
||||||
|
console.error('csst.at probably down idk...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = `
|
||||||
|
.rip-btn {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #FFF;
|
||||||
|
font-weight: 800;
|
||||||
|
transition: all 0.5s cubic-bezier(.23, 1, .32, 1);
|
||||||
|
background-color: #2d243aff;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rip-btn:hover {
|
||||||
|
background-color: #FFF;
|
||||||
|
color: #2d243aff;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const styleSheet = document.createElement('style');
|
||||||
|
styleSheet.innerText = styles;
|
||||||
|
document.head.appendChild(styleSheet);
|
||||||
|
|
||||||
|
const rightCol = await Millennium.findElement(document, '.profile_rightcol');
|
||||||
|
|
||||||
|
if (rightCol.length > 0) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const profileUrl = `${window.location.href}/?xml=1`;
|
||||||
|
|
||||||
|
const profileResponse = await fetch(profileUrl);
|
||||||
|
if (!profileResponse.ok) {
|
||||||
|
console.error(`Leetify: Failed to fetch profile data: ${profileResponse.status} ${profileResponse.statusText}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileXmlText = await profileResponse.text();
|
||||||
|
const profileXmlDoc = parser.parseFromString(profileXmlText, 'application/xml');
|
||||||
|
|
||||||
|
const steamID64 = profileXmlDoc.querySelector('steamID64')?.textContent || '0';
|
||||||
|
|
||||||
|
if (!steamID64 || steamID64 === '0') {
|
||||||
|
console.error('Leetify: Could not parse steamID64 from URL.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsContainer = document.createElement('div');
|
||||||
|
statsContainer.className = 'account-row';
|
||||||
|
|
||||||
|
const button = document.createElement('div');
|
||||||
|
button.className = 'rip-btn';
|
||||||
|
|
||||||
|
button.textContent = '.rip';
|
||||||
|
button.onclick = () => {
|
||||||
|
window.open(`https://steamcommunity.rip/id/${steamID64}`, '_self', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
|
|
||||||
|
statsContainer.appendChild(button);
|
||||||
|
rightCol[0].insertBefore(statsContainer, rightCol[0].children[1]);
|
||||||
|
} else {
|
||||||
|
console.error('Leetify: Parent container ".profile_rightcol" not found');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Leetify: Error in WebkitMain:', error);
|
||||||
|
console.error('Leetify: Stack trace:', error.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
webkit/settings.tsx
Normal file
20
webkit/settings.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { BindPluginSettings } from '@steambrew/webkit';
|
||||||
|
|
||||||
|
type CheckBox = true | false;
|
||||||
|
|
||||||
|
type EnumerateInternal<N extends number, Acc extends number[] = []> = Acc['length'] extends N ? Acc[number] : EnumerateInternal<N, [...Acc, Acc['length']]>;
|
||||||
|
|
||||||
|
type Enumerate<Min extends number, Max extends number> = Exclude<EnumerateInternal<Max>, EnumerateInternal<Min>> | Max;
|
||||||
|
|
||||||
|
type NumberTextInput<Min extends number, Max extends number> = Min | Enumerate<Min, Max>;
|
||||||
|
|
||||||
|
type DropDown<T extends readonly any[]> = T[number];
|
||||||
|
|
||||||
|
interface SettingsProps {
|
||||||
|
doFrontEndCall: CheckBox;
|
||||||
|
overrideWebkitDocument: CheckBox;
|
||||||
|
numberTextInput: NumberTextInput<1, 100>;
|
||||||
|
frontEndMessage: DropDown<['hello', 'hi', 'hello again', false, 69]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let PluginSettings: SettingsProps = BindPluginSettings();
|
||||||
9
webkit/tsconfig.json
Normal file
9
webkit/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["node_modules", "../frontend"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user