-
Notifications
You must be signed in to change notification settings - Fork 5
/
example.py
executable file
·183 lines (151 loc) · 5.58 KB
/
example.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/usr/bin/env python3
import argparse
import random
import string
import sys
from math import ceil
from time import sleep, asctime, time as time_now
from datetime import date, datetime, time, timedelta
import requests
try:
from fake_useragent import FakeUserAgent
except ImportError:
print('This program needs fake-useragent to run')
sys.exit(1)
from gforms import Form
from gforms.errors import InfiniteLoop, ClosedForm, ValidationError
from gforms.elements_base import Grid
from gforms.elements import UserEmail
from gforms.elements import Dropdown, Scale, Radio, Checkboxes, Value
from gforms.elements import Date, DateTime, Time, Duration
def lowexp(n=10, m=None):
"""Returns mostly low integer values in range [1, n].
May be useful for Scale elements
n: The maximal value.
m: The mean of used exponential distribution.
"""
if m is None:
# You may want to modify m to get higher / lower values.
# ~2.5% chance of 7+ if n == 10
# ~2.5% chance of 4+ if n == 5
m = 6 / n
while True:
res = ceil(random.expovariate(m))
if res <= n:
return res
def highexp(n=10, m=None):
"""Returns mostly high integer values in range [1, n].
For details see lowexp.
"""
return n + 1 - lowexp(n, m)
def random_email():
domain = random.choice(['example.com', 'example.org', 'example.net'])
username = ''.join(random.choices(string.ascii_lowercase, k=random.randint(6, 8)))
return f'{username}@{domain}'
class Filler:
def __init__(self):
self.mapping = {}
def callback(self, elem, i, j):
if isinstance(elem, UserEmail):
return random_email()
if isinstance(elem, Scale):
return lowexp(len(elem.options)) # custom choice function
if isinstance(elem, Radio):
return Value.DEFAULT # random choice
if isinstance(elem, Dropdown):
return Value.DEFAULT # random choice
# NOTE The default callback may fail on elements with validators (Grid, Checkboxes).
if isinstance(elem, Grid):
return Value.DEFAULT # random choices
if isinstance(elem, Checkboxes):
return Value.DEFAULT # random choices
# Generate random date / time values
if isinstance(elem, Date) or isinstance(elem, DateTime):
today = date.today().toordinal()
first = today - 10
last = today + 10
date_part = date.fromordinal(random.randint(first, last))
if isinstance(elem, Date):
return date_part
minutes = random.randint(0, 24 * 60 - 1)
time_part = time(minutes // 60, minutes % 60)
return datetime.combine(date_part, time_part)
if isinstance(elem, Time):
minutes = random.randint(0, 24 * 60 - 1)
return time(minutes // 60, minutes % 60)
if isinstance(elem, Duration):
return timedelta(seconds=random.randint(0, 24*3600))
# Text inputs
if elem.id not in self.mapping:
print('Cannot determine a default value for the element:')
print(elem.to_str(2))
self.mapping[elem.id] = input(
'Comma-separated choice list for the element (e.g. "test,qwe,rty"): '
).split(',')
return random.choice(self.mapping[elem.id])
def main():
ua = FakeUserAgent()
# A session for form loading and submission
sess = requests.session()
sess.headers['User-Agent'] = ua.chrome
# Mean time between submissions
mean_dt = 60
parser = argparse.ArgumentParser()
parser.add_argument(
'url',
help='The form url (https://docs.google.com/forms/d/e/.../viewform)'
)
args = parser.parse_args()
form = Form()
form.load(args.url, session=sess)
filler = Filler()
# Show the form
print(form.to_str(2))
# Use filler to cache custom callback values, if needed (will be reused later)
form.fill(filler.callback, fill_optional=True)
# Show the form with answers
print(form.to_str(2, include_answer=True))
print()
for i in range(10):
last = time_now()
print(f'[{asctime()}] Submit {i+1}... ', end='')
sep = ''
filled = False
for _ in range(10):
# Retry if InfiniteLoop is raised
try:
form.fill(filler.callback, fill_optional=True)
filled = True
break
except InfiniteLoop:
sep = ' '
print('X', end='')
sleep(0.1)
except ValidationError as err:
# Most probably the default callback failed on an element with a validator.
print(f'{sep}Failed: {err}')
sleep(1)
break
if not filled:
continue
try:
form.submit(session=sess)
# You may want to use history emulation:
# submission is faster, but it is still experimental
# form.submit(session=sess, emulate_history=True)
print(sep + 'OK', end='')
except ClosedForm:
print(sep + 'Form is closed')
break
except RuntimeError:
# Incorrect response code or page prediction failed
print(sep + 'Failed', end='')
# Poisson distribution for (submissions / timeframe)
delta = random.expovariate(1 / mean_dt)
print(f', sleeping for {round(delta)}s')
sleep(max(0.0, last + delta - time_now()))
if __name__ == '__main__':
try:
main()
except Exception as e:
print('WTF', e)