this repo has no description

feature: score can be incremented

Signed-off-by: Nick Gerakines <12125+ngerakines@users.noreply.github.com>

+81 -52
+26 -19
docs/playbook-rhai.md
··· 20 20 21 21 ## Scripting 22 22 23 - Rhai matchers evaluate a script that returns a `MatcherResult` object. The script must return an object that matches this type. 23 + Rhai matchers evaluate a script that returns a `Match` object or a `string` containing the AT-URI of the post that has matched. Return values of `false` or `0` are considered not matched. 24 24 25 - The `new_matcher_result()` function is available to create a new `MatcherResult` object. 25 + The `upsert_match(aturi)` function is available to create a new `Match` object. It has one parameter, the AT-URI of the post that is matched. 26 26 27 27 ```rhai 28 - let result = new_matcher_result(); 29 - 28 + let condition_thing = true; 30 29 // do some stuff ... 31 30 32 - result 31 + if condition_thing { 32 + return upsert_match(); 33 + } 34 + 35 + false 33 36 ``` 34 37 35 38 ## Provided Methods ··· 54 57 name: "rhai'ya doing" 55 58 description: "This feed uses the rhai matcher to match against a complex expression." 56 59 matchers: 57 - - source: "/opt/supercell/rhaiyadoin.rhai" 60 + - script: "/opt/supercell/rhaiyadoin.rhai" 58 61 type: rhai 59 62 ``` 60 63 61 64 An example rhai script: 62 65 63 66 ```rhai 64 - let result = new_matcher_result(); 67 + // Only match events from the bsky feed where the did is "did:plc:cbkjy5n7bk3ax2wplmtjofq2" (@ngerakines.me). 68 + if event.did != "did:plc:cbkjy5n7bk3ax2wplmtjofq2" { 69 + return false; 70 + } 65 71 72 + // If the event has a commit that has a record that has a $type, set rtype. Otherwise the value will be (). 66 73 let rtype = event?.commit?.record["$type"]; 67 - 68 - if rtype != "app.bsky.feed.post" { 69 - return result; 70 - } 71 - 72 - let root_uri = event?.commit?.record?.reply?.root?.uri; 73 - 74 - result.matched = `${root_uri}`.starts_with("at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/app.bsky.feed.post/"); 75 - 76 - if result.matched { 77 - result.aturi = build_aturi(event); 74 + switch rtype { 75 + "app.bsky.feed.post" => { 76 + // Compose the at-uri of the post that has matched. 77 + return build_aturi(event); 78 + } 79 + "app.bsky.feed.like" => { 80 + // Returns the subject uri of the like event or false if it doesn't exist. 81 + return event?.commit?.record?.subject?.uri ?? false; 82 + } 83 + _ => { } 78 84 } 79 85 80 - result 86 + // Nothing else matches 87 + false 81 88 ``` 82 89
+5 -2
etc/rhai_ngerakines_activity.rhai
··· 2 2 return false; 3 3 } 4 4 5 - let rtype = event?.commit?.record["$type"]; 5 + let rtype = event?.commit?.record?["$type"]; 6 6 switch rtype { 7 7 "app.bsky.feed.post" => { 8 8 return build_aturi(event); 9 9 } 10 10 "app.bsky.feed.like" => { 11 - return event?.commit?.record?.subject?.uri ?? false; 11 + let like_uri = event?.commit?.record?.subject?.uri ?? ""; 12 + if like_uri.starts_with("at://") { 13 + return update_match(like_uri); 14 + } 12 15 } 13 16 _ => { } 14 17 }
+9 -3
src/consumer.rs
··· 16 16 use crate::storage; 17 17 use crate::storage::consumer_control_get; 18 18 use crate::storage::consumer_control_insert; 19 - use crate::storage::feed_content_insert; 19 + use crate::storage::feed_content_update; 20 + use crate::storage::feed_content_upsert; 20 21 use crate::storage::StoragePool; 21 22 22 23 const MAX_MESSAGE_SIZE: usize = 25000; ··· 186 187 indexed_at: event.clone().time_us, 187 188 score: 1, 188 189 }; 189 - if op == MatchOperation::Upsert { 190 - feed_content_insert(&self.pool, &feed_content).await?; 190 + match op { 191 + MatchOperation::Upsert => { 192 + feed_content_upsert(&self.pool, &feed_content).await?; 193 + }, 194 + MatchOperation::Update => { 195 + feed_content_update(&self.pool, &feed_content).await?; 196 + }, 191 197 } 192 198 193 199 }
+9 -6
src/matcher.rs
··· 2 2 3 3 use serde_json_path::JsonPath; 4 4 5 - use rhai::{ 6 - serde::to_dynamic, 7 - CustomType, Dynamic, Engine, Scope, TypeBuilder, AST, 8 - }; 5 + use rhai::{serde::to_dynamic, CustomType, Dynamic, Engine, Scope, TypeBuilder, AST}; 9 6 use std::{collections::HashMap, path::PathBuf, str::FromStr}; 10 7 11 8 use crate::config; ··· 23 20 impl Match { 24 21 fn upsert(aturi: &str) -> Self { 25 22 Self(MatchOperation::Upsert, aturi.to_string()) 23 + } 24 + fn update(aturi: &str) -> Self { 25 + Self(MatchOperation::Update, aturi.to_string()) 26 26 } 27 27 } 28 28 ··· 358 358 engine 359 359 .build_type::<Match>() 360 360 .register_fn("build_aturi", build_aturi) 361 - .register_fn("new_match", Match::upsert); 361 + .register_fn("update_match", Match::update) 362 + .register_fn("upsert_match", Match::upsert); 362 363 let ast = engine 363 364 .compile_file(PathBuf::from_str(source)?) 364 365 .context("cannot compile script")?; ··· 380 381 if let Some(match_value) = value.try_cast::<Match>() { 381 382 return Ok(Some(match_value)); 382 383 } 383 - Err(anyhow!("unsupported return value type: must be int, string, or match")) 384 + Err(anyhow!( 385 + "unsupported return value type: must be int, string, or match" 386 + )) 384 387 } 385 388 386 389 impl Matcher for RhaiMatcher {
+31 -2
src/storage.rs
··· 19 19 } 20 20 } 21 21 22 - pub async fn feed_content_insert(pool: &StoragePool, feed_content: &FeedContent) -> Result<()> { 22 + pub async fn feed_content_upsert(pool: &StoragePool, feed_content: &FeedContent) -> Result<()> { 23 23 let mut tx = pool.begin().await.context("failed to begin transaction")?; 24 24 25 25 let now = Utc::now(); 26 - sqlx::query("INSERT OR REPLACE INTO feed_content (feed_id, uri, indexed_at, updated_at, score) VALUES (?, ?, ?, ?, ?)") 26 + let res = sqlx::query("INSERT OR REPLACE INTO feed_content (feed_id, uri, indexed_at, updated_at, score) VALUES (?, ?, ?, ?, ?)") 27 27 .bind(&feed_content.feed_id) 28 28 .bind(&feed_content.uri) 29 29 .bind(feed_content.indexed_at) ··· 31 31 .bind(feed_content.score) 32 32 .execute(tx.as_mut()) 33 33 .await.context("failed to insert feed content record")?; 34 + 35 + if res.rows_affected() == 0 { 36 + sqlx::query("UPDATE feed_content SET score = score + ?, updated_at = ? WHERE feed_id = ? AND uri = ?") 37 + .bind(feed_content.score) 38 + .bind(now) 39 + .bind(&feed_content.feed_id) 40 + .bind(&feed_content.uri) 41 + .execute(tx.as_mut()) 42 + .await 43 + .context("failed to update feed content record")?; 44 + } 45 + 46 + tx.commit().await.context("failed to commit transaction") 47 + } 48 + 49 + pub async fn feed_content_update(pool: &StoragePool, feed_content: &FeedContent) -> Result<()> { 50 + let mut tx = pool.begin().await.context("failed to begin transaction")?; 51 + 52 + let now = Utc::now(); 53 + sqlx::query( 54 + "UPDATE feed_content SET score = score + ?, updated_at = ? WHERE feed_id = ? AND uri = ?", 55 + ) 56 + .bind(feed_content.score) 57 + .bind(now) 58 + .bind(&feed_content.feed_id) 59 + .bind(&feed_content.uri) 60 + .execute(tx.as_mut()) 61 + .await 62 + .context("failed to update feed content record")?; 34 63 35 64 tx.commit().await.context("failed to commit transaction") 36 65 }
+1 -1
testdata/rhai_match_everything.rhai
··· 1 1 let aturi = build_aturi(event); 2 - return new_match(aturi); 2 + return upsert_match(aturi);
-19
testdata/rhai_match_liked.rhai
··· 1 - 2 - let result = new_matcher_result(); 3 - 4 - let rtype = event?.commit?.record["$type"]; 5 - 6 - switch rtype { 7 - "app.bsky.feed.like" => { 8 - result.matched = true; 9 - } 10 - // noop 11 - _ => { } 12 - } 13 - 14 - if result.matched { 15 - result.aturi = build_aturi(event); 16 - } 17 - 18 - 19 - result