@skylord123/node-red-pebble-timeline 1.0.0

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.
@@ -0,0 +1,195 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ module.exports = function(RED) {
5
+ function PebbleTimelineListNode(config) {
6
+ RED.nodes.createNode(this, config);
7
+ const node = this;
8
+
9
+ // Get the config node
10
+ const configNode = RED.nodes.getNode(config.config);
11
+ if (!configNode) {
12
+ node.error("No Pebble Timeline configuration found");
13
+ return;
14
+ }
15
+
16
+ // Make sure storage directory exists
17
+ const storageDir = path.join(RED.settings.userDir, 'pebble-timeline');
18
+ fs.ensureDirSync(storageDir);
19
+ const pinsFile = path.join(storageDir, 'timeline-pins.json');
20
+
21
+ node.on('input', function(msg, send, done) {
22
+ // Backwards compatibility with Node-RED 0.x
23
+ send = send || function() { node.send.apply(node, arguments) };
24
+
25
+ let startTime = null;
26
+ let endTime = null;
27
+ let apiUrlOverride = null;
28
+ let tokenOverride = null;
29
+
30
+ // Process filter parameters in sequence
31
+ Promise.resolve()
32
+ .then(() => {
33
+ return new Promise((resolve) => {
34
+ if (config.apiUrl) {
35
+ RED.util.evaluateNodeProperty(config.apiUrl, config.apiUrlType, node, msg, (err, result) => {
36
+ if (!err && result) {
37
+ apiUrlOverride = result;
38
+ }
39
+ resolve();
40
+ });
41
+ } else {
42
+ resolve();
43
+ }
44
+ });
45
+ })
46
+ .then(() => {
47
+ return new Promise((resolve) => {
48
+ if (config.token) {
49
+ RED.util.evaluateNodeProperty(config.token, config.tokenType, node, msg, (err, result) => {
50
+ if (!err && result) {
51
+ tokenOverride = result;
52
+ }
53
+ resolve();
54
+ });
55
+ } else {
56
+ resolve();
57
+ }
58
+ });
59
+ })
60
+ .then(() => {
61
+ return new Promise((resolve) => {
62
+ if (config.startTime) {
63
+ RED.util.evaluateNodeProperty(config.startTime, config.startTimeType, node, msg, (err, result) => {
64
+ if (!err && result) {
65
+ startTime = new Date(result);
66
+ if (isNaN(startTime.getTime())) {
67
+ node.warn("Invalid start time format");
68
+ startTime = null;
69
+ }
70
+ }
71
+ resolve();
72
+ });
73
+ } else {
74
+ resolve();
75
+ }
76
+ });
77
+ })
78
+ .then(() => {
79
+ return new Promise((resolve) => {
80
+ if (config.endTime) {
81
+ RED.util.evaluateNodeProperty(config.endTime, config.endTimeType, node, msg, (err, result) => {
82
+ if (!err && result) {
83
+ endTime = new Date(result);
84
+ if (isNaN(endTime.getTime())) {
85
+ node.warn("Invalid end time format");
86
+ endTime = null;
87
+ }
88
+ }
89
+ resolve();
90
+ });
91
+ } else {
92
+ resolve();
93
+ }
94
+ });
95
+ })
96
+ .then(() => {
97
+ // Load the pins
98
+ let pinsData = {};
99
+ try {
100
+ if (fs.existsSync(pinsFile)) {
101
+ pinsData = JSON.parse(fs.readFileSync(pinsFile, 'utf8'));
102
+ }
103
+ } catch (error) {
104
+ node.warn(`Error loading pins file: ${error.message}`);
105
+ }
106
+
107
+ // Get the timeline token to use
108
+ const timelineToken = tokenOverride || configNode.credentials.timelineToken;
109
+
110
+ // Get pins for this token only
111
+ let pins = [];
112
+ if (pinsData[timelineToken]) {
113
+ pins = pinsData[timelineToken];
114
+ }
115
+
116
+ // Apply filters
117
+ const filteredPins = pins.filter(pin => {
118
+ let include = true;
119
+
120
+ if (startTime !== null) {
121
+ const pinTime = new Date(pin.time);
122
+ if (pinTime < startTime) {
123
+ include = false;
124
+ }
125
+ }
126
+
127
+ if (endTime !== null) {
128
+ const pinTime = new Date(pin.time);
129
+ if (pinTime > endTime) {
130
+ include = false;
131
+ }
132
+ }
133
+
134
+ return include;
135
+ });
136
+
137
+ // Clean up old pins (older than 1 month) from all tokens
138
+ cleanupOldPins(pinsData);
139
+
140
+ // Create output message
141
+ msg.payload = filteredPins;
142
+ msg.count = filteredPins.length;
143
+
144
+ node.status({fill: "green", shape: "dot", text: `${filteredPins.length} pins found`});
145
+
146
+ send(msg);
147
+ if (done) done();
148
+ })
149
+ .catch(error => {
150
+ node.error(`Error listing pins: ${error.message}`, msg);
151
+ if (done) done(error);
152
+ });
153
+ });
154
+
155
+ // Helper to clean up pins older than 1 month
156
+ function cleanupOldPins(pinsData) {
157
+ const oneMonthAgo = new Date();
158
+ oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
159
+ let changed = false;
160
+
161
+ // Iterate through all tokens
162
+ Object.keys(pinsData).forEach(token => {
163
+ // Filter out pins older than 1 month
164
+ const initialCount = pinsData[token].length;
165
+ pinsData[token] = pinsData[token].filter(pin => {
166
+ const storedDate = new Date(pin._stored);
167
+ return storedDate >= oneMonthAgo;
168
+ });
169
+
170
+ // Log if pins were removed
171
+ if (pinsData[token].length < initialCount) {
172
+ node.debug(`Removed ${initialCount - pinsData[token].length} old pins for token ${token.substring(0, 8)}...`);
173
+ changed = true;
174
+ }
175
+ });
176
+
177
+ // Save the updated pins data if any pins were removed
178
+ if (changed) {
179
+ try {
180
+ fs.writeFileSync(pinsFile, JSON.stringify(pinsData, null, 2));
181
+ } catch (error) {
182
+ node.warn(`Error saving pins to file: ${error.message}`);
183
+ }
184
+ }
185
+ }
186
+
187
+ node.on('close', function() {
188
+ // Clean up any resources
189
+ });
190
+ }
191
+
192
+ RED.nodes.registerType("pebble-timeline-list", PebbleTimelineListNode, {
193
+ credentials: {}
194
+ });
195
+ };