This commit is contained in:
2026-02-21 23:57:03 +01:00
commit 2d9e3a6044
26 changed files with 6716 additions and 0 deletions

View 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
View 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
View File

@@ -0,0 +1,9 @@
{
"useTabs": true,
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"jsxSingleQuote": false,
"printWidth": 175
}

21
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
ignoredBuiltDependencies:
- '@parcel/watcher'

38
release.config.mjs Normal file
View 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
View File

@@ -0,0 +1 @@
requests>=2.25.0

127
scripts/build-plugin.ts Normal file
View 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
View 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
View 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
View File

19
tsconfig.json Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"include": ["."],
"exclude": ["node_modules", "../frontend"]
}