stated-protocol-parser 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/constants.d.ts +1 -0
  2. package/dist/constants.d.ts.map +1 -0
  3. package/dist/constants.js +1 -0
  4. package/dist/constants.js.map +1 -0
  5. package/dist/esm/constants.d.ts +47 -0
  6. package/dist/esm/constants.d.ts.map +1 -0
  7. package/dist/esm/constants.js +47 -0
  8. package/dist/esm/constants.js.map +1 -0
  9. package/dist/esm/hash.browser.d.ts +31 -0
  10. package/dist/esm/hash.browser.d.ts.map +1 -0
  11. package/dist/esm/hash.browser.js +58 -0
  12. package/dist/esm/hash.browser.js.map +1 -0
  13. package/dist/esm/hash.node.d.ts +31 -0
  14. package/dist/esm/hash.node.d.ts.map +1 -0
  15. package/dist/esm/hash.node.js +43 -0
  16. package/dist/esm/hash.node.js.map +1 -0
  17. package/dist/esm/hash.test.d.ts +2 -0
  18. package/dist/esm/hash.test.d.ts.map +1 -0
  19. package/dist/esm/hash.test.js +181 -0
  20. package/dist/esm/hash.test.js.map +1 -0
  21. package/dist/esm/index.d.ts +44 -0
  22. package/dist/esm/index.d.ts.map +1 -0
  23. package/dist/esm/index.js +643 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/index.test.d.ts +2 -0
  26. package/dist/esm/index.test.d.ts.map +1 -0
  27. package/dist/esm/index.test.js +293 -0
  28. package/dist/esm/index.test.js.map +1 -0
  29. package/dist/esm/types.d.ts +149 -0
  30. package/dist/esm/types.d.ts.map +1 -0
  31. package/dist/esm/types.js +2 -0
  32. package/dist/esm/types.js.map +1 -0
  33. package/dist/esm/utils.d.ts +4 -0
  34. package/dist/esm/utils.d.ts.map +1 -0
  35. package/dist/esm/utils.js +23 -0
  36. package/dist/esm/utils.js.map +1 -0
  37. package/dist/esm/v3.d.ts +5 -0
  38. package/dist/esm/v3.d.ts.map +1 -0
  39. package/dist/esm/v3.js +60 -0
  40. package/dist/esm/v3.js.map +1 -0
  41. package/dist/hash.browser.d.ts +1 -0
  42. package/dist/hash.browser.d.ts.map +1 -0
  43. package/dist/hash.browser.js +1 -0
  44. package/dist/hash.browser.js.map +1 -0
  45. package/dist/hash.node.d.ts +1 -0
  46. package/dist/hash.node.d.ts.map +1 -0
  47. package/dist/hash.node.js +1 -0
  48. package/dist/hash.node.js.map +1 -0
  49. package/dist/hash.test.d.ts +2 -0
  50. package/dist/hash.test.d.ts.map +1 -0
  51. package/dist/hash.test.js +183 -0
  52. package/dist/hash.test.js.map +1 -0
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +1 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/index.test.d.ts +2 -0
  58. package/dist/index.test.d.ts.map +1 -0
  59. package/dist/index.test.js +295 -0
  60. package/dist/index.test.js.map +1 -0
  61. package/dist/types.d.ts +1 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +1 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/utils.d.ts +1 -0
  66. package/dist/utils.d.ts.map +1 -0
  67. package/dist/utils.js +1 -0
  68. package/dist/utils.js.map +1 -0
  69. package/dist/v3.d.ts +1 -0
  70. package/dist/v3.d.ts.map +1 -0
  71. package/dist/v3.js +1 -0
  72. package/dist/v3.js.map +1 -0
  73. package/package.json +7 -13
  74. package/src/constants.ts +61 -0
  75. package/src/hash.browser.ts +65 -0
  76. package/src/hash.node.ts +47 -0
  77. package/src/hash.test.ts +217 -0
  78. package/src/index.test.ts +378 -0
  79. package/src/index.ts +678 -0
  80. package/src/types.ts +186 -0
  81. package/src/utils.ts +18 -0
  82. package/src/v3.ts +62 -0
package/src/index.ts ADDED
@@ -0,0 +1,678 @@
1
+ /* eslint-disable no-useless-concat */
2
+ import { legalForms, UTCFormat, peopleCountBuckets } from './constants'
3
+ import { parsePollV3 } from './v3'
4
+ import { monthIndex, birthDateFormat, minPeopleCountToRange } from './utils'
5
+ import type {
6
+ Statement,
7
+ Quotation,
8
+ Poll,
9
+ OrganisationVerification,
10
+ PersonVerification,
11
+ Vote,
12
+ DisputeAuthenticity,
13
+ DisputeContent,
14
+ ResponseContent,
15
+ PDFSigning,
16
+ Rating,
17
+ RatingSubjectTypeValue,
18
+ Bounty,
19
+ Observation,
20
+ Boycott
21
+ } from './types'
22
+
23
+ const fallBackVersion = 3
24
+ const version = 4
25
+
26
+ export * from './types'
27
+ export * from './constants'
28
+ export * from './utils'
29
+ export * from './v3'
30
+
31
+ // Export browser-compatible async hash functions (default)
32
+ export * from './hash.browser'
33
+
34
+ // Export Node.js synchronous hash functions with clear names
35
+ export {
36
+ sha256 as sha256Node,
37
+ verify as verifyNode,
38
+ fromUrlSafeBase64,
39
+ toUrlSafeBase64
40
+ } from './hash.node'
41
+
42
+
43
+ export const buildStatement = ({ domain, author, time, tags, content, representative, supersededStatement }: Statement) => {
44
+ if (content.match(/\nPublishing domain: /)) throw (new Error("Statement must not contain 'Publishing domain: ', as this marks the beginning of a new statement."))
45
+ if (content.match(/\n\n/)) throw (new Error("Statement must not contain two line breaks in a row, as this is used for separating statements."))
46
+ if (typeof time !== 'object' || !time.toUTCString) throw (new Error("Time must be a Date object."))
47
+ if (!domain) throw (new Error("Publishing domain missing."))
48
+ const statement = "Publishing domain: " + domain + "\n" +
49
+ "Author: " + (author || "") + "\n" +
50
+ (representative && representative?.length > 0 ? "Authorized signing representative: " + (representative || "") + "\n" : '') +
51
+ "Time: " + time.toUTCString() + "\n" +
52
+ (tags && tags.length > 0 ? "Tags: " + tags.join(', ') + "\n" : '') +
53
+ (supersededStatement && supersededStatement?.length > 0 ? "Superseded statement: " + (supersededStatement || "") + "\n" : '') +
54
+ "Format version: " + version + "\n" +
55
+ "Statement content: " + content + (content.match(/\n$/) ? '' : "\n");
56
+ if (statement.length > 3000) throw (new Error("Statement must not be longer than 3,000 characters."))
57
+ return statement
58
+ }
59
+
60
+ export const parseStatement = ({ statement: s, allowNoVersion = false }: { statement: string, allowNoVersion?: boolean })
61
+ : Statement & { type?: string, formatVersion: string } => {
62
+ if (s.length > 3000) throw (new Error("Statement must not be longer than 3,000 characters."))
63
+ if (s.match(/\n\n/)) throw new Error("Statements cannot contain two line breaks in a row, as this is used for separating statements.")
64
+ const statementRegex = new RegExp(''
65
+ + /^Publishing domain: (?<domain>[^\n]+?)\n/.source
66
+ + /Author: (?<author>[^\n]+?)\n/.source
67
+ + /(?:Authorized signing representative: (?<representative>[^\n]*?)\n)?/.source
68
+ + /Time: (?<time>[^\n]+?)\n/.source
69
+ + /(?:Tags: (?<tags>[^\n]*?)\n)?/.source
70
+ + /(?:Superseded statement: (?<supersededStatement>[^\n]*?)\n)?/.source
71
+ + /(?:Format version: (?<formatVersion>[^\n]*?)\n)?/.source
72
+ + /Statement content: (?:(?<typedContent>\n\tType: (?<type>[^\n]+?)\n[\s\S]+?\n$)|(?<content>[\s\S]+?\n$))/.source
73
+ );
74
+ const match = s.match(statementRegex)
75
+ if (!match) throw new Error("Invalid statement format:" + s)
76
+
77
+ const m: Partial<Statement> & { type?: string, formatVersion: string, timeStr: string, tagsStr: string } = {
78
+ domain: match[1], author: match[2], representative: match[3], timeStr: match[4], tagsStr: match[5],
79
+ supersededStatement: match[6], formatVersion: match[7], content: match[8] || match[10],
80
+ type: match[9] ? match[9].toLowerCase().replace(' ', '_') : undefined
81
+ }
82
+ if (!(m['timeStr'].match(UTCFormat))) throw new Error("Invalid statement format: time must be in UTC")
83
+ if (!m['domain']) throw new Error("Invalid statement format: domain is required")
84
+ if (!m['author']) throw new Error("Invalid statement format: author is required")
85
+ if (!m['content']) throw new Error("Invalid statement format: statement content is required")
86
+ if (!allowNoVersion && !m['formatVersion']) throw new Error("Invalid statement format: format version is required")
87
+
88
+ const tags = m['tagsStr']?.split(', ')
89
+ const time = new Date(m['timeStr'])
90
+ return {
91
+ domain: m['domain'],
92
+ author: m['author'],
93
+ representative: m['representative'],
94
+ time,
95
+ tags: (tags && tags.length > 0) ? tags : undefined,
96
+ supersededStatement: m['supersededStatement'],
97
+ formatVersion: m['formatVersion'] || ('' + fallBackVersion),
98
+ content: m['content'],
99
+ type: m['type']?.toLowerCase().replace(' ', '_'),
100
+ }
101
+ }
102
+
103
+ export const buildQuotationContent = ({ originalAuthor, authorVerification, originalTime, source,
104
+ quotation, paraphrasedStatement, picture, confidence }: Quotation) => {
105
+ if (quotation && quotation.match(/\n/)) throw (new Error("Quotation must not contain line breaks."))
106
+ if (!paraphrasedStatement && !quotation) throw (new Error("Quotation must contain either a quotation or a paraphrased statement."))
107
+ const content = "\n" +
108
+ "\t" + "Type: Quotation" + "\n" +
109
+ "\t" + "Original author: " + originalAuthor + "\n" +
110
+ "\t" + "Author verification: " + authorVerification + "\n" +
111
+ (originalTime && originalTime?.length > 0 ? "\t" + "Original publication time: " + originalTime + "\n" : "") +
112
+ (source && source?.length > 0 ? "\t" + "Source: " + (source || "") + "\n" : '') +
113
+ (picture && picture.length > 0 ? "\t" + "Picture proof: " + (picture || "") + "\n" : '') +
114
+ (confidence && confidence?.length > 0 ? "\t" + "Confidence: " + (confidence || "") + "\n" : '') +
115
+ (quotation && quotation?.length > 0 ? "\t" + "Quotation: " + (quotation || "") + "\n" : '') +
116
+ (paraphrasedStatement && paraphrasedStatement?.length > 0 ? "\t" + "Paraphrased statement: " +
117
+ (paraphrasedStatement || "").replace(/\n\t([^\t])/, '\n\t\t($1)') + "\n" : '') +
118
+ ""
119
+ return content
120
+ }
121
+
122
+ export const parseQuotation = (s: string): Quotation & { type: string | undefined } => {
123
+ const voteRegex = new RegExp(''
124
+ + /^\n\tType: Quotation\n/.source
125
+ + /\tOriginal author: (?<originalAuthor>[^\n]+?)\n/.source
126
+ + /\tAuthor verification: (?<authorVerification>[^\n]+?)\n/.source
127
+ + /(?:\tOriginal publication time: (?<originalTime>[^\n]+?)\n)?/.source
128
+ + /(?:\tSource: (?<source>[^\n]+?)\n)?/.source
129
+ + /(?:\tPicture proof: (?<picture>[^\n]+?)\n)?/.source
130
+ + /(?:\tConfidence: (?<confidence>[^\n]+?)\n)?/.source
131
+ + /(?:\tQuotation: (?<quotation>[^\n]+?)\n)?/.source
132
+ + /(?:\tParaphrased statement: (?:(?<paraphrasedTypedStatement>\n\t\tType: (?<type>[^\n]+?)\n[\s\S]+?)|(?<paraphrasedStatement>[\s\S]+?)))/.source
133
+ + /$/.source
134
+ );
135
+ let match = s.match(voteRegex)
136
+ if (!match) throw new Error("Invalid quotation format: " + s)
137
+ let m = {} as Quotation & { type: string | undefined }
138
+ m = {
139
+ originalAuthor: match[1], authorVerification: match[2], originalTime: match[3], source: match[4],
140
+ picture: match[5], confidence: match[6], quotation: match[7], paraphrasedStatement: match[8] || match[10],
141
+ type: match[9] ? match[9].toLowerCase().replace(' ', '_') : undefined
142
+ }
143
+ return {
144
+ originalAuthor: m['originalAuthor'],
145
+ authorVerification: m['authorVerification'],
146
+ originalTime: m['originalTime'],
147
+ source: m['source'],
148
+ picture: m['picture'],
149
+ confidence: m['confidence'],
150
+ quotation: m['quotation'],
151
+ paraphrasedStatement: (m['paraphrasedStatement']?.replace(/\n\t\t/g, "\n\t")),
152
+ type: m['type']?.toLowerCase().replace(' ', '_'),
153
+ }
154
+ }
155
+
156
+ export const buildPollContent = ({ country, city, legalEntity, domainScope, judges, deadline, poll,
157
+ scopeDescription, scopeQueryLink, options, allowArbitraryVote, requiredProperty: propertyScope, requiredPropertyObserver: propertyScopeObserver }: Poll) => {
158
+ if (!poll) throw (new Error("Poll must contain a poll question."))
159
+ const scopeContent =
160
+ (scopeDescription ? "\t\t" + "Description: " + scopeDescription + "\n" : "") +
161
+ (country ? "\t\t" + "Country scope: " + country + "\n" : "") +
162
+ (city ? "\t\t" + "City scope: " + city + "\n" : "") +
163
+ (legalEntity ? "\t\t" + "Legal form scope: " + legalEntity + "\n" : "") +
164
+ (domainScope && domainScope?.length > 0 ? "\t\t" + "Domain scope: " + domainScope.join(', ') + "\n" : "") +
165
+ (propertyScope ? "\t\t" + "All entities with the following property: " + propertyScope + "\n" : "") +
166
+ (propertyScopeObserver ? "\t\t" + "As observed by: " + propertyScopeObserver + "\n" : "") +
167
+ (scopeQueryLink ? "\t\t" + "Link to query defining who can vote: " + scopeQueryLink + "\n" : "")
168
+ if (scopeContent.length > 0 && !scopeDescription) throw (new Error("Poll must contain a description of who can vote."))
169
+ const content = "\n" +
170
+ "\t" + "Type: Poll" + "\n" +
171
+ (judges ? "\t" + "The poll outcome is finalized when the following nodes agree: " + judges + "\n" : "") +
172
+ (deadline ? "\t" + "Voting deadline: " + deadline.toUTCString() + "\n" : "") +
173
+ "\t" + "Poll: " + poll + "\n" +
174
+ (options.length > 0 && options[0] ? "\t" + "Option 1: " + options[0] + "\n" : "") +
175
+ (options.length > 1 && options[1] ? "\t" + "Option 2: " + options[1] + "\n" : "") +
176
+ (options.length > 2 && options[2] ? "\t" + "Option 3: " + options[2] + "\n" : "") +
177
+ (options.length > 3 && options[3] ? "\t" + "Option 4: " + options[3] + "\n" : "") +
178
+ (options.length > 4 && options[4] ? "\t" + "Option 5: " + options[4] + "\n" : "") +
179
+ ((allowArbitraryVote === true || allowArbitraryVote === false) ? ("\t" + "Allow free text votes: " + (allowArbitraryVote ? 'Yes' : 'No') + "\n") : "") +
180
+ (scopeContent ? "\t" + "Who can vote: \n" + scopeContent : "") +
181
+ ""
182
+ return content
183
+ }
184
+
185
+ export const parsePoll = (s: string, version?: string): Poll => {
186
+ if (version && version === '3') return parsePollV3(s)
187
+ if (version && version !== '4') throw new Error("Invalid version " + version)
188
+ const pollRegex = new RegExp(''
189
+ + /^\n\tType: Poll\n/.source
190
+ + /(?:\tThe poll outcome is finalized when the following nodes agree: (?<judges>[^\n]+?)\n)?/.source
191
+ + /(?:\tVoting deadline: (?<deadline>[^\n]+?)\n)?/.source
192
+ + /\tPoll: (?<poll>[^\n]+?)\n/.source
193
+ + /(?:\tOption 1: (?<option1>[^\n]+?)\n)?/.source
194
+ + /(?:\tOption 2: (?<option2>[^\n]+?)\n)?/.source
195
+ + /(?:\tOption 3: (?<option3>[^\n]+?)\n)?/.source
196
+ + /(?:\tOption 4: (?<option4>[^\n]+?)\n)?/.source
197
+ + /(?:\tOption 5: (?<option5>[^\n]+?)\n)?/.source
198
+ + /(?:\tAllow free text votes: (?<allowArbitraryVote>Yes|No)\n)?/.source
199
+ + /(?:\tWho can vote: (?<whoCanVote>\n[\s\S]+?\n))?/.source
200
+ + /$/.source)
201
+ let m: any = s.match(pollRegex)
202
+ if (!m) throw new Error("Invalid poll format: " + s)
203
+
204
+ m = {
205
+ judges: m[1], deadline: m[2], poll: m[3],
206
+ option1: m[4], option2: m[5], option3: m[6], option4: m[7], option5: m[8],
207
+ allowArbitraryVote: m[9],
208
+ whoCanVote: m[10]
209
+ }
210
+ const whoCanVoteParsed: Partial<Poll> & { domainScopeStr?: string } = {}
211
+ if (m.whoCanVote) {
212
+ const whoCanVoteRegex = new RegExp(''
213
+ + /^\n\t\tDescription: (?<scopeDescription>[^\n]+?)\n/.source
214
+ + /(?:\t\tCountry scope: (?<countryScope>[^\n]+?)\n)?/.source
215
+ + /(?:\t\tCity scope: (?<cityScope>[^\n]+?)\n)?/.source
216
+ + /(?:\t\tLegal form scope: (?<legalEntity>[^\n]+?)\n)?/.source
217
+ + /(?:\t\tDomain scope: (?<domainScope>[^\n]+?)\n)?/.source
218
+ + /(?:\t\tAll entities with the following property: (?<propertyScope>[^\n]+?)\n)?/.source
219
+ + /(?:\t\tAs observed by: (?<propertyScopeObserver>[^\n]+?)\n)?/.source
220
+ + /(?:\t\tLink to query defining who can vote: (?<scopeQueryLink>[^\n]+?)\n)?/.source
221
+ + /$/.source)
222
+ let m2: any = m.whoCanVote.match(whoCanVoteRegex)
223
+ if (!m2) throw new Error("Invalid who can vote section: " + m.whoCanVote)
224
+ whoCanVoteParsed['scopeDescription'] = m2[1]
225
+ whoCanVoteParsed['country'] = m2[2]
226
+ whoCanVoteParsed['city'] = m2[3]
227
+ whoCanVoteParsed['legalEntity'] = m2[4]
228
+ whoCanVoteParsed['domainScopeStr'] = m2[5]
229
+ whoCanVoteParsed['requiredProperty'] = m2[6]
230
+ whoCanVoteParsed['requiredPropertyObserver'] = m2[7]
231
+ whoCanVoteParsed['scopeQueryLink'] = m2[8]
232
+ }
233
+ const options = [m.option1, m.option2, m.option3, m.option4, m.option5].filter(o => o)
234
+ const domainScope = (whoCanVoteParsed.domainScopeStr as string | undefined)?.split(', ')
235
+ const allowArbitraryVote = (m['allowArbitraryVote'] === 'Yes' ? true :
236
+ (m['allowArbitraryVote'] === 'No' ? false : undefined))
237
+ const deadlineStr = m.deadline
238
+ if (deadlineStr && !deadlineStr.match(UTCFormat)) throw new Error("Invalid poll, deadline must be in UTC: " + deadlineStr)
239
+ return {
240
+ judges: m['judges'],
241
+ deadline: deadlineStr ? new Date(deadlineStr) : undefined,
242
+ poll: m['poll'],
243
+ options,
244
+ allowArbitraryVote,
245
+ country: whoCanVoteParsed['country'],
246
+ scopeDescription: whoCanVoteParsed['scopeDescription'],
247
+ requiredProperty: whoCanVoteParsed['requiredProperty'],
248
+ requiredPropertyObserver: whoCanVoteParsed['requiredPropertyObserver'],
249
+ scopeQueryLink: whoCanVoteParsed['scopeQueryLink'],
250
+ city: whoCanVoteParsed['city'],
251
+ legalEntity: whoCanVoteParsed['legalEntity'],
252
+ domainScope: (domainScope && domainScope.length > 0) ? domainScope : undefined,
253
+ }
254
+ }
255
+
256
+ export const buildOrganisationVerificationContent = (
257
+ { name, englishName, country, city, province, legalForm, department, domain, foreignDomain, serialNumber,
258
+ confidence, reliabilityPolicy, employeeCount, pictureHash, latitude, longitude, population }: OrganisationVerification) => {
259
+ if (!name || !country || !legalForm || (!domain && !foreignDomain)) throw new Error("Missing required fields")
260
+ if (!Object.values(legalForms).includes(legalForm)) throw new Error("Invalid legal form " + legalForm)
261
+ if (employeeCount && !Object.values(peopleCountBuckets).includes(employeeCount)) throw new Error("Invalid employee count " + employeeCount)
262
+ if (population && !Object.values(peopleCountBuckets).includes(population)) throw new Error("Invalid population " + population)
263
+ if (confidence && !('' + confidence)?.match(/^[0-9.]+$/)) throw new Error("Invalid confidence " + confidence)
264
+
265
+ return "\n" +
266
+ "\t" + "Type: Organisation verification" + "\n" +
267
+ "\t" + "Description: We verified the following information about an organisation." + "\n" +
268
+ "\t" + "Name: " + name + "\n" +
269
+ (englishName ? "\t" + "English name: " + englishName + "\n" : "") +
270
+ "\t" + "Country: " + country + "\n" +
271
+ "\t" + "Legal form: " + legalForm + "\n" +
272
+ (domain ? "\t" + "Owner of the domain: " + domain + "\n" : "") +
273
+ (foreignDomain ? "\t" + "Foreign domain used for publishing statements: " + foreignDomain + "\n" : "") +
274
+ (department ? "\t" + "Department using the domain: " + department + "\n" : "") +
275
+ (province ? "\t" + "Province or state: " + province + "\n" : "") +
276
+ (serialNumber ? "\t" + "Business register number: " + serialNumber + "\n" : "") +
277
+ (city ? "\t" + "City: " + city + "\n" : "") +
278
+ (latitude ? "\t" + "Latitude: " + latitude + "\n" : "") +
279
+ (longitude ? "\t" + "Longitude: " + longitude + "\n" : "") +
280
+ (population ? "\t" + "Population: " + population + "\n" : "") +
281
+ (pictureHash ? "\t" + "Logo: " + pictureHash + "\n" : "") +
282
+ (employeeCount ? "\t" + "Employee count: " + employeeCount + "\n" : "") +
283
+ (reliabilityPolicy ? "\t" + "Reliability policy: " + reliabilityPolicy + "\n" : "") +
284
+ (confidence ? "\t" + "Confidence: " + confidence + "\n" : "") +
285
+ ""
286
+ }
287
+
288
+ export const parseOrganisationVerification = (s: string): OrganisationVerification => {
289
+ const organisationVerificationRegex = new RegExp(''
290
+ + /^\n\tType: Organisation verification\n/.source
291
+ + /\tDescription: We verified the following information about an organisation.\n/.source
292
+ + /\tName: (?<name>[^\n]+?)\n/.source
293
+ + /(?:\tEnglish name: (?<englishName>[^\n]+?)\n)?/.source
294
+ + /\tCountry: (?<country>[^\n]+?)\n/.source
295
+ + /\tLegal (?:form|entity): (?<legalForm>[^\n]+?)\n/.source
296
+ + /(?:\tOwner of the domain: (?<domain>[^\n]+?)\n)?/.source
297
+ + /(?:\tForeign domain used for publishing statements: (?<foreignDomain>[^\n]+?)\n)?/.source
298
+ + /(?:\tDepartment using the domain: (?<department>[^\n]+?)\n)?/.source
299
+ + /(?:\tProvince or state: (?<province>[^\n]+?)\n)?/.source
300
+ + /(?:\tBusiness register number: (?<serialNumber>[^\n]+?)\n)?/.source
301
+ + /(?:\tCity: (?<city>[^\n]+?)\n)?/.source
302
+ + /(?:\tLatitude: (?<latitude>[^\n]+?)\n)?/.source
303
+ + /(?:\tLongitude: (?<longitude>[^\n]+?)\n)?/.source
304
+ + /(?:\tPopulation: (?<population>[^\n]+?)\n)?/.source
305
+ + /(?:\tLogo: (?<pictureHash>[^\n]+?)\n)?/.source
306
+ + /(?:\tEmployee count: (?<employeeCount>[01,+-]+?)\n)?/.source
307
+ + /(?:\tReliability policy: (?<reliabilityPolicy>[^\n]+?)\n)?/.source
308
+ + /(?:\tConfidence: (?<confidence>[0-9.]+?)\n)?/.source
309
+ + /$/.source
310
+ );
311
+ const m = s.match(organisationVerificationRegex)
312
+ if (!m) throw new Error("Invalid organisation verification format: " + s)
313
+ return {
314
+ name: m[1],
315
+ englishName: m[2],
316
+ country: m[3],
317
+ legalForm: m[4],
318
+ domain: m[5],
319
+ foreignDomain: m[6],
320
+ department: m[7],
321
+ province: m[8],
322
+ serialNumber: m[9],
323
+ city: m[10],
324
+ latitude: m[11] ? parseFloat(m[11]) : undefined,
325
+ longitude: m[12] ? parseFloat(m[12]) : undefined,
326
+ population: m[13],
327
+ pictureHash: m[14],
328
+ employeeCount: m[15],
329
+ reliabilityPolicy: m[16],
330
+ confidence: m[17] ? parseFloat(m[17]) : undefined,
331
+ }
332
+ }
333
+
334
+ export const buildPersonVerificationContent = (
335
+ { name, countryOfBirth, cityOfBirth, ownDomain, foreignDomain,
336
+ dateOfBirth, jobTitle, employer, verificationMethod, confidence,
337
+ picture, reliabilityPolicy }: PersonVerification) => {
338
+ if (!name || !countryOfBirth || !cityOfBirth || !dateOfBirth || (!ownDomain && !foreignDomain)) {
339
+ console.log("Missing required fields: ", { name, countryOfBirth, cityOfBirth, dateOfBirth, ownDomain, foreignDomain })
340
+ return ""
341
+ }
342
+ const [day, month, year] = dateOfBirth.toUTCString().split(' ').filter((i, j) => [1, 2, 3].includes(j))
343
+ let content = "\n" +
344
+ "\t" + "Type: Person verification" + "\n" +
345
+ "\t" + "Description: We verified the following information about a person." + "\n" +
346
+ "\t" + "Name: " + name + "\n" +
347
+ "\t" + "Date of birth: " + [day.replace(/$0/, ''), month, year].join(' ') + "\n" +
348
+ "\t" + "City of birth: " + cityOfBirth + "\n" +
349
+ "\t" + "Country of birth: " + countryOfBirth + "\n" +
350
+ (jobTitle ? "\t" + "Job title: " + jobTitle + "\n" : "") +
351
+ (employer ? "\t" + "Employer: " + employer + "\n" : "") +
352
+ (ownDomain ? "\t" + "Owner of the domain: " + ownDomain + "\n" : "") +
353
+ (foreignDomain ? "\t" + "Foreign domain used for publishing statements: " + foreignDomain + "\n" : "") +
354
+ (picture ? "\t" + "Picture: " + picture + "\n" : "") +
355
+ (verificationMethod ? "\t" + "Verification method: " + verificationMethod + "\n" : "") +
356
+ (confidence ? "\t" + "Confidence: " + confidence + "\n" : "") +
357
+ (reliabilityPolicy ? "\t" + "Reliability policy: " + reliabilityPolicy + "\n" : "") +
358
+ ""
359
+ return content
360
+ }
361
+
362
+ export const parsePersonVerification = (s: string): PersonVerification => {
363
+ const domainVerificationRegex = new RegExp(''
364
+ + /^\n\tType: Person verification\n/.source
365
+ + /\tDescription: We verified the following information about a person.\n/.source
366
+ + /\tName: (?<name>[^\n]+?)\n/.source
367
+ + /\tDate of birth: (?<dateOfBirth>[^\n]+?)\n/.source
368
+ + /\tCity of birth: (?<cityOfBirth>[^\n]+?)\n/.source
369
+ + /\tCountry of birth: (?<countryOfBirth>[^\n]+?)\n/.source
370
+ + /(?:\tJob title: (?<jobTitle>[^\n]+?)\n)?/.source
371
+ + /(?:\tEmployer: (?<employer>[^\n]+?)\n)?/.source
372
+ + /(?:\tOwner of the domain: (?<domain>[^\n]+?)\n)?/.source
373
+ + /(?:\tForeign domain used for publishing statements: (?<foreignDomain>[^\n]+?)\n)?/.source
374
+ + /(?:\tPicture: (?<picture>[^\n]+?)\n)?/.source
375
+ + /(?:\tVerification method: (?<verificationMethod>[^\n]+?)\n)?/.source
376
+ + /(?:\tConfidence: (?<confidence>[^\n]+?)\n)?/.source
377
+ + /(?:\tReliability policy: (?<reliabilityPolicy>[^\n]+?)\n)?/.source
378
+ + /$/.source
379
+ );
380
+ const m = s.match(domainVerificationRegex)
381
+ if (!m) throw new Error("Invalid person verification format: " + s)
382
+ if (m[2] && !m[2].match(birthDateFormat)) throw new Error("Invalid birth date format: " + m[2])
383
+ let { d, month, y } = m[2].match(birthDateFormat)?.groups || {}
384
+ if (!d || !month || !y) throw new Error("Invalid birth date format: " + m[2])
385
+ return {
386
+ name: m[1],
387
+ dateOfBirth: new Date(Date.UTC(parseInt(y), monthIndex(month), parseInt(d))),
388
+ cityOfBirth: m[3],
389
+ countryOfBirth: m[4],
390
+ jobTitle: m[5],
391
+ employer: m[6],
392
+ ownDomain: m[7],
393
+ foreignDomain: m[8],
394
+ picture: m[9],
395
+ verificationMethod: m[10],
396
+ confidence: m[11] ? parseFloat(m[11]) : undefined,
397
+ reliabilityPolicy: m[12]
398
+ }
399
+ }
400
+
401
+ export const buildVoteContent = ({ pollHash, poll, vote }: Vote) => {
402
+ const content = "\n" +
403
+ "\t" + "Type: Vote" + "\n" +
404
+ "\t" + "Poll id: " + pollHash + "\n" +
405
+ "\t" + "Poll: " + poll + "\n" +
406
+ "\t" + "Option: " + vote + "\n" +
407
+ ""
408
+ return content
409
+ }
410
+
411
+ export const parseVote = (s: string): Vote => {
412
+ const voteRegex = new RegExp(''
413
+ + /^\n\tType: Vote\n/.source
414
+ + /\tPoll id: (?<pollHash>[^\n]+?)\n/.source
415
+ + /\tPoll: (?<poll>[^\n]+?)\n/.source
416
+ + /\tOption: (?<vote>[^\n]+?)\n/.source
417
+ + /$/.source
418
+ );
419
+ const m = s.match(voteRegex)
420
+ if (!m) throw new Error("Invalid vote format: " + s)
421
+ return {
422
+ pollHash: m[1],
423
+ poll: m[2],
424
+ vote: m[3]
425
+ }
426
+ }
427
+
428
+ export const buildDisputeAuthenticityContent = ({ hash, confidence, reliabilityPolicy }: DisputeAuthenticity) => {
429
+ const content = "\n" +
430
+ "\t" + "Type: Dispute statement authenticity" + "\n" +
431
+ "\t" + "Description: We think that the referenced statement is not authentic.\n" +
432
+ "\t" + "Hash of referenced statement: " + hash + "\n" +
433
+ (confidence ? "\t" + "Confidence: " + confidence + "\n" : "") +
434
+ (reliabilityPolicy ? "\t" + "Reliability policy: " + reliabilityPolicy + "\n" : "") +
435
+ ""
436
+ return content
437
+ }
438
+
439
+ export const parseDisputeAuthenticity = (s: string): DisputeAuthenticity => {
440
+ const disputeRegex = new RegExp(''
441
+ + /^\n\tType: Dispute statement authenticity\n/.source
442
+ + /\tDescription: We think that the referenced statement is not authentic.\n/.source
443
+ + /\tHash of referenced statement: (?<hash>[^\n]+?)\n/.source
444
+ + /(?:\tConfidence: (?<confidence>[^\n]*?)\n)?/.source
445
+ + /(?:\tReliability policy: (?<reliabilityPolicy>[^\n]+?)\n)?/.source
446
+ + /$/.source
447
+ );
448
+ const m = s.match(disputeRegex)
449
+ if (!m) throw new Error("Invalid dispute authenticity format: " + s)
450
+ return {
451
+ hash: m[1],
452
+ confidence: m[2] ? parseFloat(m[2]) : undefined,
453
+ reliabilityPolicy: m[3]
454
+ }
455
+ }
456
+
457
+ export const buildDisputeContentContent = ({ hash, confidence, reliabilityPolicy }: DisputeContent) => {
458
+ const content = "\n" +
459
+ "\t" + "Type: Dispute statement content" + "\n" +
460
+ "\t" + "Description: We think that the content of the referenced statement is false.\n" +
461
+ "\t" + "Hash of referenced statement: " + hash + "\n" +
462
+ (confidence ? "\t" + "Confidence: " + confidence + "\n" : "") +
463
+ (reliabilityPolicy ? "\t" + "Reliability policy: " + reliabilityPolicy + "\n" : "") +
464
+ ""
465
+ return content
466
+ }
467
+
468
+ export const parseDisputeContent = (s: string): DisputeContent => {
469
+ const disputeRegex = new RegExp(''
470
+ + /^\n\tType: Dispute statement content\n/.source
471
+ + /\tDescription: We think that the content of the referenced statement is false.\n/.source
472
+ + /\tHash of referenced statement: (?<hash>[^\n]+?)\n/.source
473
+ + /(?:\tConfidence: (?<confidence>[^\n]*?)\n)?/.source
474
+ + /(?:\tReliability policy: (?<reliabilityPolicy>[^\n]+?)\n)?/.source
475
+ + /$/.source
476
+ );
477
+ const m = s.match(disputeRegex)
478
+ if (!m) throw new Error("Invalid dispute content format: " + s)
479
+ return {
480
+ hash: m[1],
481
+ confidence: m[2] ? parseFloat(m[2]) : undefined,
482
+ reliabilityPolicy: m[3]
483
+ }
484
+ }
485
+
486
+ export const buildResponseContent = ({ hash, response }: ResponseContent) => {
487
+ const content = "\n" +
488
+ "\t" + "Type: Response" + "\n" +
489
+ "\t" + "Hash of referenced statement: " + hash + "\n" +
490
+ "\t" + "Response: " + response + "\n" +
491
+ ""
492
+ return content
493
+ }
494
+
495
+ export const parseResponseContent = (s: string): ResponseContent => {
496
+ const disputeRegex = new RegExp(''
497
+ + /^\n\tType: Response\n/.source
498
+ + /\tHash of referenced statement: (?<hash>[^\n]+?)\n/.source
499
+ + /\tResponse: (?<response>[^\n]*?)\n/.source
500
+ + /$/.source
501
+ );
502
+ const m = s.match(disputeRegex)
503
+ if (!m) throw new Error("Invalid response content format: " + s)
504
+ return {
505
+ hash: m[1],
506
+ response: m[2]
507
+ }
508
+ }
509
+
510
+ export const buildPDFSigningContent = ({ hash }: PDFSigning) => {
511
+ const content = "\n" +
512
+ "\t" + "Type: Sign PDF" + "\n" +
513
+ "\t" + "Description: We hereby digitally sign the referenced PDF file.\n" +
514
+ "\t" + "PDF file hash: " + hash + "\n" +
515
+ ""
516
+ return content
517
+ }
518
+
519
+ export const parsePDFSigning = (s: string): PDFSigning => {
520
+ const signingRegex = new RegExp(''
521
+ + /^\n\tType: Sign PDF\n/.source
522
+ + /\tDescription: We hereby digitally sign the referenced PDF file.\n/.source
523
+ + /\tPDF file hash: (?<hash>[^\n]+?)\n/.source
524
+ + /$/.source
525
+ );
526
+ const m = s.match(signingRegex)
527
+ if (!m) throw new Error("Invalid PDF signing format: " + s)
528
+ return {
529
+ hash: m[1]
530
+ }
531
+ }
532
+
533
+ export const buildRating = ({ subjectName, subjectType, subjectReference, documentFileHash, rating, quality, comment }: Rating) => {
534
+ if (![1, 2, 3, 4, 5].includes(rating)) throw new Error("Invalid rating: " + rating)
535
+ const content = "\n" +
536
+ "\t" + "Type: Rating" + "\n" +
537
+ (subjectType ? "\t" + "Subject type: " + subjectType + "\n" : "") +
538
+ "\t" + "Subject name: " + subjectName + "\n" +
539
+ (subjectReference ? "\t" + "URL that identifies the subject: " + subjectReference + "\n" : "") +
540
+ (documentFileHash ? "\t" + "Document file hash: " + documentFileHash + "\n" : "") +
541
+ (quality ? "\t" + "Rated quality: " + quality + "\n" : "") +
542
+ "\t" + "Our rating: " + rating + "/5 Stars\n" +
543
+ (comment ? "\t" + "Comment: " + comment + "\n" : "") +
544
+ ""
545
+ return content
546
+ }
547
+
548
+ export const parseRating = (s: string): Rating => {
549
+ const ratingRegex = new RegExp(''
550
+ + /^\n\tType: Rating\n/.source
551
+ + /(?:\tSubject type: (?<subjectType>[^\n]*?)\n)?/.source
552
+ + /\tSubject name: (?<subjectName>[^\n]*?)\n/.source
553
+ + /(?:\tURL that identifies the subject: (?<subjectReference>[^\n]*?)\n)?/.source
554
+ + /(?:\tDocument file hash: (?<documentFileHash>[^\n]*?)\n)?/.source
555
+ + /(?:\tRated quality: (?<quality>[^\n]*?)\n)?/.source
556
+ + /\tOur rating: (?<rating>[1-5])\/5 Stars\n/.source
557
+ + /(?:\tComment: (?<comment>[\s\S]+?)\n)?/.source
558
+ + /$/.source
559
+ );
560
+ const m = s.match(ratingRegex)
561
+ if (!m) throw new Error("Invalid rating format: " + s)
562
+ const rating = parseInt(m[6])
563
+ if (![1, 2, 3, 4, 5].includes(rating)) throw new Error("Invalid rating: " + m[6])
564
+ if (m[1] && !['Organisation', 'Policy proposal', 'Regulation',
565
+ 'Treaty draft', 'Product', 'Research publication'].includes(m[1])) throw new Error("Invalid subject type: " + m[1])
566
+ if (!m[2]) throw new Error("Missing subject name")
567
+ return {
568
+ subjectType: m[1] as RatingSubjectTypeValue,
569
+ subjectName: m[2],
570
+ subjectReference: m[3],
571
+ documentFileHash: m[4],
572
+ quality: m[5],
573
+ rating,
574
+ comment: m[7]
575
+ }
576
+ }
577
+
578
+ export const buildBounty = ({ motivation, bounty, reward, judge, judgePay }: Bounty) => {
579
+ const content = "\n" +
580
+ "\t" + "Type: Bounty" + "\n" +
581
+ (motivation ? "\t" + "In order to: " + motivation + "\n" : "") +
582
+ "\t" + "We will reward any entity that: " + bounty + "\n" +
583
+ "\t" + "The reward is: " + reward + "\n" +
584
+ "\t" + "In case of dispute, bounty claims are judged by: " + judge + "\n" +
585
+ (judgePay ? "\t" + "The judge will be paid per investigated case with a maxium of: " + judgePay + "\n" : "") +
586
+ ""
587
+ return content
588
+ }
589
+
590
+ export const parseBounty = (s: string): Bounty => {
591
+ const bountyRegex = new RegExp(''
592
+ + /^\n\tType: Bounty\n/.source
593
+ + /(?:\tIn order to: (?<motivation>[^\n]*?)\n)?/.source
594
+ + /\tWe will reward any entity that: (?<bounty>[^\n]*?)\n/.source
595
+ + /\tThe reward is: (?<reward>[^\n]*?)\n/.source
596
+ + /\tIn case of dispute, bounty claims are judged by: (?<judge>[^\n]*?)\n/.source
597
+ + /(?:\tThe judge will be paid per investigated case with a maxium of: (?<judgePay>[^\n]*?)\n)?/.source
598
+ + /$/.source
599
+ );
600
+ const m = s.match(bountyRegex)
601
+ if (!m) throw new Error("Invalid bounty format: " + s)
602
+ return {
603
+ motivation: m[1],
604
+ bounty: m[2],
605
+ reward: m[3],
606
+ judge: m[4],
607
+ judgePay: m[5]
608
+ }
609
+ }
610
+
611
+ export const buildObservation = ({ approach, confidence, reliabilityPolicy, subject, subjectReference, observationReference, property, value }: Observation) => {
612
+ const content = "\n" +
613
+ "\t" + "Type: Observation" + "\n" +
614
+ (approach ? "\t" + "Approach: " + approach + "\n" : "") +
615
+ (confidence ? "\t" + "Confidence: " + confidence + "\n" : "") +
616
+ (reliabilityPolicy ? "\t" + "Reliability policy: " + reliabilityPolicy + "\n" : "") +
617
+ "\t" + "Subject: " + subject + "\n" +
618
+ (subjectReference ? "\t" + "Subject identity reference: " + subjectReference + "\n" : "") +
619
+ (observationReference ? "\t" + "Observation reference: " + observationReference + "\n" : "") +
620
+ "\t" + "Observed property: " + property + "\n" +
621
+ (value ? "\t" + "Observed value: " + value + "\n" : "") +
622
+ ""
623
+ return content
624
+ }
625
+
626
+ export const parseObservation = (s: string): Observation => {
627
+ const observationRegex = new RegExp(''
628
+ + /^\n\tType: Observation\n/.source
629
+ + /(?:\tApproach: (?<approach>[^\n]*?)\n)?/.source
630
+ + /(?:\tConfidence: (?<confidence>[^\n]*?)\n)?/.source
631
+ + /(?:\tReliability policy: (?<reliabilityPolicy>[^\n]+?)\n)?/.source
632
+ + /\tSubject: (?<subject>[^\n]*?)\n/.source
633
+ + /(?:\tSubject identity reference: (?<subjectReference>[^\n]*?)\n)?/.source
634
+ + /(?:\tObservation reference: (?<observationReference>[^\n]*?)\n)?/.source
635
+ + /\tObserved property: (?<property>[^\n]*?)\n/.source
636
+ + /(?:\tObserved value: (?<value>[\s\S]+?)\n)?/.source
637
+ + /$/.source
638
+ );
639
+ const m = s.match(observationRegex)
640
+ if (!m) throw new Error("Invalid observation format: " + s)
641
+ return {
642
+ approach: m[1],
643
+ confidence: m[2] ? parseFloat(m[2]) : undefined,
644
+ reliabilityPolicy: m[3],
645
+ subject: m[4],
646
+ subjectReference: m[5],
647
+ observationReference: m[6],
648
+ property: m[7],
649
+ value: m[8]
650
+ }
651
+ }
652
+
653
+ export const buildBoycott = ({ description, subject, subjectReference }: Boycott) => {
654
+ const content = "\n" +
655
+ "\t" + "Type: Boycott" + "\n" +
656
+ (description ? "\t" + "Description: " + description + "\n" : "") +
657
+ "\t" + "Subject: " + subject + "\n" +
658
+ (subjectReference ? "\t" + "Subject identity reference: " + subjectReference + "\n" : "") +
659
+ ""
660
+ return content
661
+ }
662
+
663
+ export const parseBoycott = (s: string): Boycott => {
664
+ const observationRegex = new RegExp(''
665
+ + /^\n\tType: Boycott\n/.source
666
+ + /(?:\tDescription: (?<description>[^\n]*?)\n)?/.source
667
+ + /\tSubject: (?<subject>[^\n]*?)\n/.source
668
+ + /(?:\tSubject identity reference: (?<subjectReference>[^\n]*?)\n)?/.source
669
+ + /$/.source
670
+ );
671
+ const m = s.match(observationRegex)
672
+ if (!m) throw new Error("Invalid observation format: " + s)
673
+ return {
674
+ description: m[1],
675
+ subject: m[2],
676
+ subjectReference: m[3],
677
+ }
678
+ }