Skip to content

Adding Tab Opening Rate Limit #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -31,14 +31,14 @@ jobs:

- name: "Build"
run: |
npm install
npm run build
#- name: "Upload to Actions"
# uses: actions/upload-artifact@v4
# with:
# name: artifacts
# path: web-ext-artifacts/
- name: "Upload to Actions"
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/upload-artifact@v4
with:
name: artifacts
path: web-ext-artifacts/

- name: "Upload to Release"
if: ${{ github.event_name == 'release' }}
8 changes: 4 additions & 4 deletions .github/workflows/issue.yaml
Original file line number Diff line number Diff line change
@@ -25,6 +25,10 @@ jobs:
with:
node-version: 22

- name: "Install"
run: |
npm install
- name: "Parse Issue"
id: issue
uses: cssnr/parse-issue-form-action@master
@@ -37,10 +41,6 @@ jobs:
echo Details: '${{ steps.issue.outputs.details }}'
echo Support Information: '${{ steps.issue.outputs.support_information }}'
- name: "Install"
run: |
npm install
- name: "Process Issue"
env:
URL: ${{ steps.issue.outputs.site_link }}
7 changes: 5 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -23,6 +23,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
if: ${{ !contains(github.event.head_commit.message, '#notest') }}
permissions:
contents: write
pull-requests: write

steps:
- name: "Checkout"
@@ -55,8 +58,8 @@ jobs:
pass: ${{ secrets.RSYNC_PASS }}
port: ${{ secrets.RSYNC_PORT }}
webhost: "https://artifacts.hosted-domains.com"
webhook: ${{ secrets.DISCORD_WEBHOOK }}
token: ${{ secrets.GITHUB_TOKEN }}
#webhook: ${{ secrets.DISCORD_WEBHOOK }}
#token: ${{ secrets.GITHUB_TOKEN }}

- name: "Schedule Failure Notification"
uses: sarisia/actions-status-discord@v1
11 changes: 10 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# IDE
.idea/
.vscode/

# Tools
.ruff_cache/
.mypy_cache/
.pytest_cache/

# Build
dist/
node_modules/
package-lock.json

# Files
*.html
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -85,7 +85,7 @@ for new features. For any issues, bugs or concerns; please [Open an Issue](https
### Known Issues

See the [Support](#Support) to let us know about issues...
See the [Support](#Support) section for ways to inform us about issues...

For more information see the [FAQ](https://link-extractor.cssnr.com/faq/).

190 changes: 65 additions & 125 deletions package-lock.json
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -9,9 +9,9 @@
"issue": "npm run manifest:test && node tests/issue.js",
"chrome": "npm run manifest:chrome && web-ext run --source-dir ./src/ --target=chromium",
"firefox": "npm run manifest:firefox && web-ext run --source-dir ./src/",
"manifest:chrome": "npx json-merger -p -am concat -o src/manifest.json manifest.json manifest-chrome.json",
"manifest:firefox": "npx json-merger -p -am concat -o src/manifest.json manifest.json manifest-firefox.json",
"manifest:test": "npx json-merger -p -am concat -o src/manifest.json manifest.json tests/manifest-test.json",
"manifest:chrome": "npx json-merger -p --am concat -o src/manifest.json manifest.json manifest-chrome.json",
"manifest:firefox": "npx json-merger -p --am concat -o src/manifest.json manifest.json manifest-firefox.json",
"manifest:test": "npx json-merger -p --am concat -o src/manifest.json manifest.json tests/manifest-test.json",
"build:chrome": "npm run manifest:chrome && npx web-ext build -n {name}-chrome-{version}.zip -o -s src",
"build:firefox": "npm run manifest:firefox && npx web-ext build -n {name}-firefox-{version}.zip -o -s src",
"build": "npm run build:chrome && npm run build:firefox",
@@ -30,12 +30,12 @@
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/chrome": "^0.0.299",
"@types/chrome": "^0.0.307",
"eslint": "^9.21.0",
"gulp": "^5.0.0",
"json-merger": "^2.1.0",
"prettier": "^3.5.2",
"puppeteer": "^22.15.0",
"json-merger": "^3.0.0",
"prettier": "^3.5.3",
"puppeteer": "^24.3.0",
"web-ext": "^8.4.0"
}
}
41 changes: 34 additions & 7 deletions src/html/options.html
Original file line number Diff line number Diff line change
@@ -60,20 +60,15 @@
<form id="options-form">
<div class="row">
<div class="col-12 mb-2">
<label for="flags" class="form-label"><i class="fa-solid fa-code me-2"></i> Regex Flags</label>
<!-- <a id="reset-regex" class="float-end align-bottom small text-decoration-none" role="button"-->
<!-- data-target="flags" data-value="ig" data-reset-input="flags">Reset</a>-->
<!-- <input id="flags" type="text" class="form-control" autocomplete="off">-->
<label for="flags" class="form-label"><i class="fa-solid fa-code me-1"></i> Regex Flags</label>
<i class="fa-solid fa-circle-info p-1" data-bs-toggle="tooltip" data-bs-title="Regular expression flags used when matching links."></i>
<div class="input-group">
<input id="flags" placeholder="Lazy Tab Title" type="text" class="form-control" aria-describedby="flagsHelp">
<span id="reset-regex" class="input-group-text text-warning-emphasis" role="button"
data-bs-title="Reset Regex Flags to Default Value." data-bs-toggle="tooltip"
data-value="ig" data-reset-input="flags">
<i class="fa-solid fa-rotate-left"></i>
</span>
<!-- <span class="input-group-text">-->
<!-- <i class="fa-solid fa-circle-info" data-bs-title="Title for Lazy Loaded Tabs." data-bs-toggle="tooltip"></i>-->
<!-- </span>-->
</div>
<div id="flagsHelp" class="form-text">
Regex Flags for Filtering.
@@ -157,6 +152,38 @@
</div>
</div> <!-- lazyOptions -->

<div class="form-check form-switch">
<input class="form-check-input form-control" type="checkbox" role="switch" id="tabsLimit" data-related="tabsLimitOptions">
<label class="form-check-label" for="tabsLimit">Rate Limit Opened Tabs</label>
<i class="fa-solid fa-circle-info p-1" data-bs-toggle="tooltip" data-bs-placement="bottom"
data-bs-title="Limit the Time Between Opening Tabs."></i>
</div>
<div id="tabsLimitOptions" class="ms-4 mb-1" style="display: none;">
<div class="row">
<div class="col-sm-6 col-12 mb-2">
<label for="tabsRate" class="form-label"><i class="fa-solid fa-clock-rotate-left me-1"></i> Tab Rate Limit</label>
<i class="fa-solid fa-circle-info p-1" data-bs-toggle="tooltip" data-bs-title="When opening multiple tabs, this delay will be applied between each one."></i>
<div class="input-group">
<input id="tabsRate" aria-describedby="tabsRateHelp" type="number" class="form-control" autocomplete="off"
step="50" min="0" max="5000">
<span class="input-group-text">ms</span>
</div>
<div class="form-text" id="tabsRateHelp">Time Between Opening each Tab.</div>
</div>

<div class="col-sm-6 col-12 mb-2">
<label for="tabsAfter" class="form-label"><i class="fa-solid fa-hashtag me-1"></i> Tab Limit After</label>
<i class="fa-solid fa-circle-info p-1" data-bs-toggle="tooltip" data-bs-title="When opening multiple tabs, this delay will be applied between each one."></i>
<div class="input-group">
<input id="tabsAfter" aria-describedby="tabsAfterHelp" type="number" class="form-control" autocomplete="off"
step="1" min="0" max="1000">
<span class="input-group-text">tabs</span>
</div>
<div class="form-text" id="tabsAfterHelp">Limit After This Many Tabs Opened.</div>
</div>
</div> <!-- row -->
</div> <!-- tabsLimitOptions -->

<div class="form-check form-switch">
<input class="form-check-input form-control" type="checkbox" role="switch" id="removeDuplicates">
<label class="form-check-label" for="removeDuplicates">Remove Duplicate Links</label>
41 changes: 41 additions & 0 deletions src/js/exports.js
Original file line number Diff line number Diff line change
@@ -199,6 +199,17 @@ export async function saveOptions(event) /* NOSONAR */ {
}
} else if (target.type === 'checkbox') {
value = target.checked
} else if (event.target.type === 'number') {
const number = parseFloat(event.target.value)
let min = parseFloat(event.target.min)
let max = parseFloat(event.target.max)
if (!isNaN(number) && number >= min && number <= max) {
event.target.value = number.toString()
value = number
} else {
event.target.value = options[event.target.id]
return
}
} else {
value = target.value
}
@@ -211,6 +222,36 @@ export async function saveOptions(event) /* NOSONAR */ {
}
}

/**
* Open URL
* @function openLinks
* @param {String[]|Object} links
* @param {String} [key]
*/
export async function openLinks(links, key = 'href') {
console.debug('openLinks:', links)
const { options } = await chrome.storage.sync.get(['options'])
// console.debug('options:', options)
let count = 0
for (const link of links) {
if (typeof link === 'object') {
openURL(link[key], options.lazyLoad)
} else {
openURL(link, options.lazyLoad)
}
count += 1
if (
options.tabsLimit &&
options.tabsRate &&
options.tabsAfter <= count
) {
await new Promise((resolve) =>
setTimeout(resolve, options.tabsRate)
)
}
}
}

/**
* Open URL
* @function openURL
9 changes: 5 additions & 4 deletions src/js/links.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// JS for links.html

import { openURL, textFileDownload } from './exports.js'
import { textFileDownload } from './exports.js'

window.addEventListener('keydown', handleKeyboard)
document.addEventListener('DOMContentLoaded', initLinks)
@@ -481,11 +481,12 @@ async function openLinksClick(event) {
const closest = event.target?.closest('button')
const links = getTableLinks(closest?.dataset?.target)
// console.debug('links:', links)
const { options } = await chrome.storage.sync.get(['options'])
if (links) {
links.split('\n').forEach(function (url) {
openURL(url, options.lazyLoad)
const response = await chrome.runtime.sendMessage({
message: 'openLinks',
data: links.split('\n'),
})
console.log('response:', response)
} else {
showToast('No Links to Open.', 'warning')
}
15 changes: 8 additions & 7 deletions src/js/popup.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import {
detectBrowser,
grantPerms,
injectTab,
openURL,
saveOptions,
updateManifest,
updateOptions,
@@ -220,7 +219,6 @@ async function linksForm(event) {
event.preventDefault()
const value = event.target.elements['links-text'].value
// console.debug('value:', value)
const { options } = await chrome.storage.sync.get(['options'])
if (event.submitter.id === 'parse-links') {
const urls = extractURLs(value)
// console.debug('urls:', urls)
@@ -230,16 +228,19 @@ async function linksForm(event) {
} else if (event.submitter.id === 'open-parsed') {
const urls = extractURLs(value)
// console.debug('urls:', urls)
urls.forEach(function (url) {
openURL(url.href, options.lazyLoad)
const response = await chrome.runtime.sendMessage({
message: 'openLinks',
data: urls,
})
console.log('response:', response)
} else if (event.submitter.id === 'open-text') {
let text = value.split(/\s+/).filter((s) => s !== '')
// console.debug('text:', text)
text.forEach(function (url) {
// links without a : get prepended the web extension url by default
openURL(url, options.lazyLoad)
const response = await chrome.runtime.sendMessage({
message: 'openLinks',
data: text,
})
console.log('response:', response)
} else {
console.error('Unknown event.submitter:', event.submitter)
}
24 changes: 23 additions & 1 deletion src/js/service-worker.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// JS Background Service Worker

import { checkPerms, injectTab, githubURL } from './exports.js'
import { checkPerms, injectTab, githubURL, openLinks } from './exports.js'

chrome.runtime.onInstalled.addListener(onInstalled)
chrome.runtime.onStartup.addListener(onStartup)
chrome.contextMenus?.onClicked.addListener(onClicked)
chrome.commands?.onCommand.addListener(onCommand)
chrome.runtime.onMessage.addListener(onMessage)
chrome.storage.onChanged.addListener(onChanged)
chrome.omnibox?.onInputChanged.addListener(onInputChanged)
chrome.omnibox?.onInputEntered.addListener(onInputEntered)
@@ -23,6 +24,9 @@ async function onInstalled(details) {
const { options, patterns } = await setDefaultOptions({
linksDisplay: -1,
flags: 'ig',
tabsLimit: false,
tabsRate: 250,
tabsAfter: 10,
lazyLoad: true,
lazyFavicon: true,
lazyTitle: '[{host}{pathname}]',
@@ -168,6 +172,24 @@ async function onCommand(command, tab) {
}
}

/**
* On Message Callback
* @function onMessage
* @param {Object} message
* @param {MessageSender} sender
* @param {Function} sendResponse
*/
function onMessage(message, sender, sendResponse) {
console.debug('onMessage:', message, sender)
const tabId = message.tabId || sender.tab?.id
console.debug('tabId:', tabId)
if (message.message === 'openLinks') {
// noinspection JSIgnoredPromiseFromCall
openLinks(message.data)
}
sendResponse('Success.')
}

/**
* On Changed Callback
* TODO: Cleanup this function
1 change: 1 addition & 0 deletions tests/test.js
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ async function screenshot(name) {

// Popup
console.log('Activate Popup')
await new Promise((resolve) => setTimeout(resolve, 500))
await worker.evaluate('chrome.action.openPopup();')
page = await getPage(browser, 'popup.html', true)
console.log('page:', page)