Compare commits

...

31 Commits

Author SHA1 Message Date
PascalR 7a345d1a64
Merge 21bf4e4c26 into 971889f9a6 2023-12-08 07:34:19 -07:00
Greg Heartsfield 971889f9a6 improvement: disable limit_scrapers by default
This is a good feature, but will limit valid requests from being
served.  Defaulting this to off will be less surprising to relay ops.
2023-12-03 10:51:59 -06:00
Kieran 388eadf880 feat: limit_scrapers
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-12-03 10:51:49 -06:00
PascalR 21bf4e4c26
Update info.rs 2023-11-12 22:05:05 +01:00
mroxso b522f9ed0a fix typo in relay information about payments url 2023-11-12 17:23:43 +01:00
github-actions[bot] 9a471fee25
Merge pull request #14 from scsibug/master
Fork Sync: Update from parent repository
2023-11-06 02:12:53 +00:00
github-actions[bot] a2a67dec95
Merge pull request #13 from scsibug/master
Fork Sync: Update from parent repository
2023-11-04 02:09:54 +00:00
PascalR 38f23e912a
fix relay fee information (#12)
Co-authored-by: mroxso <mail@pascalrost.de>
2023-11-02 20:50:24 +01:00
github-actions[bot] b20a5397a9
Merge pull request #11 from scsibug/master
Fork Sync: Update from parent repository
2023-09-05 02:07:55 +00:00
PascalR 9afbbd8e08
Update docker-image.yml
add latest tag to building docker container image for master branch
2023-08-18 07:26:51 +02:00
PascalR 02da57961c
Merge pull request #10 from mroxso/bootstrap
Bootstrap
2023-08-15 21:56:18 +02:00
mroxso 4c28ea0f96 comment out ci for docker build action and improved docker build action 2023-08-15 21:55:38 +02:00
mroxso 7ade6bb56f fix colors on first page (wip) 2023-08-13 23:48:51 +02:00
mroxso 3e0329d54e wip 2023-08-13 23:43:45 +02:00
PascalR 60ccbea0a2
Merge pull request #9 from scsibug/master
Fork Sync: Update from parent repository
2023-08-13 22:39:39 +02:00
PascalR cb00b825db
Merge pull request #8 from scsibug/master
Fork Sync: Update from parent repository
2023-07-31 10:23:58 +02:00
PascalR f6f90b535e
Merge pull request #7 from scsibug/master
Fork Sync: Update from parent repository
2023-07-06 17:47:04 +02:00
PascalR 37135dd690
Merge pull request #6 from scsibug/master
Fork Sync: Update from parent repository
2023-06-30 12:56:28 +02:00
github-actions[bot] 70da8eff42
Fork Sync: Update from parent repository (#5)
* docs: typo in `build-essential` package name

* improvement(NIP-42): use 'restricted:' prefix for auth error msgs

* docs: add database maintenance example queries

* feat: allow logging output to file

* feat: roll over logs daily

* refactor: reorder imports

* improvement: default to logging on stdout

* fix: ensure startup SQL runs, even with zero min writers

---------

Co-authored-by: thesimplekid <tsk@thesimplekid.com>
Co-authored-by: rorp <rorp@protonmail.com>
Co-authored-by: Yuval Adam <_@yuv.al>
Co-authored-by: Jamin M <jaminmenter@outlook.com>
Co-authored-by: Greg Heartsfield <scsibug@imap.cc>
2023-06-25 11:02:44 +02:00
PascalR 31331aa1e4
Merge branch 'scsibug:master' into master 2023-05-11 11:20:02 +02:00
github-actions[bot] ee6a7082d1
Fork Sync: Update from parent repository (#4)
* improvement: use appropriate paths for systemd example

* improvement: add a configurable postgres write conn string

This adds a new configurable connection string for postgres writes.

* improvement: document pg connection_write config

* build: upgrade checkout action for github ci

* perf: use standard allocator, limit sqlite mmap to 4GB

This is an experimental change to see if we can reduce memory usage
with large SQLite databases.  If successful, we'll do this again and
further reduce the database mmap size.

This will cause greater use of the page cache, but that is more easily
reclaimed by the kernel, and should reduce memory pressure, as well as
making it clearer how much memory the application is actually using
for connections, subscriptions, etc.

* docs: reformatting

* docs: allow host header prefix matching, required for Damus compatibility

* perf: disable sqlite mmap to reduce memory pressure

* perf: switch to jemalloc allocator

* docs: helpful ubuntu packages for building

* perf: reduce SQLite connection count and idle lifetime

On lightly loaded relays, we free up memory faster by letting idle
connections be reclaimed in 10 seconds instead of the default 10
minutes.  This also sets the minimum to zero connections, instead of
always trying to hold one open.

---------

Co-authored-by: Petr Kracik <petrkr@petrkr.net>
Co-authored-by: Kieran <kieran@harkin.me>
Co-authored-by: Greg Heartsfield <scsibug@imap.cc>
2023-05-11 11:19:41 +02:00
github-actions[bot] 8bf52646c7
Fork Sync: Update from parent repository (#3)
* improvement: use appropriate paths for systemd example

* improvement: add a configurable postgres write conn string

This adds a new configurable connection string for postgres writes.

* improvement: document pg connection_write config

* build: upgrade checkout action for github ci

* perf: use standard allocator, limit sqlite mmap to 4GB

This is an experimental change to see if we can reduce memory usage
with large SQLite databases.  If successful, we'll do this again and
further reduce the database mmap size.

This will cause greater use of the page cache, but that is more easily
reclaimed by the kernel, and should reduce memory pressure, as well as
making it clearer how much memory the application is actually using
for connections, subscriptions, etc.

* docs: reformatting

* docs: allow host header prefix matching, required for Damus compatibility

* perf: disable sqlite mmap to reduce memory pressure

* perf: switch to jemalloc allocator

* docs: helpful ubuntu packages for building

* perf: reduce SQLite connection count and idle lifetime

On lightly loaded relays, we free up memory faster by letting idle
connections be reclaimed in 10 seconds instead of the default 10
minutes.  This also sets the minimum to zero connections, instead of
always trying to hold one open.

---------

Co-authored-by: Petr Kracik <petrkr@petrkr.net>
Co-authored-by: Kieran <kieran@harkin.me>
Co-authored-by: Greg Heartsfield <scsibug@imap.cc>
2023-05-10 10:30:18 +02:00
github-actions[bot] 264c03bbab
Fork Sync: Update from parent repository (#2)
* improvement: use appropriate paths for systemd example

* improvement: add a configurable postgres write conn string

This adds a new configurable connection string for postgres writes.

* improvement: document pg connection_write config

* build: upgrade checkout action for github ci

---------

Co-authored-by: Petr Kracik <petrkr@petrkr.net>
Co-authored-by: Kieran <kieran@harkin.me>
Co-authored-by: Greg Heartsfield <scsibug@imap.cc>
2023-05-01 13:20:57 +02:00
PascalR 76ae9c9417
set ignore fail for sync fork action 2023-05-01 13:18:24 +02:00
PascalR a43613219b
Add sync fork github action 2023-04-27 11:02:48 +02:00
PascalR 8dde77a943 fix not run doubled ci on push master 2023-04-26 09:23:04 +02:00
PascalR fd95c6cef6
add CI before docker-image build and push (#1) 2023-04-26 09:20:31 +02:00
PascalR c73431e743
Merge branch 'scsibug:master' into master 2023-04-26 09:14:27 +02:00
PascalR ca770a0a54
add master to trigger for docker build and push action 2023-04-18 15:08:34 +02:00
PascalR 7917aaea15
add docker-image build and push pipeline 2023-04-18 15:06:39 +02:00
PascalR b35496b44e
add docker-image build and push pipeline 2023-04-18 15:06:38 +02:00
8 changed files with 341 additions and 185 deletions

View File

@ -1,8 +1,9 @@
name: Test and build
on:
workflow_call:
push:
branches:
branches-ignore:
- master
jobs:

67
.github/workflows/docker-image.yml vendored Normal file
View File

@ -0,0 +1,67 @@
name: Docker Build and Push
on:
workflow_dispatch:
push:
branches:
- main
- master
env:
REGISTRY_NAME: ghcr.io
IMAGE_NAME: nostr-rs-relay
jobs:
# ci:
# name: CI
# uses: ./.github/workflows/ci.yml
build_and_push:
# needs: [ci]
name: Build and Push
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Collecting Metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY_NAME }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
- name: Building And Pushing Image
id: docker_build
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# - name: Build and push
# id: docker_build
# uses: docker/build-push-action@v4
# with:
# context: .
# push: true
# tags: ghcr.io/${{ github.repository_owner }}/nostr-rs-relay:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

19
.github/workflows/sync-fork.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Sync Fork
on:
schedule:
- cron: '0 2 * * *' # once a day at 2 am
workflow_dispatch: # on button click
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: tgymnich/fork-sync@v1.8
with:
owner: scsibug
base: master
head: master
ignore_fail: true

View File

@ -155,6 +155,11 @@ reject_future_seconds = 1800
# 0, 1, 2, 3, 7, 40, 41, 42, 43, 44, 30023,
#]
# Rejects imprecise requests (kind only and author only etc)
# This is a temperary measure to improve the adoption of outbox model
# Its recommended to have this enabled
limit_scrapers = false
[authorization]
# Pubkey addresses in this array are whitelisted for event publishing.
# Only valid events by these authors will be accepted, if the variable

View File

@ -74,6 +74,7 @@ pub struct Limits {
pub event_persist_buffer: usize, // events to buffer for database commits (block senders if database writes are too slow)
pub event_kind_blacklist: Option<Vec<u64>>,
pub event_kind_allowlist: Option<Vec<u64>>,
pub limit_scrapers: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -308,6 +309,7 @@ impl Default for Settings {
event_persist_buffer: 4096,
event_kind_blacklist: None,
event_kind_allowlist: None,
limit_scrapers: false
},
authorization: Authorization {
pubkey_whitelist: None, // Allow any address to publish

View File

@ -58,7 +58,7 @@ pub struct RelayInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub limitation: Option<Limitation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_url: Option<String>,
pub payments_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fees: Option<Fees>,
}
@ -86,7 +86,7 @@ impl From<Settings> for RelayInfo {
),
};
let (payment_url, fees) = if p.enabled {
let (payments_url, fees) = if p.enabled {
let admission_fee = if p.admission_cost > 0 {
Some(vec![Fee {
amount: p.admission_cost * 1000,
@ -110,7 +110,7 @@ impl From<Settings> for RelayInfo {
publication: post_fee,
};
let payment_url = if p.enabled && i.relay_url.is_some() {
let payments_url = if p.enabled && i.relay_url.is_some() {
Some(format!(
"{}join",
i.relay_url.clone().unwrap().replace("ws", "http")
@ -118,7 +118,7 @@ impl From<Settings> for RelayInfo {
} else {
None
};
(payment_url, Some(fees))
(payments_url, Some(fees))
} else {
(None, None)
};
@ -133,7 +133,7 @@ impl From<Settings> for RelayInfo {
software: Some("https://git.sr.ht/~gheartsfield/nostr-rs-relay".to_owned()),
version: CARGO_PKG_VERSION.map(std::borrow::ToOwned::to_owned),
limitation: Some(limitations),
payment_url,
payments_url,
fees,
icon: i.relay_icon,
}

View File

@ -266,73 +266,82 @@ async fn handle_web_request(
}
let html = r#"
<!doctype HTML>
<head>
<meta charset="UTF-8">
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
font-family: Arial, sans-serif;
background-color: #6320a7;
color: white;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
a {
color: pink;
}
input[type="text"] {
width: 100%;
max-width: 500px;
box-sizing: border-box;
overflow-x: auto;
white-space: nowrap;
}
</style>
</head>
<body>
<div style="width:75%;">
<h1>Enter your pubkey</h1>
<form action="/invoice" onsubmit="return checkForm(this);">
<input type="text" name="pubkey" id="pubkey-input"><br><br>
<input type="checkbox" id="terms" required>
<label for="terms">I agree to the <a href="/terms">terms and conditions</a></label><br><br>
<button type="submit">Submit</button>
</form>
<button id="get-public-key-btn">Get Public Key</button>
</div>
<script>
function checkForm(form) {
if (!form.terms.checked) {
alert("Please agree to the terms and conditions");
return false;
}
return true;
}
const pubkeyInput = document.getElementById('pubkey-input');
const getPublicKeyBtn = document.getElementById('get-public-key-btn');
getPublicKeyBtn.addEventListener('click', async function() {
try {
const publicKey = await window.nostr.getPublicKey();
pubkeyInput.value = publicKey;
} catch (error) {
console.error(error);
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #6320a7 !important; /* Make sure this override takes precedence */
color: white;
}
a {
color: pink !important;
}
.btn-primary {
background-color: #ff4081 !important;
}
</style>
<title>Join Nostr - Enter your pubkey</title>
</head>
<body>
<div class="container py-5">
<div class="row">
<div class="col-lg-6 mx-auto">
<h1 class="text-center mb-4">Enter your pubkey</h1>
<form action="/invoice" onsubmit="return checkForm(this);">
<div class="form-group">
<input type="text" name="pubkey" class="form-control" id="pubkey-input" placeholder="Public Key" required>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="terms" required>
<label class="form-check-label" for="terms">I agree to the <a href="/terms">terms and conditions</a></label>
</div>
<div class="form-group mt-4">
<button type="submit" class="btn btn-primary btn-block">Submit</button>
</div>
</form>
<div class="form-group">
<button id="get-public-key-btn" class="btn btn-primary btn-block">Get Public Key</button>
</div>
</div>
</div>
</div>
<script>
function checkForm(form) {
if (!form.terms.checked) {
alert("Please agree to the terms and conditions");
return false;
}
return true;
}
const pubkeyInput = document.getElementById('pubkey-input');
const getPublicKeyBtn = document.getElementById('get-public-key-btn');
getPublicKeyBtn.addEventListener('click', async function() {
try {
const publicKey = await window.nostr.getPublicKey();
pubkeyInput.value = publicKey;
} catch (error) {
console.error(error);
}
});
</script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>
"#;
Ok(Response::builder()
.status(StatusCode::OK)
@ -445,89 +454,94 @@ async fn handle_web_request(
let html_result = format!(
r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {{
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
font-family: Arial, sans-serif;
background-color: #6320a7 ;
color: white;
}}
#copy-button {{
background-color: #bb5f0d ;
color: white;
padding: 10px 20px;
border-radius: 5px;
border: none;
cursor: pointer;
}}
#copy-button:hover {{
background-color: #8f29f4;
}}
.container {{
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}}
a {{
color: pink;
}}
</style>
</head>
<body>
<div style="width:75%;">
<h3>
To use this relay, an admission fee of {} sats is required. By paying the fee, you agree to the <a href='terms'>terms</a>.
</h3>
</div>
<div>
<div style="max-height: 300px;">
{}
</div>
</div>
<div>
<div style="width: 75%;">
<p style="overflow-wrap: break-word; width: 500px;">{}</p>
<button id="copy-button">Copy</button>
</div>
<div>
<p> This page will not refresh </p>
<p> Verify admission <a href=/account?pubkey={}>here</a> once you have paid</p>
</div>
</div>
</body>
</html>
<script>
const copyButton = document.getElementById("copy-button");
if (navigator.clipboard) {{
copyButton.addEventListener("click", function() {{
const textToCopy = "{}";
navigator.clipboard.writeText(textToCopy).then(function() {{
console.log("Text copied to clipboard");
}}, function(err) {{
console.error("Could not copy text: ", err);
}});
}});
}} else {{
copyButton.style.display = "none";
console.warn("Clipboard API is not supported in this browser");
}}
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<style>
body {{
background-color: #6320a7;
color: white;
}}
a {{
color: pink;
}}
.btn {{
background-color: #bb5f0d;
color: white;
}}
.btn:hover {{
background-color: #8f29f4;
}}
#copy-button {{
margin-top: 10px;
}}
</style>
<title>Join Nostr - Invoice</title>
</head>
<body>
<div class="container py-5">
<div class="row">
<div class="col-lg-6 mx-auto">
<h3 class="text-center mb-4">
To use this relay, an admission fee of {} sats is required. By paying the fee, you agree to the <a href='terms'>terms</a>.
</h3>
<div class="text-center" style="max-height: 300px;">
{}
</div>
<div class="text-center mt-4">
<p id="text-area" style="overflow-wrap: break-word;">{}</p>
<button id="copy-button" class="btn">Copy</button>
</div>
<div class="text-center mt-4">
<p>This page will not refresh</p>
<p>Verify admission <a href="/account?pubkey={}">here</a> once you have paid</p>
</div>
</div>
</div>
</div>
<script>
const copyButton = document.getElementById("copy-button");
const textArea = document.getElementById("text-area");
if (navigator.clipboard) {{
copyButton.addEventListener("click", function() {{
const textToCopy = textArea.textContent;
navigator.clipboard.writeText(textToCopy).then(function() {{
console.log("Text copied to clipboard");
}}, function(err) {{
console.error("Could not copy text: ", err);
}});
}});
}} else {{
copyButton.style.display = "none";
console.warn("Clipboard API is not supported in this browser");
}}
</script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>
"#,
settings.pay_to_relay.admission_cost,
qr_code,
invoice_info.bolt11,
pubkey,
invoice_info.bolt11
pubkey
);
Ok(Response::builder()
@ -582,30 +596,41 @@ async fn handle_web_request(
let html_result = format!(
r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {{
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
font-family: Arial, sans-serif;
background-color: #6320a7;
color: white;
height: 100vh;
}}
</style>
</head>
<body>
<div>
<h5>{} {} admitted</h5>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<style>
body {{
background-color: #6320a7;
color: white;
}}
.vh-100 {{
height: 100vh;
}}
h5 {{
color: white;
}}
</style>
<title>Bootstrap Admissions</title>
</head>
<body>
<div class="d-flex justify-content-center align-items-center vh-100">
<h5 class="text-break">{ } { } admitted</h5>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>
"#,
pubkey, text
@ -1261,7 +1286,6 @@ async fn nostr_server(
// handle each type of message
let evid = ec.event_id().to_owned();
let parsed : Result<EventWrapper> = Result::<EventWrapper>::from(ec);
metrics.cmd_event.inc();
match parsed {
Ok(WrappedEvent(e)) => {
metrics.cmd_event.inc();
@ -1342,10 +1366,15 @@ async fn nostr_server(
if conn.has_subscription(&s) {
info!("client sent duplicate subscription, ignoring (cid: {}, sub: {:?})", cid, s.id);
} else {
metrics.cmd_req.inc();
metrics.cmd_req.inc();
if let Some(ref lim) = sub_lim_opt {
lim.until_ready_with_jitter(jitter).await;
}
if settings.limits.limit_scrapers && s.is_scraper() {
info!("subscription was scraper, ignoring (cid: {}, sub: {:?})", cid, s.id);
ws_stream.send(Message::Text(format!("[\"EOSE\",\"{}\"]", s.id))).await.ok();
continue
}
let (abandon_query_tx, abandon_query_rx) = oneshot::channel::<()>();
match conn.subscribe(s.clone()) {
Ok(()) => {
@ -1369,7 +1398,7 @@ async fn nostr_server(
// closing a request simply removes the subscription.
let parsed : Result<Close> = Result::<Close>::from(cc);
if let Ok(c) = parsed {
metrics.cmd_close.inc();
metrics.cmd_close.inc();
// check if a query is currently
// running, and remove it if so.
let stop_tx = running_queries.remove(&c.id);

View File

@ -45,8 +45,8 @@ pub struct ReqFilter {
impl Serialize for ReqFilter {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
if let Some(ids) = &self.ids {
@ -80,8 +80,8 @@ impl Serialize for ReqFilter {
impl<'de> Deserialize<'de> for ReqFilter {
fn deserialize<D>(deserializer: D) -> Result<ReqFilter, D::Error>
where
D: Deserializer<'de>,
where
D: Deserializer<'de>,
{
let received: Value = Deserialize::deserialize(deserializer)?;
let filter = received.as_object().ok_or_else(|| {
@ -184,8 +184,8 @@ impl<'de> Deserialize<'de> for Subscription {
/// Custom deserializer for subscriptions, which have a more
/// complex structure than the other message types.
fn deserialize<D>(deserializer: D) -> Result<Subscription, D::Error>
where
D: Deserializer<'de>,
where
D: Deserializer<'de>,
{
let mut v: Value = Deserialize::deserialize(deserializer)?;
// this should be a 3-or-more element array.
@ -258,6 +258,29 @@ impl Subscription {
}
false
}
/// Is this subscription defined as a scraper query
pub fn is_scraper(&self) -> bool {
for f in &self.filters {
let mut precision = 0;
if f.ids.is_some() {
precision += 2;
}
if f.authors.is_some() {
precision += 1;
}
if f.kinds.is_some() {
precision += 1;
}
if f.tags.is_some() {
precision += 1;
}
if precision < 2 {
return true;
}
}
false
}
}
fn prefix_match(prefixes: &[String], target: &str) -> bool {
@ -647,4 +670,14 @@ mod tests {
}
Ok(())
}
#[test]
fn is_scraper() -> Result<()> {
assert_eq!(true, serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"kinds": [1984],"since": 123,"limit":1}]"#)?.is_scraper());
assert_eq!(true, serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"kinds": [1984]},{"kinds": [1984],"authors":["aaaa"]}]"#)?.is_scraper());
assert_eq!(false, serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"kinds": [1984],"authors":["aaaa"]}]"#)?.is_scraper());
assert_eq!(false, serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"ids": ["aaaa"]}]"#)?.is_scraper());
assert_eq!(false, serde_json::from_str::<Subscription>(r##"["REQ","some-id",{"#p": ["aaaa"],"kinds":[1,4]}]"##)?.is_scraper());
Ok(())
}
}