visidata 2.11.dev0__py3-none-any.whl → 3.0__py3-none-any.whl
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.
- visidata/__init__.py +72 -91
- visidata/_input.py +263 -44
- visidata/_open.py +84 -29
- visidata/_types.py +22 -4
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +65 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- visidata/apps/vgit/__main__.py +3 -0
- visidata/apps/vgit/abort.py +23 -0
- visidata/apps/vgit/blame.py +76 -0
- visidata/apps/vgit/branch.py +153 -0
- visidata/apps/vgit/config.py +95 -0
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- visidata/apps/vgit/grep.py +37 -0
- visidata/apps/vgit/log.py +81 -0
- visidata/apps/vgit/main.py +55 -0
- visidata/apps/vgit/remote.py +57 -0
- visidata/apps/vgit/repos.py +71 -0
- visidata/apps/vgit/setup.py +37 -0
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- visidata/apps/vgit/statusbar.py +34 -0
- visidata/basesheet.py +59 -50
- visidata/canvas.py +251 -99
- visidata/choose.py +15 -11
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +84 -18
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +89 -114
- visidata/color.py +142 -56
- visidata/column.py +134 -131
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +5 -1
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +32 -6
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +163 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +4 -2
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +33 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +75 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +180 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +17 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +3 -5
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +48 -10
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macos.py +1 -1
- visidata/macros.py +130 -41
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -154
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -147
- visidata/man/vd.txt +291 -151
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +79 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +131 -43
- visidata/pivot.py +74 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +50 -201
- visidata/rename_col.py +20 -0
- visidata/save.py +42 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -24
- visidata/sheets.py +229 -257
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +113 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +62 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +17 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +87 -39
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +18 -6
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.dev0.dist-info/RECORD +0 -142
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
'''
|
2
|
+
# Matrix Chat Sheet
|
3
|
+
A list of messages from [:underline]{sheet.sourcename}[/].
|
4
|
+
|
5
|
+
- `Enter` to push a sheet with all the messages from [:underline]{sheet.cursorRow.room.display_name}[/].
|
6
|
+
- `a` to send a message to [:underline]{sheet.cursorRow.room.display_name}[/].
|
7
|
+
'''
|
8
|
+
|
9
|
+
from visidata import vd, VisiData, Sheet, Column, ItemColumn, date, asyncthread, AttrDict, vlen, Path
|
10
|
+
|
11
|
+
|
12
|
+
vd.option('matrix_token', '', 'matrix API token')
|
13
|
+
vd.option('matrix_user_id', '', 'matrix user ID associated with token')
|
14
|
+
vd.option('matrix_device_id', 'VisiData', 'device ID associated with matrix login')
|
15
|
+
|
16
|
+
vd.matrix_client = None
|
17
|
+
|
18
|
+
|
19
|
+
@VisiData.api
|
20
|
+
def openhttp_matrix(vd, p):
|
21
|
+
vd.importExternal('matrix_client')
|
22
|
+
|
23
|
+
if not vd.options.matrix_token:
|
24
|
+
from matrix_client.client import MatrixClient
|
25
|
+
|
26
|
+
username = vd.input(f'{p.given} username: ', record=False)
|
27
|
+
password = vd.input('password: ', record=False, display=False)
|
28
|
+
|
29
|
+
vd.matrix_client = MatrixClient(p.given)
|
30
|
+
matrix_token = vd.matrix_client.login(username, password, device_id=vd.options.matrix_device_id)
|
31
|
+
|
32
|
+
vd.setPersistentOptions(matrix_user_id=username, matrix_token=matrix_token)
|
33
|
+
|
34
|
+
vd.timeouts_before_idle = -1
|
35
|
+
return MatrixSheet(p.base_stem, source=p)
|
36
|
+
|
37
|
+
vd.open_matrix = vd.openhttp_matrix
|
38
|
+
|
39
|
+
|
40
|
+
class MatrixRoomsSheet(Sheet):
|
41
|
+
def iterload(self):
|
42
|
+
yield from vd.matrix_client.get_rooms().values()
|
43
|
+
|
44
|
+
|
45
|
+
class MatrixSheet(Sheet):
|
46
|
+
help = __doc__
|
47
|
+
columns = [
|
48
|
+
Column('room', width=12, getter=lambda c,r: r.room and r.room.display_name),
|
49
|
+
ItemColumn('sender', width=0),
|
50
|
+
Column('sender', width=10, getter=lambda c,r: r.sender.split(':')[0][1:]),
|
51
|
+
Column('timestamp', width=16, type=date, getter=lambda c,r: r and r.origin_server_ts/1000, fmtstr='%Y-%m-%d %H:%m'),
|
52
|
+
ItemColumn('type', width=15),
|
53
|
+
ItemColumn('content', width=0),
|
54
|
+
ItemColumn('content.body', width=50),
|
55
|
+
ItemColumn('received', type=vlen, width=0),
|
56
|
+
ItemColumn('content.msgtype'),
|
57
|
+
]
|
58
|
+
|
59
|
+
@property
|
60
|
+
def sourcename(self):
|
61
|
+
from matrix_client.room import Room
|
62
|
+
if isinstance(self.source, Room):
|
63
|
+
return self.source.display_name or self.source.room_id
|
64
|
+
else:
|
65
|
+
return str(self.source)
|
66
|
+
|
67
|
+
@asyncthread
|
68
|
+
def reload(self):
|
69
|
+
from matrix_client.client import MatrixClient
|
70
|
+
from matrix_client.room import Room
|
71
|
+
if not vd.matrix_client:
|
72
|
+
vd.matrix_client = MatrixClient(self.source.given,
|
73
|
+
token=self.options.matrix_token,
|
74
|
+
user_id=self.options.matrix_user_id)
|
75
|
+
|
76
|
+
if isinstance(self.source, Room):
|
77
|
+
self.add_room(self.source)
|
78
|
+
self.get_room_messages(self.source)
|
79
|
+
return
|
80
|
+
|
81
|
+
for room in vd.matrix_client.get_rooms().values():
|
82
|
+
self.add_room(room)
|
83
|
+
room.backfill_previous_messages(limit=1)
|
84
|
+
|
85
|
+
vd.matrix_client.add_listener(self.global_event)
|
86
|
+
vd.matrix_client.add_ephemeral_listener(self.global_event)
|
87
|
+
|
88
|
+
vd.matrix_client.start_listener_thread(exception_handler=vd.exceptionCaught)
|
89
|
+
|
90
|
+
vd.matrix_client._sync()
|
91
|
+
|
92
|
+
vd.matrix_client.listen_for_events() # vd.matrix_client.sync(full_state=True)
|
93
|
+
|
94
|
+
def add_room(self, room):
|
95
|
+
room.add_listener(self.room_event)
|
96
|
+
room.add_ephemeral_listener(self.room_event)
|
97
|
+
room.add_state_listener(self.global_event)
|
98
|
+
|
99
|
+
@asyncthread
|
100
|
+
def get_room_messages(self, room):
|
101
|
+
while room.prev_batch:
|
102
|
+
ret = vd.matrix_client.api.get_room_messages(room.room_id, room.prev_batch, direction='b', limit=100)
|
103
|
+
for r in ret['chunk']:
|
104
|
+
r['room'] = room
|
105
|
+
self.addRow(r)
|
106
|
+
|
107
|
+
if 'end' not in ret or ret['end'] == room.prev_batch:
|
108
|
+
break
|
109
|
+
room.prev_batch = ret['end']
|
110
|
+
|
111
|
+
def addRow(self, r, **kwargs):
|
112
|
+
r = AttrDict(r)
|
113
|
+
if r.event_id not in self.event_index:
|
114
|
+
super().addRow(r, **kwargs)
|
115
|
+
self.event_index[r.event_id] = r
|
116
|
+
|
117
|
+
def global_event(self, chunk):
|
118
|
+
self.addRow(chunk)
|
119
|
+
|
120
|
+
def room_event(self, room, chunk):
|
121
|
+
ev = AttrDict(chunk)
|
122
|
+
t = chunk['type']
|
123
|
+
if t == 'm.receipt':
|
124
|
+
for msgid, content in chunk['content'].items():
|
125
|
+
msg = self.event_index.setdefault(msgid, {})
|
126
|
+
if 'received' not in msg:
|
127
|
+
msg['received'] = {}
|
128
|
+
|
129
|
+
for t, c in content.items():
|
130
|
+
assert t == 'm.read'
|
131
|
+
for userid, v in c.items():
|
132
|
+
msg['received'][(t, userid)] = v['ts']/1000
|
133
|
+
return
|
134
|
+
|
135
|
+
chunk['room'] = room
|
136
|
+
self.addRow(chunk)
|
137
|
+
|
138
|
+
def add_message(self, text):
|
139
|
+
vd.matrix_client.send_text(text)
|
140
|
+
|
141
|
+
def openRow(self, row):
|
142
|
+
return MatrixSheet(row.room.display_name, source=row.room, last_id=row.event_id)
|
143
|
+
|
144
|
+
|
145
|
+
MatrixSheet.init('event_index', dict)
|
146
|
+
|
147
|
+
MatrixSheet.addCommand('a', 'add-row', 'cursorRow.room.send_text(input(cursorRow.room.display_name+"> "))', 'send chat message to current room')
|
148
|
+
vd.addMenuItems('Row > Add > send matrix message > add-msg')
|
@@ -0,0 +1,306 @@
|
|
1
|
+
'''# RedditSheet
|
2
|
+
|
3
|
+
- [:keys]Ctrl+O[/] to open a browser tab to [:code]{sheet.cursorRow.display_name_prefixed}[/]
|
4
|
+
- [:keys]g Ctrl+O[/] to open browser windows for {sheet.nSelectedRows} selected subreddits
|
5
|
+
|
6
|
+
- [:keys]Enter[/] to open sheet with top ~1000 submissions for [:code]{sheet.cursorRow.display_name_prefixed}[/]
|
7
|
+
- [:keys]g Enter[/] to open sheet with top ~1000 submissions for {sheet.nSelectedRows} selected subreddits
|
8
|
+
|
9
|
+
- [:keys]ga[/] to append more subreddits matching input by name or description
|
10
|
+
'''
|
11
|
+
|
12
|
+
import visidata
|
13
|
+
from visidata import vd, VisiData, Sheet, AttrColumn, asyncthread, ENTER, anytype, date
|
14
|
+
|
15
|
+
|
16
|
+
vd.option('reddit_client_id', '', 'client_id for reddit api')
|
17
|
+
vd.option('reddit_client_secret', '', 'client_secret for reddit api')
|
18
|
+
vd.option('reddit_user_agent', visidata.__version__, 'user_agent for reddit api')
|
19
|
+
|
20
|
+
|
21
|
+
@VisiData.api
|
22
|
+
def open_reddit(vd, p):
|
23
|
+
vd.importExternal('praw')
|
24
|
+
vd.enable_requests_cache()
|
25
|
+
|
26
|
+
if not vd.options.reddit_client_id:
|
27
|
+
return RedditGuide('reddit_guide')
|
28
|
+
|
29
|
+
if p.given.startswith('r/') or p.given.startswith('/r/'):
|
30
|
+
return SubredditSheet(p.base_stem, source=p.base_stem.split('+'), search=(p.given[0]=='/'))
|
31
|
+
|
32
|
+
if p.given.startswith('u/') or p.given.startswith('/u/'):
|
33
|
+
return RedditorsSheet(p.base_stem, source=p.base_stem.split('+'), search=(p.given[0]=='/'))
|
34
|
+
|
35
|
+
return SubredditSheet(p.base_stem, source=p)
|
36
|
+
|
37
|
+
vd.new_reddit = vd.open_reddit
|
38
|
+
|
39
|
+
@VisiData.cached_property
|
40
|
+
def reddit(vd):
|
41
|
+
import praw
|
42
|
+
return praw.Reddit(check_for_updates=False, **vd.options.getall('reddit_'))
|
43
|
+
|
44
|
+
|
45
|
+
subreddit_hidden_attrs='''
|
46
|
+
name #accounts_active accounts_active_is_fuzzed advertiser_category
|
47
|
+
all_original_content allow_chat_post_creation allow_discovery
|
48
|
+
allow_galleries allow_images allow_polls allow_predictions
|
49
|
+
allow_predictions_tournament allow_videogifs allow_videos
|
50
|
+
banner_background_color banner_background_image banner_img banner_size
|
51
|
+
can_assign_link_flair can_assign_user_flair collapse_deleted_comments
|
52
|
+
comment_score_hide_mins community_icon community_reviewed @created
|
53
|
+
@created_utc description_html disable_contributor_requests
|
54
|
+
display_name display_name_prefixed emoji emojis_custom_size
|
55
|
+
emojis_enabled filters free_form_reports fullname has_menu_widget
|
56
|
+
header_img header_size header_title hide_ads icon_img icon_size
|
57
|
+
is_chat_post_feature_enabled is_crosspostable_subreddit
|
58
|
+
is_enrolled_in_new_modmail key_color lang link_flair_enabled
|
59
|
+
link_flair_position mobile_banner_image mod notification_level
|
60
|
+
original_content_tag_enabled over18 prediction_leaderboard_entry_type
|
61
|
+
primary_color public_description public_description_html public_traffic
|
62
|
+
quaran quarantine restrict_commenting restrict_posting show_media
|
63
|
+
show_media_preview spoilers_enabled submission_type submit_link_label
|
64
|
+
submit_text submit_text_html submit_text_label suggested_comment_sort
|
65
|
+
user_can_flair_in_sr user_flair_background_color user_flair_css_class
|
66
|
+
user_flair_enabled_in_sr user_flair_position user_flair_richtext
|
67
|
+
user_flair_template_id user_flair_text user_flair_text_color
|
68
|
+
user_flair_type user_has_favorited user_is_banned user_is_contributor
|
69
|
+
user_is_moderator user_is_muted user_is_subscriber user_sr_flair_enabled
|
70
|
+
user_sr_theme_enabled #videostream_links_count whitelist_status widgets
|
71
|
+
wiki wiki_enabled wls
|
72
|
+
'''
|
73
|
+
|
74
|
+
post_hidden_attrs='''
|
75
|
+
all_awardings allow_live_comments @approved_at_utc approved_by archived
|
76
|
+
author_flair_background_color author_flair_css_class author_flair_richtext
|
77
|
+
author_flair_template_id author_flair_text author_flair_text_color
|
78
|
+
author_flair_type author_fullname author_patreon_flair author_premium
|
79
|
+
awarders @banned_at_utc banned_by can_gild can_mod_post category clicked
|
80
|
+
comment_limit comment_sort content_categories contest_mode @created_utc
|
81
|
+
discussion_type distinguished domain edited flair fullname gilded
|
82
|
+
gildings hidden hide_score is_crosspostable is_meta is_original_content
|
83
|
+
is_reddit_media_domain is_robot_indexable is_self is_video likes
|
84
|
+
link_flair_background_color link_flair_css_class link_flair_richtext
|
85
|
+
link_flair_text link_flair_text_color link_flair_type locked media
|
86
|
+
media_embed media_only mod mod_note mod_reason_by mod_reason_title
|
87
|
+
mod_reports name no_follow num_crossposts num_duplicates num_reports
|
88
|
+
over_18 parent_whitelist_status permalink pinned pwls quarantine
|
89
|
+
removal_reason removed_by removed_by_category report_reasons saved
|
90
|
+
score secure_media secure_media_embed selftext_html send_replies
|
91
|
+
shortlink spoiler stickied subreddit_id subreddit_name_prefixed
|
92
|
+
subreddit_subscribers subreddit_type suggested_sort thumbnail
|
93
|
+
thumbnail_height thumbnail_width top_awarded_type total_awards_received
|
94
|
+
treatment_tags upvote_ratio user_reports #view_count visited
|
95
|
+
whitelist_status wls
|
96
|
+
'''
|
97
|
+
|
98
|
+
comment_hidden_attrs='''
|
99
|
+
all_awardings @approved_at_utc approved_by archived associated_award
|
100
|
+
author_flair_background_color author_flair_css_class author_flair_richtext
|
101
|
+
author_flair_template_id author_flair_text author_flair_text_color
|
102
|
+
author_flair_type author_fullname author_patreon_flair author_premium
|
103
|
+
awarders @banned_at_utc banned_by body_html can_gild can_mod_post
|
104
|
+
collapsed collapsed_because_crowd_control collapsed_reason comment_type
|
105
|
+
controversiality @created_utc distinguished fullname gilded gildings
|
106
|
+
is_root is_submitter likes link_id locked mod mod_note mod_reason_by
|
107
|
+
mod_reason_title mod_reports name no_follow num_reports parent_id
|
108
|
+
permalink removal_reason report_reasons saved #score #score_hidden
|
109
|
+
send_replies stickied submission subreddit_id subreddit_name_prefixed
|
110
|
+
subreddit_type top_awarded_type total_awards_received treatment_tags
|
111
|
+
user_reports
|
112
|
+
'''
|
113
|
+
|
114
|
+
redditor_hidden_attrs='''
|
115
|
+
#awardee_karma #awarder_karma @created @created_utc
|
116
|
+
fullname has_subscribed has_verified_email hide_from_robots icon_img id
|
117
|
+
is_employee is_friend is_gold is_mod pref_show_snoovatar
|
118
|
+
snoovatar_img snoovatar_size stream #total_karma verified
|
119
|
+
subreddit.banner_img subreddit.name subreddit.over_18 subreddit.public_description #subreddit.subscribers subreddit.title
|
120
|
+
'''
|
121
|
+
|
122
|
+
def hiddenCols(hidden_attrs):
|
123
|
+
coltypes = { t.icon:t.typetype for t in vd.typemap.values() if not t.icon.isalpha() }
|
124
|
+
for attr in hidden_attrs.split():
|
125
|
+
coltype = anytype
|
126
|
+
if attr[0] in coltypes:
|
127
|
+
coltype = coltypes.get(attr[0])
|
128
|
+
attr = attr[1:]
|
129
|
+
yield AttrColumn(attr, type=coltype, width=0)
|
130
|
+
|
131
|
+
|
132
|
+
class SubredditSheet(Sheet):
|
133
|
+
guide = __doc__
|
134
|
+
# source is a text list of subreddits
|
135
|
+
rowtype = 'subreddits' # rowdef: praw.Subreddit
|
136
|
+
nKeys=1
|
137
|
+
search=False
|
138
|
+
columns = [
|
139
|
+
AttrColumn('display_name_prefixed', width=15),
|
140
|
+
AttrColumn('active_user_count', type=int),
|
141
|
+
AttrColumn('subscribers', type=int),
|
142
|
+
AttrColumn('subreddit_type'),
|
143
|
+
AttrColumn('title'),
|
144
|
+
AttrColumn('description', width=50),
|
145
|
+
AttrColumn('url', width=10),
|
146
|
+
] + list(hiddenCols(subreddit_hidden_attrs))
|
147
|
+
|
148
|
+
def iterload(self):
|
149
|
+
for name in self.source:
|
150
|
+
name = name.strip()
|
151
|
+
if self.search:
|
152
|
+
yield from vd.reddit.subreddits.search(name)
|
153
|
+
else:
|
154
|
+
try:
|
155
|
+
r = vd.reddit.subreddit(name)
|
156
|
+
r.display_name_prefixed
|
157
|
+
yield r
|
158
|
+
except Exception as e:
|
159
|
+
vd.exceptionCaught(e)
|
160
|
+
|
161
|
+
def openRow(self, row):
|
162
|
+
return RedditSubmissions(row.display_name_prefixed, source=row)
|
163
|
+
|
164
|
+
def openRows(self, rows):
|
165
|
+
comboname = '+'.join(row.display_name for row in rows)
|
166
|
+
return RedditSubmissions(comboname, source=vd.reddit.subreddit(comboname))
|
167
|
+
|
168
|
+
|
169
|
+
class RedditorsSheet(Sheet):
|
170
|
+
# source is a text list of usernames
|
171
|
+
rowtype = 'redditors' # rowdef: praw.Subreddit
|
172
|
+
nKeys=1
|
173
|
+
columns = [
|
174
|
+
AttrColumn('name', width=15),
|
175
|
+
AttrColumn('comment_karma', type=int),
|
176
|
+
AttrColumn('link_karma', type=int),
|
177
|
+
AttrColumn('comments'),
|
178
|
+
AttrColumn('submissions'),
|
179
|
+
] + list(hiddenCols(redditor_hidden_attrs))
|
180
|
+
|
181
|
+
def iterload(self):
|
182
|
+
for name in self.source:
|
183
|
+
if self.search:
|
184
|
+
yield from vd.reddit.redditors.popular(name)
|
185
|
+
else:
|
186
|
+
yield vd.reddit.redditor(name)
|
187
|
+
|
188
|
+
def openRow(self, row):
|
189
|
+
return RedditSubmissions(row.fullname, source=row.submissions)
|
190
|
+
|
191
|
+
def openRows(self, rows):
|
192
|
+
comboname = '+'.join(row.name for row in rows)
|
193
|
+
return RedditSubmissions(comboname, source=vd.reddit.redditor(comboname).submissions)
|
194
|
+
|
195
|
+
|
196
|
+
class RedditSubmissions(Sheet):
|
197
|
+
guide = '''# Reddit Submissions
|
198
|
+
|
199
|
+
[:keys]Enter[/] to open sheet with comments for the current post
|
200
|
+
[:keys]ga[/] to add posts in this subreddit matching input'''
|
201
|
+
|
202
|
+
# source=ListingGenerator
|
203
|
+
rowtype='reddit posts' # rowdef: praw.Submission
|
204
|
+
nKeys=2
|
205
|
+
columns = [
|
206
|
+
AttrColumn('subreddit'),
|
207
|
+
AttrColumn('id', width=0),
|
208
|
+
AttrColumn('created', width=12, type=date),
|
209
|
+
AttrColumn('author'),
|
210
|
+
AttrColumn('ups', width=8, type=int),
|
211
|
+
AttrColumn('downs', width=8, type=int),
|
212
|
+
AttrColumn('num_comments', width=8, type=int),
|
213
|
+
AttrColumn('title', width=50),
|
214
|
+
AttrColumn('selftext', width=60),
|
215
|
+
AttrColumn('url'),
|
216
|
+
AttrColumn('comments', width=0),
|
217
|
+
] + list(hiddenCols(post_hidden_attrs))
|
218
|
+
|
219
|
+
def iterload(self):
|
220
|
+
kind = 'new' # 'top'
|
221
|
+
f = getattr(self.source, kind, None)
|
222
|
+
if f:
|
223
|
+
yield from f(limit=10000)
|
224
|
+
|
225
|
+
def openRow(self, row):
|
226
|
+
return RedditComments(row.id, source=row.comments.list())
|
227
|
+
|
228
|
+
|
229
|
+
class RedditComments(Sheet):
|
230
|
+
# source=list of comments
|
231
|
+
rowtype='comments' # rowdef: praw.Comment
|
232
|
+
nKeys=2
|
233
|
+
columns=[
|
234
|
+
AttrColumn('subreddit', width=0),
|
235
|
+
AttrColumn('id', width=0),
|
236
|
+
AttrColumn('ups', width=4, type=int),
|
237
|
+
AttrColumn('downs', width=4, type=int),
|
238
|
+
AttrColumn('replies', type=list),
|
239
|
+
AttrColumn('created', type=date),
|
240
|
+
AttrColumn('author'),
|
241
|
+
AttrColumn('depth', type=int),
|
242
|
+
AttrColumn('body', width=60),
|
243
|
+
AttrColumn('edited', width=0),
|
244
|
+
] + list(hiddenCols(comment_hidden_attrs))
|
245
|
+
|
246
|
+
def iterload(self):
|
247
|
+
yield from self.source
|
248
|
+
|
249
|
+
def openRow(self, row):
|
250
|
+
return RedditComments(row.id, source=row.replies)
|
251
|
+
|
252
|
+
|
253
|
+
class RedditGuide(RedditSubmissions):
|
254
|
+
guide = '''# Authenticate Reddit
|
255
|
+
The Reddit API must be configured before use.
|
256
|
+
|
257
|
+
1. Login to Reddit and go to [:underline]https://www.reddit.com/prefs/apps[/].
|
258
|
+
2. Create a "script" app. (Use "[:underline]http://localhost:8000[/]" for the redirect uri)
|
259
|
+
3. Add credentials to visidatarc:
|
260
|
+
|
261
|
+
options.reddit_client_id = '...' # below the description in the upper left
|
262
|
+
options.reddit_client_secret = '...'
|
263
|
+
|
264
|
+
## Use [:code]reddit[/] filetype for subreddits or users
|
265
|
+
|
266
|
+
Multiple may be specified, joined with "+".
|
267
|
+
|
268
|
+
vd r/commandline.reddit
|
269
|
+
vd u/gallowboob.reddit
|
270
|
+
vd r/rust+golang+python.reddit
|
271
|
+
vd u/spez+kn0thing.reddit
|
272
|
+
'''
|
273
|
+
|
274
|
+
@SubredditSheet.api
|
275
|
+
@asyncthread
|
276
|
+
def addRowsFromQuery(sheet, q):
|
277
|
+
for r in vd.reddit.subreddits.search(q):
|
278
|
+
sheet.addRow(r, index=sheet.cursorRowIndex+1)
|
279
|
+
|
280
|
+
|
281
|
+
@RedditSubmissions.api
|
282
|
+
@asyncthread
|
283
|
+
def addRowsFromQuery(sheet, q):
|
284
|
+
for r in sheet.source.search(q, limit=None):
|
285
|
+
sheet.addRow(r, index=sheet.cursorRowIndex+1)
|
286
|
+
|
287
|
+
|
288
|
+
@VisiData.api
|
289
|
+
def sysopen_subreddits(vd, *subreddits):
|
290
|
+
url = "https://www.reddit.com/r/"+"+".join(subreddits)
|
291
|
+
vd.launchBrowser(url)
|
292
|
+
|
293
|
+
|
294
|
+
SubredditSheet.addCommand('^O', 'sysopen-subreddit', 'sysopen_subreddits(cursorRow.display_name)', 'open browser window with subreddit')
|
295
|
+
SubredditSheet.addCommand('g^O', 'sysopen-subreddits', 'sysopen_subreddits(*(row.display_name for row in selectedRows))', 'open browser window with messages from selected subreddits')
|
296
|
+
SubredditSheet.addCommand('g'+ENTER, 'open-subreddits', 'vd.push(openRows(selectedRows))', 'open sheet with top ~1000 submissions for each selected subreddit')
|
297
|
+
SubredditSheet.addCommand('ga', 'add-subreddits-match', 'addRowsFromQuery(input("add subreddits matching: "))', 'add subreddits matching input by name or description')
|
298
|
+
RedditSubmissions.addCommand('ga', 'add-submissions-match', 'addRowsFromQuery(input("add posts matching: "))', 'add posts in this subreddit matching input')
|
299
|
+
|
300
|
+
vd.addMenuItems('''
|
301
|
+
File > Reddit > open selected subreddits > open-subreddits
|
302
|
+
File > Reddit > add > matching subreddits > add-subreddits-match
|
303
|
+
File > Reddit > add > matching submissions > add-submissions-match
|
304
|
+
File > Reddit > open in browser > subreddit in current row > sysopen-subreddit
|
305
|
+
File > Reddit > open in browser > selected subreddits > sysopen-subreddits
|
306
|
+
''')
|