diff --git a/go/pvl/interp_data_for_test.go b/go/pvl/interp_data_for_test.go index 2457844766db..2f5ecbcb1efb 100644 --- a/go/pvl/interp_data_for_test.go +++ b/go/pvl/interp_data_for_test.go @@ -44,6 +44,13 @@ var infoBadSig = ProofInfo{ APIURL: "https://rooter.example.com/proofs/kronkinator/5.htjsxt", } +var infoTwitterX = ProofInfo{ + ArmoredSig: sig1, + Username: "kronk", + RemoteUsername: "kronkinator", + APIURL: "https://x.com/kronkinator/status/5", +} + var html1 = `
diff --git a/go/pvl/interp_test.go b/go/pvl/interp_test.go index 7f9dec94442b..0711bd54235d 100644 --- a/go/pvl/interp_test.go +++ b/go/pvl/interp_test.go @@ -2188,6 +2188,44 @@ var interpUnitTests = []interpUnitTest{ errstatus: keybase1.ProofStatus_CONTENT_FAILURE, errstr: "Regex did not match (^foo$)", }, + { + name: "TwitterOEmbed-accepts-xcom", + proofinfo: infoTwitterX, + prepvl: map[keybase1.ProofType]string{ + keybase1.ProofType_TWITTER: `[[ +{"regex_capture": { + "pattern": "^https://(?:twitter|x)\\.com/([^/]+)/status/(\\d+)(.*)$", + "from": "hint_url", + "into": ["username_from_url", "tweet_id", "remainder"] } }, +{"assert_compare": { + "cmp": "cicmp", + "a": "username_from_url", + "b": "username_service" } }, +{"fill": { + "with": "https://api.twitter.com/1/statuses/oembed.json?id=%{tweet_id}", + "into": "our_url" } }, +{"fetch": { + "from": "our_url", + "kind": "json" } }, +{"selector_json": { + "selectors": ["author_url"], + "into": "author_url" } }, +{"regex_capture": { + "pattern": "^https://(?:twitter|x)\\.com/(.+)$", + "from": "author_url", + "into": ["author_username"] } }, +{"assert_compare": { + "cmp": "cicmp", + "a": "author_username", + "b": "username_service" } } +]]`, + }, + service: keybase1.ProofType_TWITTER, + restype: libkb.XAPIResJSON, + urloverride: "https://api.twitter.com/1/statuses/oembed.json?id=5", + resjson: `{"author_url":"https://x.com/kronkinator","html":""}`, + shouldwork: true, + }, } func TestUnits(t *testing.T) { diff --git a/pvl-tools/kit.json b/pvl-tools/kit.json index 91df910b546c..0bf2e12eb972 100644 --- a/pvl-tools/kit.json +++ b/pvl-tools/kit.json @@ -1 +1 @@ -{"kit_version":1,"ctime":1718296327,"tab":{"1":{"pvl_version":1,"revision":1,"services":{"coinbase":[[{"fill":{"with":"x","into":"tmp1"}},{"assert_regex_match":{"pattern":"^y$","from":"tmp1","error":["SERVICE_DEAD","coinbase proofs are no longer supported"]}}]],"dns":[[{"assert_regex_match":{"pattern":"^keybase-site-verification=%{sig_id_medium}$","from":"txt","error":["NOT_FOUND","matching DNS entry not found"]}}]],"facebook":[[{"assert_regex_match":{"pattern":"^[a-zA-Z0-9\\.]+$","from":"username_service","error":["BAD_USERNAME","Invalid characters in username '%{username_service}'"]}},{"regex_capture":{"pattern":"^https://(m|www)\\.facebook\\.com/([^/]*)/posts/([0-9]+)$","from":"hint_url","into":["unused1","username_from_url","post_id"],"error":["BAD_API_URL","Bad hint from server; URL should start with 'https://m.facebook.com/%{username_service}/posts/', got '%{hint_url}'"]}},{"assert_compare":{"cmp":"stripdots-then-cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; username in URL should match '%{username_service}', received '%{username_from_url}'"]}},{"fill":{"with":"https://m.facebook.com/%{username_from_url}/posts/%{post_id}","into":"our_url"}},{"fetch":{"kind":"html","from":"our_url"}},{"selector_css":{"selectors":["#m_story_permalink_view",0,"h3",1],"into":"post_text","error":["FAILED_PARSE","Could not find post text in Facebook's response"]}},{"whitespace_normalize":{"from":"post_text","into":"post_text_nw"}},{"regex_capture":{"pattern":"^Verifying myself: I am (\\S+) on Keybase.io. (\\S+)$","from":"post_text_nw","into":["username_from_post","sig_from_post"],"error":["TEXT_NOT_FOUND","Could not find Verifying myself: I am %{username_keybase} on Keybase.io. (%{sig_id_medium})"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_post","b":"username_keybase","error":["BAD_USERNAME","Wrong keybase username in post '%{username_from_post}' should be '%{username_keybase}'"]}},{"assert_compare":{"cmp":"exact","a":"sig_id_medium","b":"sig_from_post","error":["BAD_SIGNATURE","Could not find sig; '%{sig_from_post}' != '%{sig_id_medium}'"]}},{"selector_css":{"selectors":["#mobile_login_bar",0,"a",0],"attr":"href","into":"join_href","error":["CONTENT_FAILURE","Could not find 'Join' link"]}},{"regex_capture":{"pattern":"^/r\\.php\\?next=https.*facebook.com%2F([^/]*)%2Fposts.*$","from":"join_href","into":["username_from_join_href"],"error":["FAILED_PARSE","Could not interpret 'Join' link"]}},{"assert_compare":{"cmp":"stripdots-then-cicmp","a":"username_from_join_href","b":"username_service","error":["CONTENT_FAILURE","Bad hint from server; username in URL should match '%{username_service}', received '%{username_from_url}'"]}}]],"github":[[{"regex_capture":{"pattern":"^https://gist\\.github(?:usercontent)?\\.com/([^/]*)/.*$","from":"hint_url","into":["username_from_url"],"error":["BAD_API_URL","Bad hint from server; URL should start with either https://gist.github.com OR https://gist.githubusercontent.com"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}"]}},{"fetch":{"kind":"string","from":"hint_url","into":"gist"}},{"assert_find_base64":{"needle":"sig","haystack":"gist"},"error":["TEXT_NOT_FOUND","Signature not found in body"]},{"whitespace_normalize":{"from":"gist","into":"gist_nw"}},{"assert_regex_match":{"pattern":"^((### Verifying myself: I am (https://keybase\\.io)?/%{username_keybase} As part of this verification process, I am signing this object and posting as a gist as github user \\*%{username_service}\\*)|(### Keybase proof I hereby claim: \\* I am %{username_service} on github\\. \\* I am %{username_keybase} \\(https://keybase\\.io/%{username_keybase}\\) on keybase\\.)) .*$","case_insensitive":true,"from":"gist_nw","error":["TEXT_NOT_FOUND","Found sig but gist begins with unexpected content"]}},{"assert_regex_match":{"pattern":"^.*!\\[.*$","negate":true,"case_insensitive":true,"from":"gist_nw","error":["CONTENT_FAILURE","Proof gist must not contain images"]}}]],"hackernews":[[{"regex_capture":{"pattern":"^https://hacker-news\\.firebaseio\\.com/v0/user/([^/]+)/about.json$","from":"hint_url","into":["username_from_url"],"error":["BAD_API_URL","Bad hint from server; URL should match https://hacker-news.firebaseio.com/v0/user/%{username_service}/about.json"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}"]}},{"fetch":{"kind":"string","from":"hint_url","into":"profile"}},{"assert_regex_match":{"pattern":"^.*%{sig_id_medium}.*$","from":"profile","error":["TEXT_NOT_FOUND","Posted text does not include signature '%{sig_id_medium}'"]}}]],"reddit":[[{"regex_capture":{"pattern":"^https://www.reddit.com/r/([^/]+)/(.*)$","from":"hint_url","into":["subreddit_from_url","path_remainder"],"error":["BAD_API_URL","URL should start with 'https://www.reddit.com/r/keybaseproofs'"]}},{"assert_regex_match":{"pattern":"^.*reddit.*$","from":"hint_url","error":["BAD_API_URL","URL should contain 'reddit'"]}},{"assert_regex_match":{"pattern":"^keybaseproofs$","case_insensitive":true,"from":"subreddit_from_url","error":["BAD_API_URL","URL contained wrong subreddit '%{subreddit_from_url}' !+ 'keybaseproofs'"]}},{"fill":{"with":"https://old.reddit.com/r/%{subreddit_from_url}/%{path_remainder}","into":"our_url"}},{"fetch":{"from":"our_url","kind":"json"}},{"selector_json":{"selectors":[0,"kind"],"into":"kind","error":["CONTENT_MISSING","Could not find 'kind' in json"]}},{"assert_regex_match":{"pattern":"^Listing$","from":"kind","error":["CONTENT_FAILURE","Wanted a post of type 'Listing', but got '%{kind}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"kind"],"into":"inner_kind","error":["CONTENT_MISSING","Could not find inner 'kind' in json"]}},{"assert_regex_match":{"pattern":"^t3$","from":"inner_kind","error":["CONTENT_FAILURE","Wanted a child of type 't3' but got '%{inner_kind}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","subreddit"],"into":"subreddit_from_json","error":["CONTENT_MISSING","Could not find 'subreddit' in json"]}},{"assert_regex_match":{"pattern":"^keybaseproofs$","case_insensitive":true,"from":"subreddit_from_json","error":["CONTENT_FAILURE","Wrong subreddit %{subreddit_from_json}"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","author"],"into":"author","error":["CONTENT_MISSING","Could not find author in json"]}},{"assert_compare":{"cmp":"cicmp","a":"author","b":"username_service","error":["BAD_USERNAME","Bad post author; wanted '%{username_service}' but got '%{author}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","title"],"into":"title","error":["CONTENT_MISSING","Could not find title in json"]}},{"assert_regex_match":{"pattern":"^.*%{sig_id_medium}.*$","from":"title","error":["TITLE_NOT_FOUND","Missing signature ID (%{sig_id_medium})) in post title '%{title}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","selftext"],"into":"selftext","error":["CONTENT_MISSING","Could not find selftext in json"]}},{"assert_find_base64":{"needle":"sig","haystack":"selftext","error":["TEXT_NOT_FOUND","Signature not found in body"]}}]],"rooter":[[{"assert_regex_match":{"pattern":"^https?://[\\w:_\\-\\.]+/_/api/1\\.0/rooter/%{username_service}/.*$","case_insensitive":true,"from":"hint_url"}},{"fetch":{"from":"hint_url","kind":"json"}},{"selector_json":{"selectors":["status","name"],"into":"name"}},{"assert_regex_match":{"pattern":"^ok$","case_insensitive":true,"from":"name"}},{"selector_json":{"selectors":["toot","post"],"into":"post"}},{"assert_regex_match":{"pattern":"^.*%{sig_id_medium}.*$","from":"post"}}]],"twitter":[[{"regex_capture":{"pattern":"^https://twitter\\.com/([^/]+)/status/(\\d+)(.*)$","from":"hint_url","into":["username_from_url","tweet_id","remainder"],"error":["BAD_API_URL","Bad hint from server; URL should start with 'https://twitter.com/%{username_service}/'"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}"]}},{"fill":{"with":"https://api.twitter.com/1/statuses/oembed.json?id=%{tweet_id}","into":"our_url"}},{"fetch":{"from":"our_url","kind":"json"}},{"selector_json":{"selectors":["author_url"],"into":"author_url","error":["CONTENT_MISSING","Could not find 'author_url' in json"]}},{"regex_capture":{"pattern":"^https://twitter\\.com/(.+)$","from":"author_url","into":["author_username"],"error":["CONTENT_MISSING","Could not capture username from author_url"]}},{"assert_compare":{"cmp":"cicmp","a":"author_username","b":"username_service","error":["BAD_USERNAME","Bad post authored: wanted '%{username_service}' but got '%{author_username}'"]}},{"selector_json":{"selectors":["html"],"into":"tweet_contents","error":["CONTENT_MISSING","Missing 'tweet_html' in json"]}},{"whitespace_normalize":{"from":"tweet_contents","into":"tweet_contents_nw"}},{"regex_capture":{"pattern":"^.*Verifying myself: I am ([A-Za-z0-9_]+) on (?:(?:https?://)?Keybase\\.io|https://t\\.co/[A-Za-z0-9_-]+)[\\s\\p{Zs}]*\\. (\\S+) */.*$","from":"tweet_contents_nw","into":["username_from_tweet_contents","sig_from_tweet_contents"],"error":["DELETED","Could not find 'Verifying myself: I am %{username_keybase} on Keybase.io. %{sig_id_short}'"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_tweet_contents","b":"username_keybase","error":["BAD_USERNAME","Wrong username in tweet '%{username_from_tweet_contents}' should be '%{username_keybase}'"]}},{"assert_regex_match":{"pattern":"^%{sig_id_short}$","from":"sig_from_tweet_contents","error":["TEXT_NOT_FOUND","Could not find sig '%{sig_from_tweet_contents}' != '%{sig_id_short}'"]}}]],"generic_web_site":[[{"assert_regex_match":{"pattern":"^%{protocol}://%{hostname}/(?:\\.well-known/keybase\\.txt|keybase\\.txt)$","from":"hint_url","error":["BAD_API_URL","Bad hint from server; didn't recognize API url: \"%{hint_url}\""]}},{"fetch":{"kind":"string","from":"hint_url","into":"blob"}},{"assert_find_base64":{"needle":"sig","haystack":"blob","error":["TEXT_NOT_FOUND","Signature not found in body"]}}]]}}}} +{"kit_version":1,"ctime":1779991161,"tab":{"1":{"pvl_version":1,"revision":1,"services":{"coinbase":[[{"fill":{"with":"x","into":"tmp1"}},{"assert_regex_match":{"pattern":"^y$","from":"tmp1","error":["SERVICE_DEAD","coinbase proofs are no longer supported"]}}]],"dns":[[{"assert_regex_match":{"pattern":"^keybase-site-verification=%{sig_id_medium}$","from":"txt","error":["NOT_FOUND","matching DNS entry not found"]}}]],"facebook":[[{"assert_regex_match":{"pattern":"^[a-zA-Z0-9\\.]+$","from":"username_service","error":["BAD_USERNAME","Invalid characters in username '%{username_service}'"]}},{"regex_capture":{"pattern":"^https://(m|www)\\.facebook\\.com/([^/]*)/posts/([0-9]+)$","from":"hint_url","into":["unused1","username_from_url","post_id"],"error":["BAD_API_URL","Bad hint from server; URL should start with 'https://m.facebook.com/%{username_service}/posts/', got '%{hint_url}'"]}},{"assert_compare":{"cmp":"stripdots-then-cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; username in URL should match '%{username_service}', received '%{username_from_url}'"]}},{"fill":{"with":"https://m.facebook.com/%{username_from_url}/posts/%{post_id}","into":"our_url"}},{"fetch":{"kind":"html","from":"our_url"}},{"selector_css":{"selectors":["#m_story_permalink_view",0,"h3",1],"into":"post_text","error":["FAILED_PARSE","Could not find post text in Facebook's response"]}},{"whitespace_normalize":{"from":"post_text","into":"post_text_nw"}},{"regex_capture":{"pattern":"^Verifying myself: I am (\\S+) on Keybase.io. (\\S+)$","from":"post_text_nw","into":["username_from_post","sig_from_post"],"error":["TEXT_NOT_FOUND","Could not find Verifying myself: I am %{username_keybase} on Keybase.io. (%{sig_id_medium})"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_post","b":"username_keybase","error":["BAD_USERNAME","Wrong keybase username in post '%{username_from_post}' should be '%{username_keybase}'"]}},{"assert_compare":{"cmp":"exact","a":"sig_id_medium","b":"sig_from_post","error":["BAD_SIGNATURE","Could not find sig; '%{sig_from_post}' != '%{sig_id_medium}'"]}},{"selector_css":{"selectors":["#mobile_login_bar",0,"a",0],"attr":"href","into":"join_href","error":["CONTENT_FAILURE","Could not find 'Join' link"]}},{"regex_capture":{"pattern":"^/r\\.php\\?next=https.*facebook.com%2F([^/]*)%2Fposts.*$","from":"join_href","into":["username_from_join_href"],"error":["FAILED_PARSE","Could not interpret 'Join' link"]}},{"assert_compare":{"cmp":"stripdots-then-cicmp","a":"username_from_join_href","b":"username_service","error":["CONTENT_FAILURE","Bad hint from server; username in URL should match '%{username_service}', received '%{username_from_url}'"]}}]],"github":[[{"regex_capture":{"pattern":"^https://gist\\.github(?:usercontent)?\\.com/([^/]*)/.*$","from":"hint_url","into":["username_from_url"],"error":["BAD_API_URL","Bad hint from server; URL should start with either https://gist.github.com OR https://gist.githubusercontent.com"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}"]}},{"fetch":{"kind":"string","from":"hint_url","into":"gist"}},{"assert_find_base64":{"needle":"sig","haystack":"gist"},"error":["TEXT_NOT_FOUND","Signature not found in body"]},{"whitespace_normalize":{"from":"gist","into":"gist_nw"}},{"assert_regex_match":{"pattern":"^((### Verifying myself: I am (https://keybase\\.io)?/%{username_keybase} As part of this verification process, I am signing this object and posting as a gist as github user \\*%{username_service}\\*)|(### Keybase proof I hereby claim: \\* I am %{username_service} on github\\. \\* I am %{username_keybase} \\(https://keybase\\.io/%{username_keybase}\\) on keybase\\.)) .*$","case_insensitive":true,"from":"gist_nw","error":["TEXT_NOT_FOUND","Found sig but gist begins with unexpected content"]}},{"assert_regex_match":{"pattern":"^.*!\\[.*$","negate":true,"case_insensitive":true,"from":"gist_nw","error":["CONTENT_FAILURE","Proof gist must not contain images"]}}]],"hackernews":[[{"regex_capture":{"pattern":"^https://hacker-news\\.firebaseio\\.com/v0/user/([^/]+)/about.json$","from":"hint_url","into":["username_from_url"],"error":["BAD_API_URL","Bad hint from server; URL should match https://hacker-news.firebaseio.com/v0/user/%{username_service}/about.json"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}"]}},{"fetch":{"kind":"string","from":"hint_url","into":"profile"}},{"assert_regex_match":{"pattern":"^.*%{sig_id_medium}.*$","from":"profile","error":["TEXT_NOT_FOUND","Posted text does not include signature '%{sig_id_medium}'"]}}]],"reddit":[[{"regex_capture":{"pattern":"^https://www.reddit.com/r/([^/]+)/(.*)$","from":"hint_url","into":["subreddit_from_url","path_remainder"],"error":["BAD_API_URL","URL should start with 'https://www.reddit.com/r/keybaseproofs'"]}},{"assert_regex_match":{"pattern":"^.*reddit.*$","from":"hint_url","error":["BAD_API_URL","URL should contain 'reddit'"]}},{"assert_regex_match":{"pattern":"^keybaseproofs$","case_insensitive":true,"from":"subreddit_from_url","error":["BAD_API_URL","URL contained wrong subreddit '%{subreddit_from_url}' !+ 'keybaseproofs'"]}},{"fill":{"with":"https://old.reddit.com/r/%{subreddit_from_url}/%{path_remainder}","into":"our_url"}},{"fetch":{"from":"our_url","kind":"json"}},{"selector_json":{"selectors":[0,"kind"],"into":"kind","error":["CONTENT_MISSING","Could not find 'kind' in json"]}},{"assert_regex_match":{"pattern":"^Listing$","from":"kind","error":["CONTENT_FAILURE","Wanted a post of type 'Listing', but got '%{kind}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"kind"],"into":"inner_kind","error":["CONTENT_MISSING","Could not find inner 'kind' in json"]}},{"assert_regex_match":{"pattern":"^t3$","from":"inner_kind","error":["CONTENT_FAILURE","Wanted a child of type 't3' but got '%{inner_kind}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","subreddit"],"into":"subreddit_from_json","error":["CONTENT_MISSING","Could not find 'subreddit' in json"]}},{"assert_regex_match":{"pattern":"^keybaseproofs$","case_insensitive":true,"from":"subreddit_from_json","error":["CONTENT_FAILURE","Wrong subreddit %{subreddit_from_json}"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","author"],"into":"author","error":["CONTENT_MISSING","Could not find author in json"]}},{"assert_compare":{"cmp":"cicmp","a":"author","b":"username_service","error":["BAD_USERNAME","Bad post author; wanted '%{username_service}' but got '%{author}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","title"],"into":"title","error":["CONTENT_MISSING","Could not find title in json"]}},{"assert_regex_match":{"pattern":"^.*%{sig_id_medium}.*$","from":"title","error":["TITLE_NOT_FOUND","Missing signature ID (%{sig_id_medium})) in post title '%{title}'"]}},{"selector_json":{"selectors":[0,"data","children",0,"data","selftext"],"into":"selftext","error":["CONTENT_MISSING","Could not find selftext in json"]}},{"assert_find_base64":{"needle":"sig","haystack":"selftext","error":["TEXT_NOT_FOUND","Signature not found in body"]}}]],"rooter":[[{"assert_regex_match":{"pattern":"^https?://[\\w:_\\-\\.]+/_/api/1\\.0/rooter/%{username_service}/.*$","case_insensitive":true,"from":"hint_url"}},{"fetch":{"from":"hint_url","kind":"json"}},{"selector_json":{"selectors":["status","name"],"into":"name"}},{"assert_regex_match":{"pattern":"^ok$","case_insensitive":true,"from":"name"}},{"selector_json":{"selectors":["toot","post"],"into":"post"}},{"assert_regex_match":{"pattern":"^.*%{sig_id_medium}.*$","from":"post"}}]],"twitter":[[{"regex_capture":{"pattern":"^https://(?:twitter|x)\\.com/([^/]+)/status/(\\d+)(.*)$","from":"hint_url","into":["username_from_url","tweet_id","remainder"],"error":["BAD_API_URL","Bad hint from server; URL should start with 'https://twitter.com/%{username_service}/' or 'https://x.com/%{username_service}/'"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_url","b":"username_service","error":["BAD_API_URL","Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}"]}},{"fill":{"with":"https://api.twitter.com/1/statuses/oembed.json?id=%{tweet_id}","into":"our_url"}},{"fetch":{"from":"our_url","kind":"json"}},{"selector_json":{"selectors":["author_url"],"into":"author_url","error":["CONTENT_MISSING","Could not find 'author_url' in json"]}},{"regex_capture":{"pattern":"^https://(?:twitter|x)\\.com/(.+)$","from":"author_url","into":["author_username"],"error":["CONTENT_MISSING","Could not capture username from author_url"]}},{"assert_compare":{"cmp":"cicmp","a":"author_username","b":"username_service","error":["BAD_USERNAME","Bad post authored: wanted '%{username_service}' but got '%{author_username}'"]}},{"selector_json":{"selectors":["html"],"into":"tweet_contents","error":["CONTENT_MISSING","Missing 'tweet_html' in json"]}},{"whitespace_normalize":{"from":"tweet_contents","into":"tweet_contents_nw"}},{"regex_capture":{"pattern":"^.*Verifying myself: I am ([A-Za-z0-9_]+) on (?:(?:https?://)?Keybase\\.io|https://t\\.co/[A-Za-z0-9_-]+)[\\s\\p{Zs}]*\\. (\\S+) */.*$","from":"tweet_contents_nw","into":["username_from_tweet_contents","sig_from_tweet_contents"],"error":["DELETED","Could not find 'Verifying myself: I am %{username_keybase} on Keybase.io. %{sig_id_short}'"]}},{"assert_compare":{"cmp":"cicmp","a":"username_from_tweet_contents","b":"username_keybase","error":["BAD_USERNAME","Wrong username in tweet '%{username_from_tweet_contents}' should be '%{username_keybase}'"]}},{"assert_regex_match":{"pattern":"^%{sig_id_short}$","from":"sig_from_tweet_contents","error":["TEXT_NOT_FOUND","Could not find sig '%{sig_from_tweet_contents}' != '%{sig_id_short}'"]}}]],"generic_web_site":[[{"assert_regex_match":{"pattern":"^%{protocol}://%{hostname}/(?:\\.well-known/keybase\\.txt|keybase\\.txt)$","from":"hint_url","error":["BAD_API_URL","Bad hint from server; didn't recognize API url: \"%{hint_url}\""]}},{"fetch":{"kind":"string","from":"hint_url","into":"blob"}},{"assert_find_base64":{"needle":"sig","haystack":"blob","error":["TEXT_NOT_FOUND","Signature not found in body"]}}]]}}}} diff --git a/pvl-tools/tab/1.cson b/pvl-tools/tab/1.cson index 4f453ef96e75..d241daec3d5e 100644 --- a/pvl-tools/tab/1.cson +++ b/pvl-tools/tab/1.cson @@ -284,10 +284,10 @@ services: # validate url and extract username { regex_capture: { - , pattern: "^https://twitter\\.com/([^/]+)/status/(\\d+)(.*)$" + , pattern: "^https://(?:twitter|x)\\.com/([^/]+)/status/(\\d+)(.*)$" , from: "hint_url" , into: ["username_from_url", "tweet_id", "remainder"] - , error: ["BAD_API_URL", "Bad hint from server; URL should start with 'https://twitter.com/%{username_service}/'"] } }, + , error: ["BAD_API_URL", "Bad hint from server; URL should start with 'https://twitter.com/%{username_service}/' or 'https://x.com/%{username_service}/'"] } }, { assert_compare: { , cmp: "cicmp" , a: "username_from_url" @@ -309,7 +309,7 @@ services: , into: "author_url" , error: ["CONTENT_MISSING", "Could not find 'author_url' in json"] } }, { regex_capture: { - , pattern: "^https://twitter\\.com/(.+)$" + , pattern: "^https://(?:twitter|x)\\.com/(.+)$" , from: "author_url" , into: ["author_username"] , error: ["CONTENT_MISSING", "Could not capture username from author_url"] } },Verifying myself: I am kronk on Keybase.io. 9JHQ8ZNOFRORQUpmH0jLbNbFClOccMEghH5l /