Code cleaning

parent f8521de0
# Chrome Extension Runtime API Emulation
This directory contains a test extension and implementation for emulating the Chrome Extension Runtime API in our Qt6-based browser.
## Overview
The implementation consists of:
1. **RuntimeBridge** (`assets/browser/js/runtime_bridge.py`): A Python QObject that bridges between the browser and JavaScript
2. **chrome-runtime-api.js** (`assets/browser/js/chrome-runtime-api.js`): JavaScript implementation of the chrome.runtime API
3. **Test Extension** (`assets/browser/extensions/test-extension/`): A simple extension to test the API implementation
## Supported APIs
The following chrome.runtime APIs are supported:
- `chrome.runtime.id` - Returns the extension ID
- `chrome.runtime.getURL()` - Converts a relative path to a fully-qualified extension URL
- `chrome.runtime.getManifest()` - Returns the extension's manifest.json contents
- `chrome.runtime.sendMessage()` - Sends a message to the extension or another extension
- `chrome.runtime.onMessage` - Event fired when a message is received
- `chrome.runtime.connect()` - Creates a connection to the extension or another extension
- `chrome.runtime.onConnect` - Event fired when a connection is made
- `chrome.runtime.getBackgroundPage()` - Returns the background page
- Various lifecycle events: `onInstalled`, `onStartup`, `onSuspend`, `onUpdateAvailable`
## Test Extension
The test extension demonstrates the use of the chrome.runtime API. It includes:
- **Popup** (`popup.html`, `popup.js`): A simple UI to test various chrome.runtime methods
- **Background Script** (`background.js`): A background script that listens for messages and connections
- **Content Script** (`content.js`): A content script that injects a control panel into web pages
## Testing
To test the chrome.runtime API implementation:
1. Run the test script: `python test_extension.py`
2. Click the "Test Extension" button in the toolbar
3. Use the buttons in the popup to test various chrome.runtime API methods
4. Check the browser console for log messages
You can also test the content script by navigating to any website and looking for the control panel in the bottom-right corner.
## Implementation Details
### Extension Loading
Extensions are loaded from the `assets/browser/extensions` directory and copied to the browser profile's extensions directory. The browser scans for extensions on startup and registers them with the runtime bridge.
### URL Scheme Handling
The browser uses a custom URL scheme handler (`qextension://`) to load extension resources. This allows extensions to use relative paths in their manifest and code.
### Message Passing
Message passing is implemented using QWebChannel to communicate between JavaScript and Python. The RuntimeBridge class handles routing messages between different parts of an extension.
### Background Pages
Background pages are loaded in hidden tabs. For service workers, a special HTML page is created that loads the service worker script.
## Limitations
- This is a simplified implementation and does not support all chrome.runtime APIs
- Service workers are not fully supported; they are loaded in a regular page context
- Cross-extension messaging is limited
- Some APIs may behave differently than in Chrome
\ No newline at end of file
This diff is collapsed.
(function(a,b){var r=a0b,c=a();while(!![]){try{var d=-parseInt(r(0x177))/0x1*(-parseInt(r(0x18e))/0x2)+parseInt(r(0x19d))/0x3+-parseInt(r(0x183))/0x4*(-parseInt(r(0x182))/0x5)+-parseInt(r(0x189))/0x6+-parseInt(r(0x17d))/0x7*(parseInt(r(0x18d))/0x8)+-parseInt(r(0x178))/0x9+-parseInt(r(0x19c))/0xa*(parseInt(r(0x18f))/0xb);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0a,0x19149),!(function(){var A=a0b,a,b;a=function(){var s=a0b,c=document[s(0x184)](s(0x19f));c['innerHTML']=s(0x17c),document[s(0x19b)][s(0x194)](c),publicApi(s(0x17e))[s(0x179)](f=>{var t=s,g=f,h=document['querySelector'](t(0x1a0)),i=document['querySelector']('#cbMessageInputSet'),j=document[t(0x190)](t(0x198)),k=document[t(0x190)]('#cbSendMessageSetBtn');g&&void 0x0!==g?(localStorage['setItem']('cbMessageApi',!0x0),window['onunload']=function(){var u=t;localStorage[u(0x188)]('cbMessageApi');},j[t(0x193)](t(0x18b),function(m){var v=t;let p=h[v(0x17b)];p=v(0x199)==typeof p?JSON[v(0x19e)](p):p,g[v(0x18c)](p);}),k[t(0x193)](t(0x18b),function(){var w=t;g[w(0x181)](i['value'],w(0x180));})):j[t(0x193)](t(0x18b),function(){var x=t;window[x(0x197)][x(0x181)](h[x(0x17b)],x(0x180));});let l={'displayName':g['model'][t(0x187)](),'username':g['model'][t(0x191)]()};h[t(0x17a)](t(0x196),JSON['stringify'](l)),g[t(0x193)](t(0x19a),({amount:m,username:p,displayName:q})=>{var y=t;window[y(0x195)]({'type':y(0x19a),'from':y(0x17f),'tip':{'amount':m,'tipperName':q,'username':p,'cParameter':''}});}),g[t(0x193)](t(0x185),({message:m})=>{var z=t;window['postMessage']({'type':z(0x192),'from':'bongacams','message':m});});});},'interactive'===(b=document[A(0x186)])||A(0x18a)===b?a():window[A(0x193)](A(0x176),a);}()));function a0b(a,b){var c=a0a();return a0b=function(d,e){d=d-0x176;var f=c[d];return f;},a0b(a,b);}function a0a(){var B=['1483155beMQRr','then','setAttribute','value','\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27button\x27\x20style=\x27display:\x20none;\x27\x20id=\x27cbSendMessageBtn\x27/>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27text\x27\x20style=\x27display:none;\x27\x20id=\x27cbMessageInput\x27/>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27button\x27\x20style=\x27display:\x20none;\x27\x20id=\x27cbSendMessageSetBtn\x27/>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27text\x27\x20style=\x27display:none;\x27\x20id=\x27cbMessageInputSet\x27/>\x0a\x20\x20\x20\x20','21xZbwoS','lovense','bongacams','btn','sendMessage','5yCBsZF','820288PDCiyg','createElement','order','readyState','getDisplayName','removeItem','394380wJoPNa','complete','click','send','54744HXmhJK','4arBiLz','35079ZLUbyQ','querySelector','getUsername','orderMessage','addEventListener','appendChild','postMessage','data','chat','#cbSendMessageBtn','string','tip','body','40acdosV','410037eFFxca','parse','div','#cbMessageInput','DOMContentLoaded','12394RshGtw'];a0a=function(){return B;};return a0a();}
\ No newline at end of file
function a1b(a,b){var c=a1a();return a1b=function(d,e){d=d-0x112;var f=c[d];return f;},a1b(a,b);}function a1a(){var i=['760588ipeXxo','5eLbVuT','sendMessage','1086620GaZhHN','14071360qeKQTq','3085188xfUxLF','#cbMessageInput','click','4290930gpYhql','265786GqqelY','chat','1475229EwekQg','val'];a1a=function(){return i;};return a1a();}var a1g=a1b;(function(a,b){var f=a1b,c=a();while(!![]){try{var d=parseInt(f(0x11e))/0x1+parseInt(f(0x11a))/0x2+parseInt(f(0x11c))/0x3+-parseInt(f(0x114))/0x4+-parseInt(f(0x112))/0x5*(-parseInt(f(0x116))/0x6)+parseInt(f(0x119))/0x7+-parseInt(f(0x115))/0x8;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a1a,0x75a2d),$('#cbSendMessageBtn')['on'](a1g(0x118),function(){var h=a1g;Chat[h(0x113)]($(h(0x117))[h(0x11d)](),h(0x11b));}));
\ No newline at end of file
var a2g=a2b;(function(a,b){var f=a2b,c=a();while(!![]){try{var d=-parseInt(f(0xd2))/0x1+-parseInt(f(0xdc))/0x2*(-parseInt(f(0xd0))/0x3)+parseInt(f(0xda))/0x4+parseInt(f(0xd5))/0x5+-parseInt(f(0xd6))/0x6+-parseInt(f(0xd8))/0x7*(-parseInt(f(0xd3))/0x8)+parseInt(f(0xdd))/0x9*(parseInt(f(0xdb))/0xa);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a2a,0x51a8c),$('#cbSendMessageBtn')['on'](a2g(0xd9),function(){var h=a2g;window[h(0xd7)][h(0xd1)][h(0xcf)]($(h(0xd4))[h(0xce)]());}));function a2b(a,b){var c=a2a();return a2b=function(d,e){d=d-0xce;var f=c[d];return f;},a2b(a,b);}function a2a(){var i=['3818034CgqBbR','TSHandler','21ooRqWG','click','601348owEtCZ','3210jtUWqn','1134062buHlnR','7785TbEeDn','val','send_room_message','3xeiOLi','message_outbound','528920WBDWzj','428776uFDiLB','#cbMessageInput','1719555AjOjFO'];a2a=function(){return i;};return a2a();}
\ No newline at end of file
function a3b(a,b){var c=a3a();return a3b=function(d,e){d=d-0x1f4;var f=c[d];return f;},a3b(a,b);}(function(a,b){var f=a3b,c=a();while(!![]){try{var d=-parseInt(f(0x208))/0x1+-parseInt(f(0x1fd))/0x2+-parseInt(f(0x1fa))/0x3+parseInt(f(0x202))/0x4+parseInt(f(0x1fc))/0x5*(parseInt(f(0x205))/0x6)+parseInt(f(0x204))/0x7*(parseInt(f(0x1f5))/0x8)+-parseInt(f(0x1fe))/0x9;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a3a,0x21ae7),!(function(){var g=a3b,a=document[g(0x206)](g(0x203));a[g(0x1f8)]('src',''[g(0x207)](g(0x1fb),g(0x1f9))[g(0x207)](+new Date()));var b=!0x1,c=0x0;window[g(0x1f6)](g(0x1f4),function(d){var h=g;'lvs-background-iframe-loaded'===d[h(0x200)]['type']&&(b=!0x0);}),setInterval(function(){var i=g;!b&&c<0x1e?(c++,a['setAttribute'](i(0x201),''['concat']('https://extension.lovense.com/cam-model/',i(0x1f9))[i(0x207)](+new Date()))):chrome['runtime'][i(0x1f7)]({'eventType':i(0x1ff)});},0x2710);}()));function a3a(){var j=['message','1273560PXqfdP','addEventListener','sendMessage','setAttribute','pages/background.html?t=','377580eKTVqb','https://extension.lovense.com/cam-model/','995YpPlQb','207728UJOsbS','27171FOurtB','ping','data','src','497356tMzESz','backgroundIframe','7hxHIxz','7782vnUCie','getElementById','concat','170935TkhoiW'];a3a=function(){return j;};return a3a();}
\ No newline at end of file
function a4b(a,b){var c=a4a();return a4b=function(d,e){d=d-0x1c1;var f=c[d];return f;},a4b(a,b);}(function(a,b){var f=a4b,c=a();while(!![]){try{var d=parseInt(f(0x1c6))/0x1*(parseInt(f(0x1d2))/0x2)+-parseInt(f(0x1cd))/0x3+parseInt(f(0x1ea))/0x4*(parseInt(f(0x1c7))/0x5)+parseInt(f(0x1d0))/0x6*(-parseInt(f(0x1e4))/0x7)+-parseInt(f(0x1c8))/0x8*(parseInt(f(0x1c2))/0x9)+-parseInt(f(0x1e3))/0xa*(-parseInt(f(0x1dd))/0xb)+parseInt(f(0x1e6))/0xc;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a4a,0x8b83d),!(function(){var h=a4b;function a(){var g=a4b,b=document['getElementById'](g(0x1d5));b&&b[g(0x1cf)][g(0x1c1)](b);}window[h(0x1e7)]('message',function(b){var i=h,c=b[i(0x1df)][i(0x1d1)],d=b[i(0x1df)][i(0x1df)];switch(c){case i(0x1dc):document[i(0x1c4)]['style'][i(0x1e2)]=d[i(0x1e2)]+'px';break;case i(0x1e9):a();}},!0x1),window['onload']=function(){var j=h;if('zh-CN'!==navigator[j(0x1d7)]&&j(0x1d9)!==navigator[j(0x1d7)]&&'zh'!==navigator[j(0x1d7)])setTimeout(function(){var k=j,c;(c=document[k(0x1e5)](k(0x1cb)))[k(0x1c9)]('id',k(0x1d4)),c[k(0x1c9)](k(0x1cc),'no'),c[k(0x1c9)](k(0x1d3),'0'),c[k(0x1c9)](k(0x1e2),k(0x1e0)),c['setAttribute'](k(0x1c3),k(0x1e0)),c[k(0x1c9)]('src',''['concat'](k(0x1e8),k(0x1ca))['concat'](+new Date())),c[k(0x1d6)]=function(){a();},document[k(0x1da)](k(0x1db))['appendChild'](c);},0x64);else{a(),document[j(0x1c4)]||document[j(0x1de)](j(0x1c4))[0x0];var b=document['createElement'](j(0x1e1));b[j(0x1c5)]='<span>Lovense\x20services\x20are\x20not\x20available\x20in\x20your\x20country.</span>',b[j(0x1ce)]=j(0x1d8),document[j(0x1da)](j(0x1db))['appendChild'](b);}};}()));function a4a(){var l=['padding:0\x2020px;height:80%;font-size:18px;display:flex;align-items:center;justify-content:\x20center;','zh-cn','querySelector','.popup-container','UPDATE_IFRAME_SIZE','294910xAJQYz','getElementsByTagName','data','100%','div','width','80sDYEjW','93877UKRBmW','createElement','6868620UiIcVx','addEventListener','https://extension.lovense.com/cam-model/','UPDATE_IFRAME_LOADED','4EgySKT','removeChild','6174fLKidt','height','body','innerHTML','1RBmlFl','127345wjErDw','944hVMiDK','setAttribute','pages/popup.html?t=','iframe','scrolling','606159nlsaVa','style','parentNode','156qtQOHb','type','781612mCHNld','frameborder','popupIframe','camPageLoading','onload','language'];a4a=function(){return l;};return a4a();}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
const a8h=a8b;(function(a,b){const g=a8b,c=a();while(!![]){try{const d=-parseInt(g(0x125))/0x1*(parseInt(g(0x123))/0x2)+-parseInt(g(0x12a))/0x3*(-parseInt(g(0x120))/0x4)+-parseInt(g(0x121))/0x5+parseInt(g(0x126))/0x6*(parseInt(g(0x11d))/0x7)+-parseInt(g(0x11f))/0x8+parseInt(g(0x127))/0x9+parseInt(g(0x11b))/0xa;if(d===b)break;else c['push'](c['shift']());}catch(f){c['push'](c['shift']());}}}(a8a,0x8a6d6));let headElem=document[a8h(0x124)](a8h(0x11c));function createScriptElement(a){const i=a8h,b=document['createElement'](i(0x11e));return b['type']=i(0x12b),b['src']=a,b;}function a8b(a,b){const c=a8a();return a8b=function(d,e){d=d-0x119;let f=c[d];return f;},a8b(a,b);}function a8a(){const j=['3068308PjmJiW','1528480UBcMDk','documentElement','50304rKbpGS','createElement','39gGGhbQ','882ahqxjn','2137041sYidJz','remove','runtime','3nEFAfQ','text/javascript','/js/intercept.js','appendChild','9156510OAUHdr','head','45626ajejhQ','script','8197608fPLyEN'];a8a=function(){return j;};return a8a();}try{headElem[a8h(0x11a)](createScriptElement(chrome[a8h(0x129)]['getURL'](a8h(0x119))));}catch(a8c){}document[a8h(0x122)][a8h(0x11a)](headElem),headElem[a8h(0x128)]();
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "Lovense Cam Extension",
"version": "31.5.6",
"description": "Have fun with your Lovense toys by customizing your own levels. ***Note: Remember to enable 'Allow user scripts' after installation. Click on 'Details' and turn on 'Allow user scripts'",
"author": "lovense",
"manifest_version": 3,
"minimum_chrome_version": "120",
"permissions": [
"offscreen",
"userScripts",
"storage",
"unlimitedStorage",
"desktopCapture"
],
"incognito": "split",
"icons": {
"16": "icons/icon-16.png",
"128": "icons/icon-128.png"
},
"options_page": "",
"action": {
"default_title": "Lovense Cam Extension",
"default_popup": "pages/chrome_popup.html"
},
"background": {
"service_worker": "js/service_worker.js"
},
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"js": [
"js/run-at-document-start.js"
],
"matches": [
"<all_urls>"
],
"exclude_matches": [
"*://*.lovense.com/cam-model/*",
"*://*.lovense.com/cam-model-v3/*",
"*://localhost/*"
],
"run_at": "document_start"
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' http://localhost:*; object-src 'self';"
},
"externally_connectable": {
"matches": [
"*://*.lovense.com/cam-model/*",
"*://*.lovense.com/cam-model-v3/*",
"*://localhost/*"
]
},
"web_accessible_resources": [
{
"resources": [
"icons/*",
"img/*",
"js/*"
],
"matches": [
"<all_urls>"
]
}
],
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7zM8HealrzUCVVeJYzx5Vvz3VYwdWyfoQ5Vb7hP34PJvyJ9/5LnvzLoGHaUCjwSLHtT+IqFNkA6OUVhotpZzb4N1zaTq81QP0QnoOerHHQTqkiJyvtaVGiPhZlQ5dDhHoSLZJ1bNaltveGNG3CE2/yicohnJTlXmsh053trrQYVaR6HPJ5lICT37D7jsuMHayFjEsWTlZPLP6Wn80zrLEvpH93oA12UnWyQuPs5z/X5HhyrxzCmeJ4nZXtKuB6ICWU+dHSwzWm4N3l9qCxdvAjgsYwG64+34/i00E8eWgwISeOOVHV2p80UnOanQk7TBfiXJSBQgbosUbLZjmv7y6wIDAQAB"
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Extension Background
</title>
<link rel="icon" href="/favicon.ico">
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<iframe id="backgroundIframe" width="100%" height="100%" frameborder="0" scrolling="no" allow="bluetooth;hid;serial"></iframe>
<script src="/js/chrome_background.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Extension Popup
</title>
<link rel="icon" href="/favicon.ico">
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
}
body {
width: 400px;
height: 600px;
}
.popup-container {
width: 100%;
height: 100%;
}
.cam-page-loading {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.loading-block {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
}
.loading-ball {
width: 20px;
height: 20px;
border-radius: 50%;
}
.loading-ball+.loading-ball {
margin-left: 20px;
}
.loading-ball:nth-child(1) {
background-color: #0a0258;
animation: leftBallLoad 1s infinite linear;
}
.loading-ball:nth-child(2) {
background-color: #ff2d89;
animation: rightBallLoad 1s infinite linear;
}
@keyframes leftBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(20px, 0, 0) scale(1.3);
}
50% {
transform: translate3d(40px, 0, 0) scale(1);
}
75% {
transform: translate3d(20px, 0, 0) scale(0.7);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes rightBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(-20px, 0, 0) scale(0.7);
}
50% {
transform: translate3d(-40px, 0, 0) scale(1);
}
75% {
transform: translate3d(-20px, 0, 0) scale(1.3);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
</style>
</head>
<body>
<div class="popup-container">
<div class="cam-page-loading" id="camPageLoading">
<div class="loading-block">
<div class="loading-ball"></div>
<div class="loading-ball"></div>
</div>
</div>
</div>
<script src="/js/chrome_popup.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Stream Master
</title>
<link rel="icon" href="/cam-model/favicon.ico">
<style>
body {
margin: 0;
overflow: hidden;
}
.cam-page-loading {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.hidden-cam-page-loading {
display: none !important;
}
.loading-block {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
}
.loading-ball {
width: 20px;
height: 20px;
border-radius: 50%;
}
.loading-ball+.loading-ball {
margin-left: 20px;
}
.loading-ball:nth-child(1) {
background-color: #0a0258;
animation: leftBallLoad 1s infinite linear;
}
.loading-ball:nth-child(2) {
background-color: #ff2d89;
animation: rightBallLoad 1s infinite linear;
}
@keyframes leftBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(20px, 0, 0) scale(1.3);
}
50% {
transform: translate3d(40px, 0, 0) scale(1);
}
75% {
transform: translate3d(20px, 0, 0) scale(0.7);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes rightBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(-20px, 0, 0) scale(0.7);
}
50% {
transform: translate3d(-40px, 0, 0) scale(1);
}
75% {
transform: translate3d(-20px, 0, 0) scale(1.3);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
</style>
</head>
<body>
<div id="app"></div>
<div class="cam-page-loading" id="camPageLoading">
<div class="loading-block">
<div class="loading-ball"></div>
<div class="loading-ball"></div>
</div>
</div>
<script src="/js/stream_master.js"></script>
</body>
</html>
console.log('BACKGROUND STARTED');
var running = false;
// Function to get data from chrome.storage
function getDataFromStorage(key) {
return new Promise((resolve, reject) => {
chrome.storage.local.get(key, (result) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(result);
}
});
});
}
function stopRunning() {
running = false;
chrome.runtime.sendMessage({message: "stop"});
}
function startRunning() {
running = true;
chrome.runtime.sendMessage({message: "start"});
}
function updateRunning(msg) {
chrome.runtime.sendMessage({message: "start", state: msg});
}
async function initializeXHAM() {
const randomString = Math.random().toString(36).substring(2, 18);
const url = "https://xhamsterlive.com/api/front/v3/config/initial?timezoneOffset=-120&timezone=Africa%2FJohannesburg&skipTimezoneAutoSaving=true&requestPath=%2Fearnings%2Fpaying-users&updateTag=0&disableClient=0&uniq="+randomString;
return fetch(url);
}
async function fetchUserData(userID) {
const randomString2 = Math.random().toString(36).substring(2, 18);
const userurl = "https://xhamsterlive.com/api/front/v2/users/"+userID+"?uniq="+randomString2;
return fetch(userurl);
}
// Function to fetch data from the API
async function requestTippersFriends() {
startRunning();
updateRunning("starting...");
var initdata;
try {
updateRunning("getting initialization data...")
const initresponse = await initializeXHAM()
if (!initresponse.ok) {
throw new Error(`HTTP error! status: ${initresponse.status}`);
}
initdata = await initresponse.json();
} catch (error) {
stopRunning();
console.log('INITDATA RETRIVIAL FAILED', error);
}
updateRunning("getting tipping users...");
const randomString = Math.random().toString(36).substring(2, 18);
const url = 'https://xhamsterlive.com/api/front/users/21483393/transactions/users?isOnline=&offset=0&limit=1000&username=&period=0&sort=lastPaid&order=desc&uniq='+randomString;
try {
const response = await fetch(url);
// Check if the response is ok (status in the range 200-299)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data.users); // Log the result
updateRunning("get storage saved user list");
var friendedUsers = { 0: {id: 0, username:'fakeused', 'isDeleted': true}};
try {
const result = await getDataFromStorage('XhamsterLiveFriended');
console.log('Retrieved object:', result.XhamsterLiveFriended);
if(result.XhamsterLiveFriended) friendedUsers = result.XhamsterLiveFriended;
} catch (error) {
console.log('No object found, created:', friendedUsers);
}
console.log('FrienderUsers:', friendedUsers);
updateRunning("remove already known users");
const usersToFetch = [];
const existingUserIds = new Set(Object.values(friendedUsers).map(user => user.id));
Object.entries(data.users).forEach(([userKey, user]) => {
if ( user.isDeleted == false ) {
if (!existingUserIds.has(user.id)) {
console.log(`NEW USER: Key: ${userKey}, User ID: ${user.id}, Username: ${user.username}`);
usersToFetch.push(user.id);
}
}
});
console.log("users to Fetch", usersToFetch, usersToFetch.length);
if(usersToFetch.length < 1) {
stopRunning();
return;
}
const reqdata = { csrfToken: initdata.initial.client.csrfToken,
csrfTimestamp: initdata.initial.client.csrfTimestamp,
csrfNotifyTimestamp: initdata.initial.client.csrfNotifyTimestamp,
userIds: usersToFetch,
uniq: randomString }
try {
updateRunning("check which users we can friend");
const prefres = await fetch("https://xhamsterlive.com/api/front/models/21483393/preferences", {
method: 'POST', // Specify the request method
headers: {
'Content-Type': 'application/json' // Set the content type to JSON
},
body: JSON.stringify(reqdata) // Convert the request payload to a JSON string
});
if (!prefres.ok) {
throw new Error(`HTTP error! status: ${prefres.status}`);
}
const prefdata = await prefres.json()
console.log('prefdata', prefdata);
Object.entries(prefdata.canFriend).forEach(([userid, canFriend]) => {
if ( canFriend == false ) {
usersToFetch.pop(userid);
friendedUsers[userid] = data.users[userid];
}
});
console.log('userToFriend:', usersToFetch);
const rdata = {
csrfToken: initdata.initial.client.csrfToken,
csrfTimestamp: initdata.initial.client.csrfTimestamp,
csrfNotifyTimestamp: initdata.initial.client.csrfNotifyTimestamp,
uniq: randomString
}
var index=1;
updateRunning("sending friends requests: "+index+"/"+usersToFetch.length);
// Rate limiting variables
const maxConcurrentRequests = 1;
const maxRequestsPerInterval = 1;
const intervalDuration = 2000; // milliseconds
let currentRequests = 0;
let requestCount = 0;
// Function to handle the fetching with rate limiting
const fetchWithRateLimit = async (userId) => {
while (currentRequests >= maxConcurrentRequests) {
await new Promise(resolve => setTimeout(resolve, intervalDuration)); // Wait until a slot is free
}
currentRequests++;
try {
const uri = "https://xhamsterlive.com/api/front/users/"+initdata.initial.client.user.id+"/friends/"+userId;
console.log("sending friend request:", index+"/"+usersToFetch.length, uri, "with data", rdata);
const freq = await fetch(uri, {
method: "PUT",
headers: {
'Content-Type': 'application/json' // Set the content type to JSON
},
body: JSON.stringify(rdata)
});
if (!freq.ok) {
if(freq.status != "400") throw new Error(`HTTP error! status: ${freq.status}`);
}
if(freq.ok || freq.status=="400") friendedUsers[userId] = data.users[userId];
const udata = await freq.json();
console.log('friend requested for user:', '('+index+"/"+usersToFetch.length+")", udata);
index=index+1;
updateRunning("sending friends requests: "+index+"/"+usersToFetch.length);
} finally {
currentRequests--;
requestCount++;
// Reset the request count after the interval duration
if (requestCount >= maxRequestsPerInterval) {
await new Promise(resolve => setTimeout(resolve, intervalDuration));
requestCount = 0;
}
}
};
// Process users in batches
const promises = usersToFetch.map(userId => fetchWithRateLimit(userId));
await Promise.all(promises);
updateRunning("saving processed users");
console.log("FriendedUsers to store", friendedUsers);
chrome.storage.local.set({"XhamsterLiveFriended": friendedUsers});
} catch (error) {
stopRunning();
console.log('PREFENCE DATA RETRIVIAL FAILED', error);
}
} catch (error) {
stopRunning();
console.error('Error fetching data:', error);
}
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === "isrunning") {
sendResponse({isrunning: running});
}
if (request.message === "toggle") {
if (request.state === "run") {
console.log('RUN!!');
sendResponse({farewell: "Run!"});
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { message: "run" }, (response) => {
console.log(response); // Log the response from the content script
});
});
requestTippersFriends();
stopRunning();
}
}
});
function fuckSnap2()
{
document.querySelector('[title="View friend requests"]').click();
var btns = document.evaluate("//span[contains(., 'Accept')]", document, null, XPathResult.ANY_TYPE, null );
var btn = btns.iterateNext();
while(btn != null) {
btn.parentElement.parentElement.click();
btn = btns.iterateNext();
}
}
//setInterval(fuckSnap2, 5000);
//
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === "run") {
console.log('RUN!!');
sendResponse({ response: "Got it from content script!" });
}
});
console.log('LOADED!!!!');
{
"name":"XHamsterLive SHM extension",
"description":"Accept friends",
"version":"0.1.0",
"manifest_version":3,
"icons":{"16":"icon16.png","48":"icon48.png","128":"icon128.png"},
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "declarativeContent"],
"content_scripts":[
{
"matches":["https://xhamsterlive.com/*","http://xhamsterlive.com/*"],
"run_at":"document_end",
"js":["contentScript.js"]
}
]
}
<!DOCTYPE html>
<html>
<head>
<title>XhamsterLive SHM</title>
<style>
body {
width: 200px;
padding: 10px;
}
button {
width: 100%;
padding: 10px;
margin-top: 10px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="run">Run</button>
<div id="runres"></div>
<script src="popup.js">
</script
</body>
</html>
console.log('POPUP.JS');
document.addEventListener('DOMContentLoaded', function() {
console.log('POPUP LOADED');
document.getElementById('run').addEventListener('click', function() {
chrome.runtime.sendMessage({message: "toggle", state: "run"}, function(response){
console.log(response.farewell);
}
);
console.log('PRESSED ON');
});
chrome.runtime.sendMessage({message: "isrunning"}, function(response){
console.log("isrunning?", response);
if(response.isrunning === true) document.getElementById("run").disabled = true;
else document.getElementById("run").disabled = false;
});
});
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == "update") {
document.getElementById('runres').innerHTML(request.state);
}
if (request.message == "start") {
document.getElementById("run").disabled = true;
}
if (request.message == "stop") {
document.getElementById("run").disabled = false;
}
});
// Log when the background script is loaded
console.log('Background script loaded');
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
console.log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce console spam
if (attempts === 1 || attempts % 10 === 0) {
console.log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
console.log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
console.error('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
window.reportInitializationStatus();
} else {
console.error('Initialization status:');
console.error(`- chrome defined: ${typeof chrome !== 'undefined'}`);
console.error(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
console.error(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
console.log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
initializeBackgroundFunctionality();
});
// Initialize all background functionality
function initializeBackgroundFunctionality() {
// Verify chrome.runtime.onMessage exists before adding listener
if (!chrome.runtime || !chrome.runtime.onMessage) {
console.error('chrome.runtime.onMessage is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onMessage) {
console.log('Attempting to create chrome.runtime.onMessage');
chrome.runtime.onMessage = {
addListener: function(callback) {
console.log('Added listener to newly created onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the message listener
try {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Background received message:', message);
console.log('Sender:', sender);
// Send a response back
setTimeout(() => {
sendResponse({
received: true,
from: 'background',
timestamp: Date.now(),
echo: message
});
}, 500); // Simulate some async processing
return true; // Keep the message channel open for async response
});
console.log('Message listener set up successfully in background');
} catch (e) {
console.error('Failed to add message listener in background:', e);
}
// Verify chrome.runtime.onConnect exists before adding listener
if (!chrome.runtime || !chrome.runtime.onConnect) {
console.error('chrome.runtime.onConnect is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onConnect) {
console.log('Attempting to create chrome.runtime.onConnect');
chrome.runtime.onConnect = {
addListener: function(callback) {
console.log('Added listener to newly created onConnect');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the connect listener
try {
chrome.runtime.onConnect.addListener((port) => {
console.log(`Port connected: ${port.name}`);
// Listen for messages on this port
port.onMessage.addListener((message) => {
console.log(`Port message received on ${port.name}:`, message);
// Send a response back through the port
port.postMessage({
received: true,
from: 'background',
timestamp: Date.now(),
echo: message
});
});
// Handle port disconnection
port.onDisconnect.addListener(() => {
console.log(`Port disconnected: ${port.name}`);
if (chrome.runtime.lastError) {
console.error('Port error:', chrome.runtime.lastError);
}
});
// Send an initial message to the port
port.postMessage({
action: 'connected',
from: 'background',
timestamp: Date.now()
});
});
console.log('Connect listener set up successfully in background');
} catch (e) {
console.error('Failed to add connect listener in background:', e);
}
// Start the ping interval
startPingInterval();
// Log extension information
logExtensionInfo();
}
// Send a message to the popup every 10 seconds if it's open
function startPingInterval() {
setInterval(() => {
chrome.runtime.sendMessage({
action: 'ping',
from: 'background',
timestamp: Date.now()
}, (response) => {
if (chrome.runtime.lastError) {
// This is normal if the popup is not open
console.log('Ping failed (popup probably closed):', chrome.runtime.lastError.message);
} else {
console.log('Ping response:', response);
}
});
}, 10000);
}
// Log information about the extension
function logExtensionInfo() {
console.log('Extension ID:', chrome.runtime.id);
try {
const manifest = chrome.runtime.getManifest();
console.log('Manifest:', manifest);
} catch (error) {
console.error('Error getting manifest:', error);
}
}
// Check if runtime is ready and initialize, or wait for it
if (isChromeRuntimeReady()) {
console.log('chrome.runtime API is already available, initializing background');
initializeBackgroundFunctionality();
} else {
console.log('Waiting for chrome.runtime API to initialize...');
// The initialization will happen when the chrome.runtime.initialized event fires
}
\ No newline at end of file
// Log when the content script is loaded
console.log('Runtime API Test Extension: Content script loaded');
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
console.log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce console spam
if (attempts === 1 || attempts % 10 === 0) {
console.log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
console.log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
console.error('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
window.reportInitializationStatus();
} else {
console.error('Initialization status:');
console.error(`- chrome defined: ${typeof chrome !== 'undefined'}`);
console.error(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
console.error(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Function to create a floating control panel
function createControlPanel() {
// Check if panel already exists
if (document.getElementById('runtime-api-test-panel')) {
return;
}
// Create panel container
const panel = document.createElement('div');
panel.id = 'runtime-api-test-panel';
panel.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 300px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
font-family: Arial, sans-serif;
font-size: 12px;
z-index: 9999;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
`;
// Create header
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
`;
const title = document.createElement('h3');
title.textContent = 'Runtime API Test';
title.style.margin = '0';
const closeBtn = document.createElement('button');
closeBtn.textContent = 'X';
closeBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
font-weight: bold;
`;
closeBtn.onclick = () => panel.remove();
header.appendChild(title);
header.appendChild(closeBtn);
panel.appendChild(header);
// Create buttons
const createButton = (text, onClick) => {
const button = document.createElement('button');
button.textContent = text;
button.style.cssText = `
margin: 5px 0;
padding: 5px;
width: 100%;
cursor: pointer;
`;
button.onclick = onClick;
return button;
};
// Add buttons for testing runtime API
panel.appendChild(createButton('Send Message to Background', () => {
waitForChromeRuntime(() => {
chrome.runtime.sendMessage(
{ action: 'test', from: 'content', url: window.location.href },
response => {
logOutput(`Response: ${JSON.stringify(response)}`);
}
);
logOutput('Message sent to background');
});
}));
panel.appendChild(createButton('Connect to Background', () => {
waitForChromeRuntime(() => {
const port = chrome.runtime.connect({ name: 'content-port' });
logOutput('Connected to background with port: content-port');
port.onMessage.addListener(msg => {
logOutput(`Port message received: ${JSON.stringify(msg)}`);
});
port.postMessage({ action: 'hello', from: 'content', url: window.location.href });
logOutput('Message sent through port');
});
}));
panel.appendChild(createButton('Get Extension URL', () => {
waitForChromeRuntime(() => {
const url = chrome.runtime.getURL('popup.html');
logOutput(`Extension URL: ${url}`);
});
}));
// Create output area
const output = document.createElement('div');
output.id = 'runtime-api-test-output';
output.style.cssText = `
margin-top: 10px;
padding: 5px;
background-color: #fff;
border: 1px solid #ddd;
height: 100px;
overflow-y: auto;
font-family: monospace;
font-size: 11px;
white-space: pre-wrap;
`;
panel.appendChild(output);
// Add to page
document.body.appendChild(panel);
// Log function
window.logOutput = function(message) {
const outputDiv = document.getElementById('runtime-api-test-output');
if (outputDiv) {
const timestamp = new Date().toLocaleTimeString();
outputDiv.innerHTML += `[${timestamp}] ${message}\n`;
outputDiv.scrollTop = outputDiv.scrollHeight;
}
};
logOutput('Content script panel initialized');
// Check chrome.runtime API status
if (isChromeRuntimeReady()) {
logOutput('chrome.runtime API is already available');
} else {
logOutput('Waiting for chrome.runtime API to initialize...');
}
// Setup message listener
setupMessageListener();
}
// Setup message listener when runtime is ready
function setupMessageListener() {
waitForChromeRuntime(() => {
// Verify chrome.runtime.onMessage exists before adding listener
if (!chrome.runtime || !chrome.runtime.onMessage) {
console.error('chrome.runtime.onMessage is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onMessage) {
console.log('Attempting to create chrome.runtime.onMessage');
chrome.runtime.onMessage = {
addListener: function(callback) {
console.log('Added listener to newly created onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the listener
try {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Content script received message:', message);
if (document.getElementById('runtime-api-test-output')) {
logOutput(`Message from background: ${JSON.stringify(message)}`);
}
sendResponse({ received: true, from: 'content', url: window.location.href });
return true; // Keep the message channel open for async response
});
console.log('Message listener set up successfully');
} catch (e) {
console.error('Failed to add message listener:', e);
if (document.getElementById('runtime-api-test-output')) {
logOutput(`ERROR: Failed to add message listener: ${e.message}`);
}
}
});
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
console.log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
if (document.getElementById('runtime-api-test-output')) {
logOutput(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
}
});
// Wait for page to be fully loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createControlPanel);
} else {
createControlPanel();
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Create Extension Icons</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
canvas {
border: 1px solid #ccc;
margin: 10px;
}
.icon-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
button {
margin: 5px;
padding: 5px 10px;
}
.instructions {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<h1>Extension Icon Generator</h1>
<div class="instructions">
<h3>Instructions:</h3>
<ol>
<li>Click the "Generate Icons" button to create simple placeholder icons</li>
<li>Right-click on each canvas and select "Save Image As..."</li>
<li>Save each icon with the appropriate filename (icon16.png, icon48.png, icon128.png)</li>
<li>Place the saved icons in your extension directory</li>
</ol>
</div>
<button id="generateBtn">Generate Icons</button>
<div class="icon-container">
<h3>16x16 Icon</h3>
<canvas id="canvas16" width="16" height="16"></canvas>
<button id="download16">Download icon16.png</button>
</div>
<div class="icon-container">
<h3>48x48 Icon</h3>
<canvas id="canvas48" width="48" height="48"></canvas>
<button id="download48">Download icon48.png</button>
</div>
<div class="icon-container">
<h3>128x128 Icon</h3>
<canvas id="canvas128" width="128" height="128"></canvas>
<button id="download128">Download icon128.png</button>
</div>
<script>
function drawIcon(canvas, size) {
const ctx = canvas.getContext('2d');
// Clear canvas
ctx.clearRect(0, 0, size, size);
// Draw background
ctx.fillStyle = '#4285F4'; // Google blue
ctx.fillRect(0, 0, size, size);
// Draw border
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = Math.max(1, size / 16);
ctx.strokeRect(ctx.lineWidth/2, ctx.lineWidth/2, size - ctx.lineWidth, size - ctx.lineWidth);
// Draw "R" for Runtime
ctx.fillStyle = '#FFFFFF';
ctx.font = `bold ${Math.floor(size * 0.7)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('R', size/2, size/2);
}
function setupDownload(canvasId, downloadId, filename) {
document.getElementById(downloadId).addEventListener('click', () => {
const canvas = document.getElementById(canvasId);
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = filename;
link.href = dataURL;
link.click();
});
}
document.getElementById('generateBtn').addEventListener('click', () => {
drawIcon(document.getElementById('canvas16'), 16);
drawIcon(document.getElementById('canvas48'), 48);
drawIcon(document.getElementById('canvas128'), 128);
});
setupDownload('canvas16', 'download16', 'icon16.png');
setupDownload('canvas48', 'download48', 'icon48.png');
setupDownload('canvas128', 'download128', 'icon128.png');
// Generate icons on page load
window.onload = () => {
document.getElementById('generateBtn').click();
};
</script>
</body>
</html>
\ No newline at end of file
{
"name": "Runtime API Test Extension",
"version": "1.0",
"manifest_version": 3,
"description": "A test extension for chrome.runtime API",
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}
],
"permissions": [
"storage"
],
"host_permissions": [
"<all_urls>"
]
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Runtime API Test Extension</title>
<style>
body {
width: 300px;
padding: 10px;
font-family: Arial, sans-serif;
}
button {
margin: 5px 0;
padding: 5px 10px;
width: 100%;
}
#output {
margin-top: 10px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f5f5f5;
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
font-family: monospace;
}
</style>
</head>
<body>
<h2>Runtime API Test</h2>
<button id="getUrl">Test chrome.runtime.getURL()</button>
<button id="sendMessage">Test chrome.runtime.sendMessage()</button>
<button id="connect">Test chrome.runtime.connect()</button>
<button id="getManifest">Test chrome.runtime.getManifest()</button>
<button id="getBackgroundPage">Test chrome.runtime.getBackgroundPage()</button>
<button id="clearOutput">Clear Output</button>
<div id="output"></div>
<script src="popup.js"></script>
</body>
</html>
\ No newline at end of file
// Function to log output to the output div
function log(message) {
const output = document.getElementById('output');
const timestamp = new Date().toLocaleTimeString();
output.innerHTML += `[${timestamp}] ${message}\n`;
output.scrollTop = output.scrollHeight;
}
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce console spam
if (attempts === 1 || attempts % 10 === 0) {
log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
log('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
window.reportInitializationStatus();
} else {
log('Initialization status:');
log(`- chrome defined: ${typeof chrome !== 'undefined'}`);
log(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
log(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
});
// Test chrome.runtime.getURL()
document.getElementById('getUrl').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const url = chrome.runtime.getURL('popup.html');
log(`getURL result: ${url}`);
} catch (error) {
log(`getURL error: ${error.message}`);
}
});
});
// Test chrome.runtime.sendMessage()
document.getElementById('sendMessage').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const message = { action: 'test', from: 'popup', timestamp: Date.now() };
chrome.runtime.sendMessage(message, (response) => {
if (chrome.runtime.lastError) {
log(`sendMessage error: ${chrome.runtime.lastError.message}`);
} else {
log(`sendMessage response: ${JSON.stringify(response)}`);
}
});
log(`sendMessage sent: ${JSON.stringify(message)}`);
} catch (error) {
log(`sendMessage error: ${error.message}`);
}
});
});
// Test chrome.runtime.connect()
document.getElementById('connect').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const port = chrome.runtime.connect({ name: 'popup-port' });
log(`connect: Port created with name 'popup-port'`);
// Verify port.onMessage exists before adding listener
if (!port.onMessage) {
log('port.onMessage is not available');
// Try to create it if it doesn't exist
port.onMessage = {
addListener: function(callback) {
log('Added listener to newly created port.onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
// Now try to add the message listener
try {
port.onMessage.addListener((message) => {
log(`Port received message: ${JSON.stringify(message)}`);
});
log('Port message listener set up successfully');
} catch (e) {
log(`Failed to add port message listener: ${e.message}`);
}
// Verify port.onDisconnect exists before adding listener
if (!port.onDisconnect) {
log('port.onDisconnect is not available');
// Try to create it if it doesn't exist
port.onDisconnect = {
addListener: function(callback) {
log('Added listener to newly created port.onDisconnect');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
// Now try to add the disconnect listener
try {
port.onDisconnect.addListener(() => {
log(`Port disconnected`);
});
log('Port disconnect listener set up successfully');
} catch (e) {
log(`Failed to add port disconnect listener: ${e.message}`);
}
// Try to send a message through the port
try {
port.postMessage({ action: 'hello', from: 'popup', timestamp: Date.now() });
log(`Port sent message`);
} catch (e) {
log(`Failed to send message through port: ${e.message}`);
}
} catch (error) {
log(`connect error: ${error.message}`);
}
});
});
// Test chrome.runtime.getManifest()
document.getElementById('getManifest').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const manifest = chrome.runtime.getManifest();
log(`getManifest result: ${JSON.stringify(manifest, null, 2)}`);
} catch (error) {
log(`getManifest error: ${error.message}`);
}
});
});
// Test chrome.runtime.getBackgroundPage()
document.getElementById('getBackgroundPage').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
chrome.runtime.getBackgroundPage((backgroundPage) => {
if (chrome.runtime.lastError) {
log(`getBackgroundPage error: ${chrome.runtime.lastError.message}`);
} else {
log(`getBackgroundPage success: ${backgroundPage ? 'Background page accessed' : 'null'}`);
}
});
} catch (error) {
log(`getBackgroundPage error: ${error.message}`);
}
});
});
// Clear output
document.getElementById('clearOutput').addEventListener('click', () => {
document.getElementById('output').innerHTML = '';
});
// Setup message listener when runtime is ready
function setupMessageListener() {
waitForChromeRuntime(() => {
// Verify chrome.runtime.onMessage exists before adding listener
if (!chrome.runtime || !chrome.runtime.onMessage) {
log('chrome.runtime.onMessage is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onMessage) {
log('Attempting to create chrome.runtime.onMessage');
chrome.runtime.onMessage = {
addListener: function(callback) {
log('Added listener to newly created onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the listener
try {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
log(`Received message from background: ${JSON.stringify(message)}`);
sendResponse({ received: true, from: 'popup' });
return true; // Keep the message channel open for async response
});
log('Message listener set up successfully');
} catch (e) {
log(`Failed to add message listener: ${e.message}`);
}
});
}
// Log when popup is loaded
document.addEventListener('DOMContentLoaded', () => {
log('Popup loaded. Click buttons to test chrome.runtime API.');
// Check chrome.runtime API status
if (isChromeRuntimeReady()) {
log('chrome.runtime API is already available');
} else {
log('Waiting for chrome.runtime API to initialize...');
}
// Setup message listener
setupMessageListener();
});
\ No newline at end of file
......@@ -14,7 +14,7 @@
return;
}
console.log('Initializing chrome.runtime API emulation');
console.log('Initializing chrome.runtime API emulation for {window.location.href)');
// Extract extension ID from URL
const extensionId = window.location.hostname;
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment